feat(04-01): update thread service, routes, and hooks for categoryId

- createThread now inserts categoryId from data
- getAllThreads joins categories table, returns categoryName/categoryEmoji
- updateThread accepts optional categoryId
- ThreadListItem interface includes category fields
- useCreateThread hook sends categoryId
- Fix test files to pass categoryId when creating threads

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 16:31:48 +01:00
parent 629e14f60c
commit ed8508110f
4 changed files with 30 additions and 23 deletions

View File

@@ -17,11 +17,11 @@ function createTestApp() {
return { app, db };
}
async function createThreadViaAPI(app: Hono, name: string) {
async function createThreadViaAPI(app: Hono, name: string, categoryId = 1) {
const res = await app.request("/api/threads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
body: JSON.stringify({ name, categoryId }),
});
return res.json();
}
@@ -48,7 +48,7 @@ describe("Thread Routes", () => {
const res = await app.request("/api/threads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "New Tent" }),
body: JSON.stringify({ name: "New Tent", categoryId: 1 }),
});
expect(res.status).toBe(201);

View File

@@ -22,7 +22,7 @@ describe("Thread Service", () => {
describe("createThread", () => {
it("creates thread with name, returns thread with id/status/timestamps", () => {
const thread = createThread(db, { name: "New Tent" });
const thread = createThread(db, { name: "New Tent", categoryId: 1 });
expect(thread).toBeDefined();
expect(thread.id).toBeGreaterThan(0);
@@ -36,7 +36,7 @@ describe("Thread Service", () => {
describe("getAllThreads", () => {
it("returns active threads with candidateCount and price range", () => {
const thread = createThread(db, { name: "Backpack Options" });
const thread = createThread(db, { name: "Backpack Options", categoryId: 1 });
createCandidate(db, thread.id, {
name: "Pack A",
categoryId: 1,
@@ -57,8 +57,8 @@ describe("Thread Service", () => {
});
it("excludes resolved threads by default", () => {
const t1 = createThread(db, { name: "Active Thread" });
const t2 = createThread(db, { name: "Resolved Thread" });
const t1 = createThread(db, { name: "Active Thread", categoryId: 1 });
const t2 = createThread(db, { name: "Resolved Thread", categoryId: 1 });
const candidate = createCandidate(db, t2.id, {
name: "Winner",
categoryId: 1,
@@ -71,8 +71,8 @@ describe("Thread Service", () => {
});
it("includes resolved threads when includeResolved=true", () => {
const t1 = createThread(db, { name: "Active Thread" });
const t2 = createThread(db, { name: "Resolved Thread" });
const t1 = createThread(db, { name: "Active Thread", categoryId: 1 });
const t2 = createThread(db, { name: "Resolved Thread", categoryId: 1 });
const candidate = createCandidate(db, t2.id, {
name: "Winner",
categoryId: 1,
@@ -86,7 +86,7 @@ describe("Thread Service", () => {
describe("getThreadWithCandidates", () => {
it("returns thread with nested candidates array including category info", () => {
const thread = createThread(db, { name: "Tent Options" });
const thread = createThread(db, { name: "Tent Options", categoryId: 1 });
createCandidate(db, thread.id, {
name: "Tent A",
categoryId: 1,
@@ -111,7 +111,7 @@ describe("Thread Service", () => {
describe("createCandidate", () => {
it("adds candidate to thread with all item-compatible fields", () => {
const thread = createThread(db, { name: "Tent Options" });
const thread = createThread(db, { name: "Tent Options", categoryId: 1 });
const candidate = createCandidate(db, thread.id, {
name: "Tent A",
weightGrams: 1200,
@@ -135,7 +135,7 @@ describe("Thread Service", () => {
describe("updateCandidate", () => {
it("updates candidate fields, returns updated candidate", () => {
const thread = createThread(db, { name: "Test" });
const thread = createThread(db, { name: "Test", categoryId: 1 });
const candidate = createCandidate(db, thread.id, {
name: "Original",
categoryId: 1,
@@ -159,7 +159,7 @@ describe("Thread Service", () => {
describe("deleteCandidate", () => {
it("removes candidate, returns deleted candidate", () => {
const thread = createThread(db, { name: "Test" });
const thread = createThread(db, { name: "Test", categoryId: 1 });
const candidate = createCandidate(db, thread.id, {
name: "To Delete",
categoryId: 1,
@@ -182,7 +182,7 @@ describe("Thread Service", () => {
describe("updateThread", () => {
it("updates thread name", () => {
const thread = createThread(db, { name: "Original" });
const thread = createThread(db, { name: "Original", categoryId: 1 });
const updated = updateThread(db, thread.id, { name: "Renamed" });
expect(updated).toBeDefined();
@@ -197,7 +197,7 @@ describe("Thread Service", () => {
describe("deleteThread", () => {
it("removes thread and cascading candidates", () => {
const thread = createThread(db, { name: "To Delete" });
const thread = createThread(db, { name: "To Delete", categoryId: 1 });
createCandidate(db, thread.id, { name: "Candidate", categoryId: 1 });
const deleted = deleteThread(db, thread.id);
@@ -217,7 +217,7 @@ describe("Thread Service", () => {
describe("resolveThread", () => {
it("atomically creates collection item from candidate data and archives thread", () => {
const thread = createThread(db, { name: "Tent Decision" });
const thread = createThread(db, { name: "Tent Decision", categoryId: 1 });
const candidate = createCandidate(db, thread.id, {
name: "Winner Tent",
weightGrams: 1200,
@@ -244,7 +244,7 @@ describe("Thread Service", () => {
});
it("fails if thread is not active", () => {
const thread = createThread(db, { name: "Already Resolved" });
const thread = createThread(db, { name: "Already Resolved", categoryId: 1 });
const candidate = createCandidate(db, thread.id, {
name: "Winner",
categoryId: 1,
@@ -258,8 +258,8 @@ describe("Thread Service", () => {
});
it("fails if candidate is not in thread", () => {
const thread1 = createThread(db, { name: "Thread 1" });
const thread2 = createThread(db, { name: "Thread 2" });
const thread1 = createThread(db, { name: "Thread 1", categoryId: 1 });
const thread2 = createThread(db, { name: "Thread 2", categoryId: 1 });
const candidate = createCandidate(db, thread2.id, {
name: "Wrong Thread",
categoryId: 1,
@@ -271,7 +271,7 @@ describe("Thread Service", () => {
});
it("fails if candidate not found", () => {
const thread = createThread(db, { name: "Test" });
const thread = createThread(db, { name: "Test", categoryId: 1 });
const result = resolveThread(db, thread.id, 9999);
expect(result.success).toBe(false);
expect(result.error).toBeDefined();