mirror of
https://github.com/kennethnym/freya
synced 2026-06-22 01:14:55 +01:00
feat: add conversations endpoint (#141)
This commit is contained in:
110
apps/freya-backend/src/conversations/http.test.ts
Normal file
110
apps/freya-backend/src/conversations/http.test.ts
Normal 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: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
44
apps/freya-backend/src/conversations/http.ts
Normal file
44
apps/freya-backend/src/conversations/http.ts
Normal 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(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -101,6 +101,14 @@ export function conversations(db: Database, userId: string) {
|
|||||||
return insertConversation(db, userId)
|
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> {
|
async getOrCreateConversation(): Promise<ConversationRow> {
|
||||||
return db.transaction(async (tx) => {
|
return db.transaction(async (tx) => {
|
||||||
await requireUserForUpdate(tx, userId)
|
await requireUserForUpdate(tx, userId)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { registerAuthHandlers } from "./auth/http.ts"
|
|||||||
import { createAuth } from "./auth/index.ts"
|
import { createAuth } from "./auth/index.ts"
|
||||||
import { createRequireSession } from "./auth/session-middleware.ts"
|
import { createRequireSession } from "./auth/session-middleware.ts"
|
||||||
import { CalDavSourceProvider } from "./caldav/provider.ts"
|
import { CalDavSourceProvider } from "./caldav/provider.ts"
|
||||||
|
import { registerConversationsHttpHandlers } from "./conversations/http.ts"
|
||||||
import { createDatabase } from "./db/index.ts"
|
import { createDatabase } from "./db/index.ts"
|
||||||
import { registerFeedHttpHandlers } from "./engine/http.ts"
|
import { registerFeedHttpHandlers } from "./engine/http.ts"
|
||||||
import { createFeedEnhancer } from "./enhancement/enhance-feed.ts"
|
import { createFeedEnhancer } from "./enhancement/enhance-feed.ts"
|
||||||
@@ -108,6 +109,7 @@ function main() {
|
|||||||
|
|
||||||
registerAuthHandlers(app, auth)
|
registerAuthHandlers(app, auth)
|
||||||
|
|
||||||
|
registerConversationsHttpHandlers(app, { db, authSessionMiddleware })
|
||||||
registerFeedHttpHandlers(app, {
|
registerFeedHttpHandlers(app, {
|
||||||
sessionManager,
|
sessionManager,
|
||||||
authSessionMiddleware,
|
authSessionMiddleware,
|
||||||
|
|||||||
Reference in New Issue
Block a user