feat: US-006 — Project tRPC procedures (CRUD)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-02-19 16:58:46 +01:00
parent 14e0a3aca6
commit fa1fd2b9f5

View File

@@ -1,6 +1,6 @@
import { initTRPC } from '@trpc/server'; import { initTRPC } from '@trpc/server';
import { z } from 'zod'; import { z } from 'zod';
import { eq, asc, inArray } from 'drizzle-orm'; import { eq, asc, inArray, and } from 'drizzle-orm';
import { getDb } from '../db'; import { getDb } from '../db';
import { clients, projects, tasks } from '../db/schema'; import { clients, projects, tasks } from '../db/schema';
import { getStore } from '../store'; import { getStore } from '../store';
@@ -101,14 +101,40 @@ const clientsRouter = router({
const projectsRouter = router({ const projectsRouter = router({
list: publicProcedure list: publicProcedure
.input(z.object({ clientId: z.string().optional(), includeArchived: z.boolean().optional() }).optional()) .input(z.object({ clientId: z.string().optional(), includeArchived: z.boolean().optional() }).optional())
.query(() => []), .query(({ input }) => {
listAll: publicProcedure.query(() => [] as Array<{ id: string; name: string }>), const where = and(
input?.clientId !== undefined ? eq(projects.clientId, input.clientId) : undefined,
!input?.includeArchived ? eq(projects.status, 'active') : undefined,
);
return getDb().select().from(projects).where(where).orderBy(asc(projects.name)).all();
}),
listAll: publicProcedure.query(() => {
return getDb().select({ id: projects.id, name: projects.name }).from(projects).orderBy(asc(projects.name)).all();
}),
get: publicProcedure get: publicProcedure
.input(z.object({ id: z.string() })) .input(z.object({ id: z.string() }))
.query(() => null), .query(({ input }) => {
const result = getDb().select().from(projects).where(eq(projects.id, input.id)).all();
return result[0] ?? null;
}),
create: publicProcedure create: publicProcedure
.input(z.object({ name: z.string(), clientId: z.string().optional() })) .input(z.object({ name: z.string(), clientId: z.string().optional() }))
.mutation(() => null), .mutation(({ input }) => {
const id = crypto.randomUUID();
const now = Date.now();
getDb().insert(projects).values({
id,
name: input.name,
clientId: input.clientId ?? null,
status: 'active',
createdAt: now,
}).run();
return { id };
}),
update: publicProcedure update: publicProcedure
.input(z.object({ .input(z.object({
id: z.string(), id: z.string(),
@@ -117,10 +143,28 @@ const projectsRouter = router({
status: z.enum(['active', 'archived']).optional(), status: z.enum(['active', 'archived']).optional(),
aiSummary: z.string().optional(), aiSummary: z.string().optional(),
})) }))
.mutation(() => null), .mutation(({ input }) => {
const set: Partial<{ name: string; clientId: string | null; status: 'active' | 'archived'; aiSummary: string | null }> = {};
if (input.name !== undefined) set.name = input.name;
if (input.clientId !== undefined) set.clientId = input.clientId;
if (input.status !== undefined) set.status = input.status;
if (input.aiSummary !== undefined) set.aiSummary = input.aiSummary;
if (Object.keys(set).length > 0) {
getDb().update(projects).set(set).where(eq(projects.id, input.id)).run();
}
return null;
}),
delete: publicProcedure delete: publicProcedure
.input(z.object({ id: z.string() })) .input(z.object({ id: z.string() }))
.mutation(() => null), .mutation(({ input }) => {
const db = getDb();
// Null out projectId on tasks belonging to this project
db.update(tasks).set({ projectId: null }).where(eq(tasks.projectId, input.id)).run();
// Delete the project
db.delete(projects).where(eq(projects.id, input.id)).run();
return { success: true as const };
}),
}); });
const tasksRouter = router({ const tasksRouter = router({