feat: add conversations endpoint (#141)

This commit is contained in:
2026-06-17 23:01:08 +01:00
committed by GitHub
parent e11051b04b
commit e1c58cdf28
4 changed files with 164 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
import { beforeEach, describe, expect, mock, test } from "bun:test"
import { Hono } from "hono"
import type { Database } from "../db/index.ts"
import type { ConversationRow } from "./storage.ts"
import { mockAuthSessionMiddleware } from "../auth/session-middleware.ts"
import { registerConversationsHttpHandlers } from "./http.ts"
const MockUserId = "k7Gx2mPqRvNwYs9TdLfA4bHcJeUo1iZn"
const conversationRowsByUser = new Map<string, ConversationRow[]>()
mock.module("./storage.ts", () => ({
conversations: (_db: Database, userId: string) => ({
async listConversations(): Promise<ConversationRow[]> {
return conversationRowsByUser.get(userId) ?? []
},
}),
}))
const fakeDb = {} as Database
function buildTestApp(userId?: string) {
const app = new Hono()
registerConversationsHttpHandlers(app, {
db: fakeDb,
authSessionMiddleware: mockAuthSessionMiddleware(userId),
})
return app
}
function createConversationRow(
id: string,
createdAt: string,
updatedAt: string,
userId = MockUserId,
): ConversationRow {
return {
id,
userId,
createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
}
}
describe("GET /api/conversations", () => {
beforeEach(() => {
conversationRowsByUser.clear()
})
test("returns 401 without auth", async () => {
const app = buildTestApp()
const res = await app.request("/api/conversations")
expect(res.status).toBe(401)
})
test("returns conversation summaries for the authenticated user", async () => {
conversationRowsByUser.set(MockUserId, [
createConversationRow(
"conversation-newer",
"2026-06-16T10:00:00.000Z",
"2026-06-17T09:30:00.000Z",
),
createConversationRow(
"conversation-older",
"2026-06-15T10:00:00.000Z",
"2026-06-16T09:30:00.000Z",
),
])
const app = buildTestApp("user-1")
const res = await app.request("/api/conversations")
expect(res.status).toBe(200)
const body = (await res.json()) as {
conversations: Array<{ id: string; createdAt: string; updatedAt: string }>
}
expect(body).toEqual({
conversations: [
{
id: "conversation-newer",
createdAt: "2026-06-16T10:00:00.000Z",
updatedAt: "2026-06-17T09:30:00.000Z",
},
{
id: "conversation-older",
createdAt: "2026-06-15T10:00:00.000Z",
updatedAt: "2026-06-16T09:30:00.000Z",
},
],
})
})
test("returns an empty list when no conversations exist", async () => {
const app = buildTestApp("user-1")
const res = await app.request("/api/conversations")
expect(res.status).toBe(200)
const body = (await res.json()) as {
conversations: Array<{ id: string; createdAt: string; updatedAt: string }>
}
expect(body).toEqual({
conversations: [],
})
})
})

View File

@@ -0,0 +1,44 @@
import type { Context, Hono } from "hono"
import { createMiddleware } from "hono/factory"
import type { AuthSessionMiddleware } from "../auth/session-middleware.ts"
import type { Database } from "../db/index.ts"
import { conversations } from "./storage.ts"
type Env = {
Variables: {
db: Database
}
}
interface ConversationsHttpHandlersDeps {
db: Database
authSessionMiddleware: AuthSessionMiddleware
}
export function registerConversationsHttpHandlers(
app: Hono,
{ db, authSessionMiddleware }: ConversationsHttpHandlersDeps,
) {
const inject = createMiddleware<Env>(async (c, next) => {
c.set("db", db)
await next()
})
app.get("/api/conversations", inject, authSessionMiddleware, handleListConversations)
}
async function handleListConversations(c: Context<Env>) {
const user = c.get("user")!
const db = c.get("db")
return c.json({
conversations: (await conversations(db, user.id).listConversations()).map((row) => ({
id: row.id,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
})),
})
}

View File

@@ -101,6 +101,14 @@ export function conversations(db: Database, userId: string) {
return insertConversation(db, userId)
},
async listConversations(): Promise<ConversationRow[]> {
return db
.select()
.from(conversationsTable)
.where(eq(conversationsTable.userId, userId))
.orderBy(desc(conversationsTable.updatedAt), desc(conversationsTable.createdAt))
},
async getOrCreateConversation(): Promise<ConversationRow> {
return db.transaction(async (tx) => {
await requireUserForUpdate(tx, userId)

View File

@@ -9,6 +9,7 @@ import { registerAuthHandlers } from "./auth/http.ts"
import { createAuth } from "./auth/index.ts"
import { createRequireSession } from "./auth/session-middleware.ts"
import { CalDavSourceProvider } from "./caldav/provider.ts"
import { registerConversationsHttpHandlers } from "./conversations/http.ts"
import { createDatabase } from "./db/index.ts"
import { registerFeedHttpHandlers } from "./engine/http.ts"
import { createFeedEnhancer } from "./enhancement/enhance-feed.ts"
@@ -108,6 +109,7 @@ function main() {
registerAuthHandlers(app, auth)
registerConversationsHttpHandlers(app, { db, authSessionMiddleware })
registerFeedHttpHandlers(app, {
sessionManager,
authSessionMiddleware,