feat: US-007 — Task tRPC procedures (CRUD + filtering)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-02-19 17:02:09 +01:00
parent bdfaab85b6
commit 9cff6a4126

View File

@@ -1,6 +1,7 @@
import { initTRPC } from '@trpc/server'; import { initTRPC } from '@trpc/server';
import { z } from 'zod'; import { z } from 'zod';
import { eq, asc, inArray, and } from 'drizzle-orm'; import { eq, asc, inArray, and, or, like, sql } from 'drizzle-orm';
import { alias } from 'drizzle-orm/sqlite-core';
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';
@@ -175,7 +176,53 @@ const tasksRouter = router({
search: z.string().optional(), search: z.string().optional(),
orderBy: z.enum(['dueDate', 'priority', 'createdAt']).optional(), orderBy: z.enum(['dueDate', 'priority', 'createdAt']).optional(),
}).optional()) }).optional())
.query(() => []), .query(({ input }) => {
const db = getDb();
const parentClients = alias(clients, 'parent_clients');
const searchTerm = input?.search?.trim();
const conditions = and(
input?.projectId !== undefined ? eq(tasks.projectId, input.projectId) : undefined,
input?.status !== undefined ? eq(tasks.status, input.status) : undefined,
searchTerm
? or(
like(tasks.title, `%${searchTerm}%`),
like(tasks.description, `%${searchTerm}%`),
)
: undefined,
);
const orderByClause =
input?.orderBy === 'dueDate'
? asc(tasks.dueDate)
: input?.orderBy === 'priority'
? asc(sql`CASE ${tasks.priority} WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`)
: asc(tasks.createdAt);
return db
.select({
id: tasks.id,
projectId: tasks.projectId,
title: tasks.title,
description: tasks.description,
status: tasks.status,
priority: tasks.priority,
assignee: tasks.assignee,
dueDate: tasks.dueDate,
createdAt: tasks.createdAt,
projectName: projects.name,
clientName: sql<string | null>`CASE WHEN ${clients.parentId} IS NOT NULL THEN ${parentClients.name} ELSE ${clients.name} END`,
subClientName: sql<string | null>`CASE WHEN ${clients.parentId} IS NOT NULL THEN ${clients.name} ELSE NULL END`,
})
.from(tasks)
.leftJoin(projects, eq(tasks.projectId, projects.id))
.leftJoin(clients, eq(projects.clientId, clients.id))
.leftJoin(parentClients, eq(clients.parentId, parentClients.id))
.where(conditions)
.orderBy(orderByClause)
.all();
}),
create: publicProcedure create: publicProcedure
.input(z.object({ .input(z.object({
title: z.string(), title: z.string(),
@@ -186,7 +233,23 @@ const tasksRouter = router({
dueDate: z.number().optional(), dueDate: z.number().optional(),
projectId: z.string().optional(), projectId: z.string().optional(),
})) }))
.mutation(() => null), .mutation(({ input }) => {
const id = crypto.randomUUID();
const now = Date.now();
getDb().insert(tasks).values({
id,
title: input.title,
description: input.description ?? null,
status: input.status ?? 'todo',
priority: input.priority ?? 'medium',
assignee: input.assignee ?? null,
dueDate: input.dueDate ?? null,
projectId: input.projectId ?? null,
createdAt: now,
}).run();
return { id };
}),
update: publicProcedure update: publicProcedure
.input(z.object({ .input(z.object({
id: z.string(), id: z.string(),
@@ -198,10 +261,35 @@ const tasksRouter = router({
dueDate: z.number().optional(), dueDate: z.number().optional(),
projectId: z.string().optional(), projectId: z.string().optional(),
})) }))
.mutation(() => null), .mutation(({ input }) => {
const set: Partial<{
title: string;
description: string | null;
status: string;
priority: string;
assignee: string | null;
dueDate: number | null;
projectId: string | null;
}> = {};
if (input.title !== undefined) set.title = input.title;
if (input.description !== undefined) set.description = input.description;
if (input.status !== undefined) set.status = input.status;
if (input.priority !== undefined) set.priority = input.priority;
if (input.assignee !== undefined) set.assignee = input.assignee;
if (input.dueDate !== undefined) set.dueDate = input.dueDate;
if (input.projectId !== undefined) set.projectId = input.projectId;
if (Object.keys(set).length > 0) {
getDb().update(tasks).set(set).where(eq(tasks.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 }) => {
getDb().delete(tasks).where(eq(tasks.id, input.id)).run();
return { success: true as const };
}),
}); });
const checkpointsRouter = router({ const checkpointsRouter = router({