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:
2026-01-21 18:55:17 -07:00
parent 89daa0c395
commit 0f2de751af
3 changed files with 78 additions and 33 deletions

View File

@ -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);
} }
})); }));

View File

@ -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
} }
}, },
{ {

View File

@ -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 } },