import schedule from "node-cron"; import { insert, execute } from "../db/queryHelper/index.js"; import { getIO, getSocketId } from "../socket.js"; // Run every hour export const startExpirationJob = () => { schedule.schedule("0 * * * *", async () => { console.log("Running listing expiration check..."); try { const result = await execute({ type: 'transaction', handler: async (trx) => { // 1. Identify expired listings (active & not updated in last 48h) const expiredListings = await trx('listings') .select('id', 'title', 'seller_id') .where({ status: 'active', deleted: false }) .whereRaw("updated_at < NOW() - INTERVAL '48 hours'") .forUpdate() .skipLocked(); if (expiredListings.length === 0) { return []; } console.log(`Found ${expiredListings.length} listings to expire.`); // 2. Update status to 'expired' const expiredIds = expiredListings.map(l => l.id); await trx('listings') .whereIn('id', expiredIds) .update({ status: 'expired' }); // 3. Create Notifications & Real-time Alerts for (const listing of expiredListings) { const message = `Your listing "${listing.title}" has expired after 48 hours of inactivity. Click here to re-list it.`; // Insert Notification await trx('notifications').insert({ user_id: listing.seller_id, type: 'listing_expired', message, data: { listing_id: listing.id } }); // Real-time Socket Emit const socketId = getSocketId(listing.seller_id); if (socketId) { getIO().to(socketId).emit("notification", { type: "listing_expired", message, data: { listing_id: listing.id } }); } } return expiredListings; } }); if (result && result.length > 0) { console.log("Expiration check completed successfully."); } } catch (err) { console.error("Error in expiration job:", err); } }); };