111 lines
3.3 KiB
TypeScript
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));
|
|
},
|
|
}; |