feat: US-007 — Task tRPC procedures (CRUD + filtering)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user