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) => {
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 = {
googleId: profile.id,
email: profile.emails[0].value,
email: email,
name: profile.displayName,
picture: profile.photos?.[0]?.value || null
};
@ -21,6 +27,7 @@ const configurePassport = (passport, db) => {
const user = await findOrCreateUser(db, userProfile);
done(null, user);
} catch (error) {
console.error('Passport strategy error:', error);
done(error, null);
}
}));

View File

@ -18,21 +18,20 @@ async function findOrCreateUser(db, profile) {
const now = new Date();
const result = await db.collection(USER_COLLECTION).findOneAndUpdate(
{ googleId: profile.id },
{ googleId: profile.googleId },
{
$set: {
email: profile.emails[0].value,
name: profile.displayName,
picture: profile.photos?.[0]?.value || null,
email: profile.email,
name: profile.name,
picture: profile.picture || null,
lastLoginAt: now
},
$inc: { loginCount: 1 },
$setOnInsert: {
googleId: profile.id,
googleId: profile.googleId,
isActive: true,
role: 'user',
createdAt: now,
loginCount: 0 // Will be incremented to 1 by $inc
createdAt: now
}
},
{

View File

@ -85,14 +85,50 @@ async function connectToMongoDB() {
}
}
// Helper function to get today's date
const getTodayDate = () => new Date().toISOString().split('T')[0];
// Helper function to get today's date in UTC
const getTodayDateUTC = () => new Date().toISOString().split('T')[0];
// Helper function to get yesterday's date
const getYesterdayDate = () => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday.toISOString().split('T')[0];
// Cache for the most recent date with data
let cachedLatestDate = null;
let cachedLatestDateTimestamp = 0;
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
// 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)
@ -154,7 +190,7 @@ app.get('/last-scrape', requireAuth, async (req, res) => {
// Get daily summary (matches your daily_summaries collection)
app.get('/daily-summary', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
// Try to get today's summary first
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) => {
try {
const days = parseInt(req.query.days) || 15;
// Generate date range
const latestDate = await getLatestDateWithData();
// Generate date range ending at the latest date with data
const dates = [];
const baseDate = new Date(latestDate + 'T00:00:00Z');
for (let i = days - 1; i >= 0; i--) {
const date = new Date();
const date = new Date(baseDate);
date.setDate(date.getDate() - i);
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
app.get('/available-units', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
// Get units that have prices today (excluding furnished units)
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
app.get('/analytics', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
// 1. Monthly Price Trends - aggregate all prices by month
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)
app.get('/recent-activity', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const yesterday = getYesterdayDate();
const today = await getLatestDateWithData();
const yesterday = getYesterdayDate(today);
// Get today's summary for new/rented units
const todaySummary = await db.collection(DAILY_SUMMARIES_COLLECTION)
@ -799,7 +837,7 @@ app.get('/recent-activity', requireAuth, async (req, res) => {
// Get plan statistics
app.get('/plan-stats', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
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)
app.get('/best-deals', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
const limit = parseInt(req.query.limit) || 5;
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)
app.get('/price-drops', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
const daysBack = parseInt(req.query.days) || 7;
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 baseDate = new Date(today + 'T00:00:00Z');
for (let i = 0; i < daysBack; i++) {
const date = new Date();
const date = new Date(baseDate);
date.setDate(date.getDate() - i);
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)
app.get('/stale-inventory', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
const minDays = parseInt(req.query.minDays) || 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
app.get('/market-insights', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const today = await getLatestDateWithData();
// Get various market metrics
const [
@ -1420,8 +1459,8 @@ app.get('/market-insights', requireAuth, async (req, res) => {
// Enhanced available units with more details
app.get('/available-units-enhanced', requireAuth, async (req, res) => {
try {
const today = getTodayDate();
const yesterday = getYesterdayDate();
const today = await getLatestDateWithData();
const yesterday = getYesterdayDate(today);
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' });
}
const today = getTodayDate();
const today = await getLatestDateWithData();
const unit = await db.collection(UNITS_COLLECTION).aggregate([
{ $match: { unit_code: unitCode } },