Dissolve/backend/src/index.ts
2026-07-15 20:30:42 +05:30

111 lines
3.3 KiB
TypeScript

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<AppContext>();
// 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<string, number>();
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));
},
};