import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { AppContext } from './utils'; import publicRoutes from './routes/public'; import adminRoutes from './routes/admin'; const app = new Hono(); // CORS Middleware app.use( '*', cors({ origin: '*', // In production, restrict this to your frontend's origin allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization', 'X-Anonymous-User-ID', 'x-mworld-origin'], }) ); // Health check app.get('/', (c) => c.text('API is running')); // Mount API routes app.route('/api', publicRoutes); app.route('/api/admin', adminRoutes); // 404 Handler app.notFound((c) => { return c.json({ success: false, error: 'Not found' }, 404); }); // Error Handler app.onError((err, c) => { console.error('An error occurred:', err); return c.json({ success: false, error: 'An internal error occurred' }, 500); }); // Scheduled Task for dissolving posts const scheduledTask = async (env: AppContext['Bindings']) => { console.log('Running scheduled task: Dissolving expired posts...'); const { DB } = env; try { const { results: expiredPosts } = await DB.prepare( `SELECT id, anonymous_user_id, emotion_tag FROM posts WHERE expires_at <= CURRENT_TIMESTAMP` ).all<{ id: string; anonymous_user_id: string; emotion_tag: string }>(); if (!expiredPosts || expiredPosts.length === 0) { console.log('No expired posts to process.'); return; } const postIds = expiredPosts.map((p) => p.id); // Batch fetch hold counts const holdsCountsQuery = ` SELECT post_id, COUNT(id) as hold_count FROM holds WHERE post_id IN (${postIds.map(() => '?').join(',')}) GROUP BY post_id; `; const { results: holdsCounts } = await DB.prepare(holdsCountsQuery) .bind(...postIds) .all<{ post_id: string; hold_count: number }>(); const holdsMap = new Map(); if (holdsCounts) { for (const row of holdsCounts) { holdsMap.set(row.post_id, row.hold_count); } } // Prepare batch insert for summaries const summaryStmts = expiredPosts.map((post) => { const holdCount = holdsMap.get(post.id) || 0; return DB.prepare( `INSERT INTO post_summaries (id, anonymous_user_id, emotion_tag, hold_count, dissolved_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)` ).bind(crypto.randomUUID(), post.anonymous_user_id, post.emotion_tag, holdCount ); }); if (summaryStmts.length > 0) { await DB.batch(summaryStmts); console.log(`Created ${summaryStmts.length} post summaries.`); } // Batch delete expired posts const deleteQuery = `DELETE FROM posts WHERE id IN (${postIds.map(() => '?').join(',')})`; const deleteResult = await DB.prepare(deleteQuery).bind(...postIds).run(); if(deleteResult.success) { console.log(`Successfully deleted ${deleteResult.meta.changes} expired posts.`); } else { console.error(`Failed to delete expired posts: ${deleteResult.error}`); } } catch (e: any) { console.error('Error in scheduled task:', e.message); } }; export default { fetch: app.fetch, scheduled: async (event: ScheduledEvent, env: AppContext['Bindings'], ctx: ExecutionContext) => { ctx.waitUntil(scheduledTask(env)); }, };