From a6b09df0a4b7fd438006e4e42669eef4e91592a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Tue, 10 Mar 2026 11:33:13 +0100 Subject: [PATCH] add rest api routes for threads, messages Co-Authored-By: Claude Opus 4.6 --- backend/src/index.ts | 6 +++ backend/src/routes/emails.test.ts | 73 +++++++++++++++++++++++++++++++ backend/src/routes/emails.ts | 24 ++++++++++ 3 files changed, 103 insertions(+) create mode 100644 backend/src/routes/emails.test.ts create mode 100644 backend/src/routes/emails.ts diff --git a/backend/src/index.ts b/backend/src/index.ts index 2bb3809..ac3cd97 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,5 +1,9 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; +import { emailRoutes } from "./routes/emails"; +import { createDatabase } from "./db/index"; + +const db = createDatabase(process.env.DB_PATH ?? "magnumopus.db"); const app = new Hono(); @@ -7,6 +11,8 @@ app.use("*", cors()); app.get("/health", (c) => c.json({ status: "ok" })); +app.route("/api", emailRoutes(db)); + export default { port: Number(process.env.PORT ?? 3000), fetch: app.fetch, diff --git a/backend/src/routes/emails.test.ts b/backend/src/routes/emails.test.ts new file mode 100644 index 0000000..b2ffc53 --- /dev/null +++ b/backend/src/routes/emails.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test, beforeEach } from "bun:test"; +import { Hono } from "hono"; +import { emailRoutes } from "./emails"; +import { createDatabase, insertThread, insertMessage } from "../db/index"; +import type { Database } from "bun:sqlite"; + +let app: Hono; +let db: Database; + +beforeEach(() => { + db = createDatabase(":memory:"); + app = new Hono(); + app.route("/api", emailRoutes(db)); + + insertThread(db, { + threadId: "t001", + subject: "Q1 Planning", + authors: "Alice, Bob", + totalMessages: 2, + tags: "inbox,unread", + timestamp: 1709884532, + accountId: "personal", + }); + + insertMessage(db, { + messageId: "msg001@example.com", + threadId: "t001", + fromHeader: "Alice ", + toHeader: "user@example.com", + subject: "Q1 Planning", + date: "2024-03-08T10:15:32+01:00", + inReplyTo: "", + body: "Hey, let's plan Q1.", + tags: "inbox,unread", + timestamp: 1709884532, + accountId: "personal", + }); + + insertMessage(db, { + messageId: "msg003@example.com", + threadId: "t001", + fromHeader: "Bob ", + toHeader: "alice@example.com", + subject: "Re: Q1 Planning", + date: "2024-03-08T10:16:40+01:00", + inReplyTo: "msg001@example.com", + body: "Sounds good. Tuesday work?", + tags: "inbox", + timestamp: 1709884600, + accountId: "personal", + }); +}); + +describe("GET /api/threads", () => { + test("returns threads for an account", async () => { + const res = await app.request("/api/threads?accountId=personal"); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data.threads).toHaveLength(1); + expect(data.threads[0].subject).toBe("Q1 Planning"); + }); +}); + +describe("GET /api/threads/:threadId/messages", () => { + test("returns messages in a thread sorted by time", async () => { + const res = await app.request("/api/threads/t001/messages"); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data.messages).toHaveLength(2); + expect(data.messages[0].messageId).toBe("msg001@example.com"); + expect(data.messages[1].messageId).toBe("msg003@example.com"); + }); +}); diff --git a/backend/src/routes/emails.ts b/backend/src/routes/emails.ts new file mode 100644 index 0000000..a07177f --- /dev/null +++ b/backend/src/routes/emails.ts @@ -0,0 +1,24 @@ +import { Hono } from "hono"; +import type { Database } from "bun:sqlite"; +import { getThreads, getMessagesForThread } from "../db/index"; + +export function emailRoutes(db: Database): Hono { + const router = new Hono(); + + router.get("/threads", (c) => { + const accountId = c.req.query("accountId"); + if (!accountId) { + return c.json({ error: "accountId query parameter required" }, 400); + } + const threads = getThreads(db, accountId); + return c.json({ threads }); + }); + + router.get("/threads/:threadId/messages", (c) => { + const threadId = c.req.param("threadId"); + const messages = getMessagesForThread(db, threadId); + return c.json({ messages }); + }); + + return router; +}