import { Elysia, t } from "elysia"; import { db } from "../db"; import { tasks, taskComments } from "../db/schema"; import { desc, sql } from "drizzle-orm"; import { auth } from "../lib/auth"; const BEARER_TOKEN = process.env.API_BEARER_TOKEN || "hammer-dev-token"; async function requireSessionOrBearer(request: Request, headers: Record) { const authHeader = headers["authorization"]; if (authHeader === `Bearer ${BEARER_TOKEN}`) return; try { const session = await auth.api.getSession({ headers: request.headers }); if (session?.user) return; } catch {} throw new Error("Unauthorized"); } export interface ActivityFeedItem { type: "progress" | "comment"; timestamp: string; taskId: string; taskNumber: number | null; taskTitle: string; taskStatus: string; // For progress notes note?: string; // For comments commentId?: string; authorName?: string; authorId?: string | null; content?: string; } export const activityRoutes = new Elysia({ prefix: "/api/activity" }) .onError(({ error, set }) => { const msg = (error as any)?.message || String(error); if (msg === "Unauthorized") { set.status = 401; return { error: "Unauthorized" }; } set.status = 500; return { error: "Internal server error" }; }) // GET /api/activity — unified feed of progress notes + comments .get("/", async ({ request, headers, query }) => { await requireSessionOrBearer(request, headers); const limit = Math.min(Number(query.limit) || 50, 200); // Fetch all tasks with progress notes const allTasks = await db.select().from(tasks); // Collect progress note items const items: ActivityFeedItem[] = []; for (const task of allTasks) { const notes = (task.progressNotes || []) as { timestamp: string; note: string }[]; for (const note of notes) { items.push({ type: "progress", timestamp: note.timestamp, taskId: task.id, taskNumber: task.taskNumber, taskTitle: task.title, taskStatus: task.status, note: note.note, }); } } // Fetch all comments const allComments = await db .select() .from(taskComments) .orderBy(desc(taskComments.createdAt)); // Build task lookup for comment items const taskMap = new Map(allTasks.map(t => [t.id, t])); for (const comment of allComments) { const task = taskMap.get(comment.taskId); if (!task) continue; items.push({ type: "comment", timestamp: comment.createdAt.toISOString(), taskId: task.id, taskNumber: task.taskNumber, taskTitle: task.title, taskStatus: task.status, commentId: comment.id, authorName: comment.authorName, authorId: comment.authorId, content: comment.content, }); } // Sort by timestamp descending, take limit items.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); return { items: items.slice(0, limit), total: items.length, }; });