add rest api routes for threads, messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 11:33:13 +01:00
parent 8b1cb7e07f
commit a6b09df0a4
3 changed files with 103 additions and 0 deletions

View File

@@ -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,

View File

@@ -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 <alice@example.com>",
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 <bob@example.com>",
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");
});
});

View File

@@ -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;
}