diff --git a/backend/src/routes/letters.ts b/backend/src/routes/letters.ts index 1ed83ca..8f0b8ab 100644 --- a/backend/src/routes/letters.ts +++ b/backend/src/routes/letters.ts @@ -14,17 +14,35 @@ export async function letterRoutes(app: FastifyInstance) { // GET /api/letters app.get("/api/letters", async () => { - return db + const rows = await db .select({ id: letters.id, letterDate: letters.letterDate, createdAt: letters.createdAt, deliverAt: letters.deliverAt, readAt: letters.readAt, + content: letters.content, }) .from(letters) .where(sql`(${letters.deliverAt} IS NULL OR ${letters.deliverAt} <= ${utcDate()})`) .orderBy(sql`COALESCE(${letters.letterDate}, DATE(${letters.createdAt})) DESC`); + + return rows.map((r) => ({ + id: r.id, + letterDate: r.letterDate, + createdAt: r.createdAt, + deliverAt: r.deliverAt, + readAt: r.readAt, + snippet: r.content + .replace(/<[^>]+>/g, "") + .replace(/ /g, " ") + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/\s+/g, " ") + .trim() + .slice(0, 120), + })); }); // GET /api/letters/calendar @@ -70,6 +88,19 @@ export async function letterRoutes(app: FastifyInstance) { return { current, longest }; }); + // POST /api/letters/:id/unread — reader marks a letter as unread + app.post<{ Params: { id: string } }>("/api/letters/:id/unread", async (req, reply) => { + const { role } = req.user as { role: string }; + if (role !== "reader") { + return reply.code(403).send({ error: "Only readers can mark letters as unread" }); + } + const id = parseInt(req.params.id, 10); + const rows = await db.select({ id: letters.id }).from(letters).where(eq(letters.id, id)); + if (!rows[0]) return reply.code(404).send({ error: "Letter not found" }); + await db.update(letters).set({ readAt: null }).where(eq(letters.id, id)); + return { ok: true }; + }); + // GET /api/letters/:id app.get<{ Params: { id: string } }>("/api/letters/:id", async (req, reply) => { const id = parseInt(req.params.id, 10); @@ -87,10 +118,9 @@ export async function letterRoutes(app: FastifyInstance) { // Mark as read when the reader opens a letter for the first time const { role } = req.user as { role: string }; if (role === "reader" && !rows[0].readAt) { - await db - .update(letters) - .set({ readAt: new Date().toISOString() }) - .where(eq(letters.id, id)); + const readAt = new Date().toISOString(); + await db.update(letters).set({ readAt }).where(eq(letters.id, id)); + return { ...rows[0], readAt }; } return rows[0]; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 531420c..752e041 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate, NavLink, useNavigate } from "re import Login from "./pages/Login"; import Editor from "./pages/Editor"; import Archive from "./pages/Archive"; +import Inbox from "./pages/Inbox"; import LetterView from "./pages/LetterView"; import { clearToken, getRole } from "./api/client"; @@ -36,7 +37,7 @@ function Nav() { Hi bb ♡
{isWriter && Write} - Archive + {isWriter ? "Archive" : "Inbox"} @@ -71,7 +72,7 @@ export default function App() {