Add Google OAuth authentication system #4
@ -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);
|
||||
}
|
||||
}));
|
||||
|
||||
@ -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
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
87
server.js
87
server.js
@ -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;
|
||||
const latestDate = await getLatestDateWithData();
|
||||
|
||||
// Generate date range
|
||||
// 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 } },
|
||||
|
||||
Reference in New Issue
Block a user