Fix timezone mismatch and OAuth profile field access
- Add getLatestDateWithData() helper that queries the most recent date with price data instead of using server UTC time - Cache latest date for 5 minutes to reduce database queries - Update all date-dependent endpoints to use database-relative dates - Fix OAuth callback profile field access (use profile.email instead of profile.emails[0].value) - Remove loginCount from $setOnInsert to avoid MongoDB conflict error
This commit is contained in:
@ -11,9 +11,15 @@ const configurePassport = (passport, db) => {
|
|||||||
},
|
},
|
||||||
async (accessToken, refreshToken, profile, done) => {
|
async (accessToken, refreshToken, profile, done) => {
|
||||||
try {
|
try {
|
||||||
|
// Safely extract email
|
||||||
|
const email = profile.emails?.[0]?.value;
|
||||||
|
if (!email) {
|
||||||
|
return done(new Error('No email found in Google profile'), null);
|
||||||
|
}
|
||||||
|
|
||||||
const userProfile = {
|
const userProfile = {
|
||||||
googleId: profile.id,
|
googleId: profile.id,
|
||||||
email: profile.emails[0].value,
|
email: email,
|
||||||
name: profile.displayName,
|
name: profile.displayName,
|
||||||
picture: profile.photos?.[0]?.value || null
|
picture: profile.photos?.[0]?.value || null
|
||||||
};
|
};
|
||||||
@ -21,6 +27,7 @@ const configurePassport = (passport, db) => {
|
|||||||
const user = await findOrCreateUser(db, userProfile);
|
const user = await findOrCreateUser(db, userProfile);
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Passport strategy error:', error);
|
||||||
done(error, null);
|
done(error, null);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -18,21 +18,20 @@ async function findOrCreateUser(db, profile) {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
const result = await db.collection(USER_COLLECTION).findOneAndUpdate(
|
const result = await db.collection(USER_COLLECTION).findOneAndUpdate(
|
||||||
{ googleId: profile.id },
|
{ googleId: profile.googleId },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
email: profile.emails[0].value,
|
email: profile.email,
|
||||||
name: profile.displayName,
|
name: profile.name,
|
||||||
picture: profile.photos?.[0]?.value || null,
|
picture: profile.picture || null,
|
||||||
lastLoginAt: now
|
lastLoginAt: now
|
||||||
},
|
},
|
||||||
$inc: { loginCount: 1 },
|
$inc: { loginCount: 1 },
|
||||||
$setOnInsert: {
|
$setOnInsert: {
|
||||||
googleId: profile.id,
|
googleId: profile.googleId,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
role: 'user',
|
role: 'user',
|
||||||
createdAt: now,
|
createdAt: now
|
||||||
loginCount: 0 // Will be incremented to 1 by $inc
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
89
server.js
89
server.js
@ -85,14 +85,50 @@ async function connectToMongoDB() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get today's date
|
// Helper function to get today's date in UTC
|
||||||
const getTodayDate = () => new Date().toISOString().split('T')[0];
|
const getTodayDateUTC = () => new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
// Helper function to get yesterday's date
|
// Cache for the most recent date with data
|
||||||
const getYesterdayDate = () => {
|
let cachedLatestDate = null;
|
||||||
const yesterday = new Date();
|
let cachedLatestDateTimestamp = 0;
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||||
return yesterday.toISOString().split('T')[0];
|
|
||||||
|
// Helper function to get the most recent date with data in the database
|
||||||
|
// This handles timezone mismatches between server and data collection
|
||||||
|
const getLatestDateWithData = async () => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// Return cached value if still valid
|
||||||
|
if (cachedLatestDate && (now - cachedLatestDateTimestamp) < CACHE_TTL) {
|
||||||
|
return cachedLatestDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await db.collection(PRICES_COLLECTION)
|
||||||
|
.find({})
|
||||||
|
.sort({ date_checked: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.project({ date_checked: 1 })
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
cachedLatestDate = result[0].date_checked;
|
||||||
|
cachedLatestDateTimestamp = now;
|
||||||
|
return cachedLatestDate;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching latest date with data:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to UTC today if no data found
|
||||||
|
return getTodayDateUTC();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get yesterday relative to the latest date with data
|
||||||
|
const getYesterdayDate = (today) => {
|
||||||
|
const todayDate = new Date(today + 'T00:00:00Z');
|
||||||
|
todayDate.setDate(todayDate.getDate() - 1);
|
||||||
|
return todayDate.toISOString().split('T')[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to validate unit code (prevents NoSQL injection)
|
// Helper function to validate unit code (prevents NoSQL injection)
|
||||||
@ -154,7 +190,7 @@ app.get('/last-scrape', requireAuth, async (req, res) => {
|
|||||||
// Get daily summary (matches your daily_summaries collection)
|
// Get daily summary (matches your daily_summaries collection)
|
||||||
app.get('/daily-summary', requireAuth, async (req, res) => {
|
app.get('/daily-summary', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
// Try to get today's summary first
|
// Try to get today's summary first
|
||||||
let summary = await db.collection(DAILY_SUMMARIES_COLLECTION)
|
let summary = await db.collection(DAILY_SUMMARIES_COLLECTION)
|
||||||
@ -194,11 +230,13 @@ app.get('/daily-summary', requireAuth, async (req, res) => {
|
|||||||
app.get('/price-history', requireAuth, async (req, res) => {
|
app.get('/price-history', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const days = parseInt(req.query.days) || 15;
|
const days = parseInt(req.query.days) || 15;
|
||||||
|
const latestDate = await getLatestDateWithData();
|
||||||
// Generate date range
|
|
||||||
|
// Generate date range ending at the latest date with data
|
||||||
const dates = [];
|
const dates = [];
|
||||||
|
const baseDate = new Date(latestDate + 'T00:00:00Z');
|
||||||
for (let i = days - 1; i >= 0; i--) {
|
for (let i = days - 1; i >= 0; i--) {
|
||||||
const date = new Date();
|
const date = new Date(baseDate);
|
||||||
date.setDate(date.getDate() - i);
|
date.setDate(date.getDate() - i);
|
||||||
dates.push(date.toISOString().split('T')[0]);
|
dates.push(date.toISOString().split('T')[0]);
|
||||||
}
|
}
|
||||||
@ -248,7 +286,7 @@ app.get('/price-history', requireAuth, async (req, res) => {
|
|||||||
// Get available units with current prices
|
// Get available units with current prices
|
||||||
app.get('/available-units', requireAuth, async (req, res) => {
|
app.get('/available-units', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
// Get units that have prices today (excluding furnished units)
|
// Get units that have prices today (excluding furnished units)
|
||||||
const availableUnits = await db.collection(UNITS_COLLECTION).aggregate([
|
const availableUnits = await db.collection(UNITS_COLLECTION).aggregate([
|
||||||
@ -428,7 +466,7 @@ app.get('/unit/:unitCode/price-history', requireAuth, async (req, res) => {
|
|||||||
// Get comprehensive analytics data
|
// Get comprehensive analytics data
|
||||||
app.get('/analytics', requireAuth, async (req, res) => {
|
app.get('/analytics', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
// 1. Monthly Price Trends - aggregate all prices by month
|
// 1. Monthly Price Trends - aggregate all prices by month
|
||||||
const monthlyTrends = await db.collection(PRICES_COLLECTION).aggregate([
|
const monthlyTrends = await db.collection(PRICES_COLLECTION).aggregate([
|
||||||
@ -670,8 +708,8 @@ app.get('/analytics', requireAuth, async (req, res) => {
|
|||||||
// Get recent activity (new units, rented units, price changes)
|
// Get recent activity (new units, rented units, price changes)
|
||||||
app.get('/recent-activity', requireAuth, async (req, res) => {
|
app.get('/recent-activity', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
const yesterday = getYesterdayDate();
|
const yesterday = getYesterdayDate(today);
|
||||||
|
|
||||||
// Get today's summary for new/rented units
|
// Get today's summary for new/rented units
|
||||||
const todaySummary = await db.collection(DAILY_SUMMARIES_COLLECTION)
|
const todaySummary = await db.collection(DAILY_SUMMARIES_COLLECTION)
|
||||||
@ -799,7 +837,7 @@ app.get('/recent-activity', requireAuth, async (req, res) => {
|
|||||||
// Get plan statistics
|
// Get plan statistics
|
||||||
app.get('/plan-stats', requireAuth, async (req, res) => {
|
app.get('/plan-stats', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
const planStats = await db.collection(UNITS_COLLECTION).aggregate([
|
const planStats = await db.collection(UNITS_COLLECTION).aggregate([
|
||||||
{
|
{
|
||||||
@ -862,7 +900,7 @@ app.get('/plan-stats', requireAuth, async (req, res) => {
|
|||||||
// Get best deals (units priced below their historical average)
|
// Get best deals (units priced below their historical average)
|
||||||
app.get('/best-deals', requireAuth, async (req, res) => {
|
app.get('/best-deals', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
const limit = parseInt(req.query.limit) || 5;
|
const limit = parseInt(req.query.limit) || 5;
|
||||||
|
|
||||||
const bestDeals = await db.collection(UNITS_COLLECTION).aggregate([
|
const bestDeals = await db.collection(UNITS_COLLECTION).aggregate([
|
||||||
@ -987,14 +1025,15 @@ app.get('/best-deals', requireAuth, async (req, res) => {
|
|||||||
// Get price drops (units with recent price decreases)
|
// Get price drops (units with recent price decreases)
|
||||||
app.get('/price-drops', requireAuth, async (req, res) => {
|
app.get('/price-drops', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
const daysBack = parseInt(req.query.days) || 7;
|
const daysBack = parseInt(req.query.days) || 7;
|
||||||
const limit = parseInt(req.query.limit) || 10;
|
const limit = parseInt(req.query.limit) || 10;
|
||||||
|
|
||||||
// Generate date range for the last N days
|
// Generate date range for the last N days relative to latest data date
|
||||||
const dates = [];
|
const dates = [];
|
||||||
|
const baseDate = new Date(today + 'T00:00:00Z');
|
||||||
for (let i = 0; i < daysBack; i++) {
|
for (let i = 0; i < daysBack; i++) {
|
||||||
const date = new Date();
|
const date = new Date(baseDate);
|
||||||
date.setDate(date.getDate() - i);
|
date.setDate(date.getDate() - i);
|
||||||
dates.push(date.toISOString().split('T')[0]);
|
dates.push(date.toISOString().split('T')[0]);
|
||||||
}
|
}
|
||||||
@ -1084,7 +1123,7 @@ app.get('/price-drops', requireAuth, async (req, res) => {
|
|||||||
// Get stale inventory (units on market for a long time)
|
// Get stale inventory (units on market for a long time)
|
||||||
app.get('/stale-inventory', requireAuth, async (req, res) => {
|
app.get('/stale-inventory', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
const minDays = parseInt(req.query.minDays) || 10;
|
const minDays = parseInt(req.query.minDays) || 10;
|
||||||
const limit = parseInt(req.query.limit) || 10;
|
const limit = parseInt(req.query.limit) || 10;
|
||||||
|
|
||||||
@ -1210,7 +1249,7 @@ app.get('/stale-inventory', requireAuth, async (req, res) => {
|
|||||||
// Get market insights and predictions
|
// Get market insights and predictions
|
||||||
app.get('/market-insights', requireAuth, async (req, res) => {
|
app.get('/market-insights', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
// Get various market metrics
|
// Get various market metrics
|
||||||
const [
|
const [
|
||||||
@ -1420,8 +1459,8 @@ app.get('/market-insights', requireAuth, async (req, res) => {
|
|||||||
// Enhanced available units with more details
|
// Enhanced available units with more details
|
||||||
app.get('/available-units-enhanced', requireAuth, async (req, res) => {
|
app.get('/available-units-enhanced', requireAuth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
const yesterday = getYesterdayDate();
|
const yesterday = getYesterdayDate(today);
|
||||||
|
|
||||||
const availableUnits = await db.collection(UNITS_COLLECTION).aggregate([
|
const availableUnits = await db.collection(UNITS_COLLECTION).aggregate([
|
||||||
{
|
{
|
||||||
@ -1613,7 +1652,7 @@ app.get('/unit/:unitCode', requireAuth, async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'Invalid unit code format' });
|
return res.status(400).json({ error: 'Invalid unit code format' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const today = getTodayDate();
|
const today = await getLatestDateWithData();
|
||||||
|
|
||||||
const unit = await db.collection(UNITS_COLLECTION).aggregate([
|
const unit = await db.collection(UNITS_COLLECTION).aggregate([
|
||||||
{ $match: { unit_code: unitCode } },
|
{ $match: { unit_code: unitCode } },
|
||||||
|
|||||||
Reference in New Issue
Block a user