89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
|
|
import { z } from "zod";
|
||
|
|
import { NextResponse } from "next/server";
|
||
|
|
|
||
|
|
export const TASK_STATUSES = ["PENDING", "DONE", "IN_PROGRESS", "CANCELLED"] as const;
|
||
|
|
export const REPORT_STATUSES = ["IN_PROGRESS", "COMPLETED", "SUBMITTED"] as const;
|
||
|
|
|
||
|
|
const httpLink = z
|
||
|
|
.string()
|
||
|
|
.trim()
|
||
|
|
.max(2000)
|
||
|
|
.refine((v) => v === "" || /^https?:\/\//i.test(v), {
|
||
|
|
message: "Link must start with http:// or https://",
|
||
|
|
});
|
||
|
|
|
||
|
|
export const createReportSchema = z.object({
|
||
|
|
managerName: z.string().trim().min(1).max(200),
|
||
|
|
date: z
|
||
|
|
.string()
|
||
|
|
.regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be YYYY-MM-DD")
|
||
|
|
.optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const updateReportSchema = z.object({
|
||
|
|
managerName: z.string().trim().min(1).max(200).optional(),
|
||
|
|
status: z.enum(REPORT_STATUSES).optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const createTaskSchema = z.object({
|
||
|
|
reportId: z.string().min(1),
|
||
|
|
type: z.enum(["PLANNED", "COMPLETED"]),
|
||
|
|
description: z.string().max(2000).default(""),
|
||
|
|
timeEstimate: z.string().max(100).nullish(),
|
||
|
|
notes: z.string().max(2000).nullish(),
|
||
|
|
link: httpLink.nullish(),
|
||
|
|
status: z.enum(TASK_STATUSES).nullish(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const updateTaskSchema = z.object({
|
||
|
|
id: z.string().min(1),
|
||
|
|
type: z.enum(["PLANNED", "COMPLETED"]).optional(),
|
||
|
|
description: z.string().max(2000).optional(),
|
||
|
|
timeEstimate: z.string().max(100).nullish(),
|
||
|
|
notes: z.string().max(2000).nullish(),
|
||
|
|
link: httpLink.nullish(),
|
||
|
|
status: z.enum(TASK_STATUSES).nullish(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const updateSettingSchema = z.object({
|
||
|
|
key: z.enum(["GOOGLE_DRIVE_FOLDER_ID"]),
|
||
|
|
value: z.string().trim().max(500),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const updateUserRoleSchema = z.object({
|
||
|
|
userId: z.string().min(1),
|
||
|
|
role: z.enum(["EMPLOYEE", "ADMIN"]),
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parse and validate a request body. Returns the parsed data, or a
|
||
|
|
* NextResponse 400 that the route should return immediately.
|
||
|
|
*/
|
||
|
|
export async function parseBody<T extends z.ZodTypeAny>(
|
||
|
|
req: Request,
|
||
|
|
schema: T
|
||
|
|
): Promise<{ data: z.infer<T>; error: null } | { data: null; error: NextResponse }> {
|
||
|
|
let json: unknown;
|
||
|
|
try {
|
||
|
|
json = await req.json();
|
||
|
|
} catch {
|
||
|
|
return {
|
||
|
|
data: null,
|
||
|
|
error: NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = schema.safeParse(json);
|
||
|
|
if (!result.success) {
|
||
|
|
return {
|
||
|
|
data: null,
|
||
|
|
error: NextResponse.json(
|
||
|
|
{ error: "Validation failed", details: result.error.flatten().fieldErrors },
|
||
|
|
{ status: 400 }
|
||
|
|
),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return { data: result.data, error: null };
|
||
|
|
}
|