import schedule from "node-cron"; import pool from "../db/pool.js"; import { getIO, getSocketId } from "../socket.js"; // Run every hour export const startExpirationJob = () => { schedule.schedule("0 * * * *", async () => { console.log("Running listing expiration check..."); const client = await pool.connect(); try { await client.query("BEGIN"); // 1. Identify expired listings (active & not updated in last 48h) // Using INTERVAL '48 hours' const findExpiredQuery = ` SELECT id, title, seller_id FROM listings WHERE status = 'active' AND updated_at < NOW() - INTERVAL '48 hours' AND deleted = FALSE FOR UPDATE SKIP LOCKED `; const { rows: expiredListings } = await client.query(findExpiredQuery); if (expiredListings.length === 0) { await client.query("COMMIT"); return; } console.log(`Found ${expiredListings.length} listings to expire.`); // 2. Update status to 'expired' const expiredIds = expiredListings.map(l => l.id); await client.query(` UPDATE listings SET status = 'expired' WHERE id = ANY($1::uuid[]) `, [expiredIds]); // 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 client.query(` INSERT INTO notifications (user_id, type, message, data) VALUES ($1, 'listing_expired', $2, $3) `, [listing.seller_id, message, { 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 } }); } } await client.query("COMMIT"); console.log("Expiration check completed successfully."); } catch (err) { await client.query("ROLLBACK"); console.error("Error in expiration job:", err); } finally { client.release(); } }); };