/** * @factory/shared — sharing DTOs (PRODUCT-SPEC §18; prisma `Share`). * * Sharing is per resource (page/record/team) with a scope; public shares use * an unguessable `token` → `/share/:token` URL. */ import { z } from "zod"; /** Share scopes (prisma `ShareScope`). */ export const shareScopeSchema = z.enum(["private", "workspace", "team", "users", "public"]); export type ShareScope = z.infer; const isoDate = z .string() .refine((s) => Number.isFinite(Date.parse(s)), { message: "invalid ISO date" }); export const createShareSchema = z .object({ scope: shareScopeSchema, /** Required when scope = "users". */ userIds: z.array(z.string().min(1)).min(1).max(100).optional(), /** Required when scope = "team". */ teamId: z.string().min(1).optional(), allowComments: z.boolean().default(true), allowDuplicate: z.boolean().default(false), allowSearchIndexing: z.boolean().default(false), /** Public shares may be password-protected. */ password: z.string().min(4).max(128).optional(), /** Expiry (public shares default to expiring; others may omit). */ expiresAt: isoDate.optional(), }) .strict() .superRefine((val, ctx) => { if (val.scope === "users" && (!val.userIds || val.userIds.length === 0)) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "scope 'users' requires userIds", path: ["userIds"], }); } if (val.scope === "team" && !val.teamId) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "scope 'team' requires teamId", path: ["teamId"], }); } if (val.password && val.scope !== "public") { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "password is only supported for public shares", path: ["password"], }); } }); export type CreateShare = z.infer; export const updateShareSchema = z .object({ scope: shareScopeSchema.optional(), userIds: z.array(z.string().min(1)).min(1).max(100).optional(), teamId: z.string().min(1).nullable().optional(), allowComments: z.boolean().optional(), allowDuplicate: z.boolean().optional(), allowSearchIndexing: z.boolean().optional(), password: z.string().min(4).max(128).nullable().optional(), expiresAt: isoDate.nullable().optional(), }) .strict() .refine((v) => Object.keys(v).length > 0, { message: "No fields to update" }); export type UpdateShare = z.infer;