feat(backend): subtask category support (schema, validation, sync, routes)

This commit is contained in:
2026-08-10 11:41:25 +02:00
parent 4c3d1a118c
commit f3bcd78e49
15 changed files with 451 additions and 4 deletions
+2
View File
@@ -44,6 +44,7 @@ export const tasks = pgTable('tasks', {
id: text('id').primaryKey(),
userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
categoryId: text('category_id').references(() => categories.id, { onDelete: 'cascade' }),
tags: text('tags').notNull().default(''),
title: text('title').notNull(),
description: text('description').notNull().default(''),
priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'),
@@ -84,6 +85,7 @@ export const subtasks = pgTable('subtasks', {
userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
taskId: text('task_id').notNull().references(() => tasks.id, { onDelete: 'cascade' }),
parentSubtaskId: text('parent_subtask_id').references((): AnyPgColumn => subtasks.id, { onDelete: 'cascade' }),
categoryId: text('category_id').references(() => categories.id, { onDelete: 'set null' }),
title: text('title').notNull(),
description: text('description').notNull().default(''),
priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'),
+1
View File
@@ -82,6 +82,7 @@ router.post('/task/:taskId', asyncHandler(async (req: Request, res: Response) =>
userId,
taskId: req.params.taskId,
parentSubtaskId,
categoryId: data.categoryId ?? null,
title: data.title,
description: data.description ?? '',
priority: data.priority ?? 'none',
+2
View File
@@ -235,6 +235,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => {
title: task.title,
description: task.description,
categoryId: task.categoryId,
tags: task.tags ?? '',
priority: task.priority,
completed: task.completed,
dueDate: task.dueDate,
@@ -290,6 +291,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => {
.set({
taskId: sub.taskId,
parentSubtaskId: sub.parentSubtaskId ?? null,
categoryId: sub.categoryId ?? null,
title: sub.title,
description: sub.description ?? '',
priority: sub.priority ?? 'none',
+2
View File
@@ -135,6 +135,7 @@ router.post('/', asyncHandler(async (req: Request, res: Response) => {
id: taskId,
userId,
categoryId: data.categoryId,
tags: data.tags ?? '',
title: data.title,
description: data.description ?? '',
priority: data.priority ?? 'none',
@@ -159,6 +160,7 @@ router.post('/', asyncHandler(async (req: Request, res: Response) => {
userId,
taskId,
title: st.title,
categoryId: (st as any).categoryId ?? null,
completed: false,
order: index,
createdAt: now,
+5 -1
View File
@@ -39,6 +39,7 @@ export const taskCreateSchema = z.object({
title: z.string().min(1).max(100),
description: z.string().max(1000).optional(),
categoryId: z.string().max(100).transform((v) => (v === '' ? null : v)).nullable(),
tags: z.string().max(500).optional(),
priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(),
dueDate: z.number().int().min(0).optional(),
dueTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')).transform(v => v ?? ''),
@@ -52,13 +53,14 @@ export const taskCreateSchema = z.object({
reminders: z.string().max(100).optional(),
assigneeId: z.string().nullable().optional(),
completedAt: z.number().int().min(0).nullable().optional(),
subtasks: z.array(z.object({ title: z.string().min(1).max(100) })).optional(),
subtasks: z.array(z.object({ title: z.string().min(1).max(100), categoryId: z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)) })).optional(),
});
export const taskUpdateSchema = z.object({
title: z.string().min(1).max(100).optional(),
description: z.string().max(1000).optional(),
categoryId: z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)),
tags: z.string().max(500).optional(),
priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(),
completed: z.boolean().optional(),
dueDate: z.number().int().min(0).optional(),
@@ -72,6 +74,7 @@ export const taskUpdateSchema = z.object({
export const subtaskCreateSchema = z.object({
title: z.string().min(1).max(100),
description: z.string().max(1000).optional(),
categoryId: z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)),
priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(),
dueDate: z.number().int().min(0).optional(),
dueTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')).transform(v => v ?? ''),
@@ -91,6 +94,7 @@ export const subtaskCreateSchema = z.object({
export const subtaskUpdateSchema = z.object({
title: z.string().min(1).max(100).optional(),
description: z.string().max(1000).optional(),
categoryId: z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)),
priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(),
completed: z.boolean().optional(),
dueDate: z.number().int().min(0).optional(),