import { ArchivedFormat, TokenExpiry } from "@linkwarden/types/global"; import { AiTaggingMethod, LinksRouteTo, DashboardSectionType, Theme, } from "@linkwarden/prisma/client"; import { number, z } from "zod"; // const stringField = z.string({ // errorMap: (e) => ({ // message: `Invalid ${e.path}.`, // }), // }); export const ForgotPasswordSchema = z.object({ email: z.string().email(), }); export const ResetPasswordSchema = z.object({ token: z.string(), password: z.string().min(8), }); export const VerifyEmailSchema = z.object({ token: z.string(), }); export const PostTokenSchema = z.object({ name: z.string().max(50), expires: z.enum(TokenExpiry), }); export type PostTokenSchemaType = z.infer; export const PostUserSchema = () => { const emailEnabled = process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; return z.object({ name: z.string().trim().min(1).max(50).optional(), password: z.string().min(8).max(2048).optional(), email: emailEnabled ? z.string().trim().email().toLowerCase() : z.string().nullish(), username: emailEnabled ? z.string().optional() : z .string() .trim() .toLowerCase() .min(3) .max(50) .regex(/^[a-z0-9_-]{3,50}$/), invite: z.boolean().default(false), acceptPromotionalEmails: z.boolean().default(false), }); }; export const UpdateUserSchema = () => { const emailEnabled = process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false; return z.object({ name: z.string().trim().min(1).max(50).optional(), email: emailEnabled ? z.string().trim().email().toLowerCase() : z.string().nullish(), username: z .string() .trim() .toLowerCase() .min(3) .max(50) .regex(/^[a-z0-9_-]{3,50}$/), image: z.string().nullish(), password: z.string().min(8).max(2048).optional(), newPassword: z.string().min(8).max(2048).optional(), oldPassword: z.string().min(8).max(2048).optional(), archiveAsScreenshot: z.boolean().optional(), archiveAsMonolith: z.boolean().optional(), archiveAsPDF: z.boolean().optional(), archiveAsReadable: z.boolean().optional(), archiveAsWaybackMachine: z.boolean().optional(), aiTaggingMethod: z.enum(AiTaggingMethod).optional(), aiPredefinedTags: z.array(z.string().max(20).trim()).max(20).optional(), aiTagExistingLinks: z.boolean().optional(), locale: z.string().max(20).optional(), isPrivate: z.boolean().optional(), preventDuplicateLinks: z.boolean().optional(), collectionOrder: z.array(z.number()).optional(), linksRouteTo: z.enum(LinksRouteTo).optional(), referredBy: z.string().max(100).nullish(), }); }; export const UpdateUserPreferenceSchema = z.object({ theme: z.enum(Theme).optional(), readableFontFamily: z.string().trim().max(100).optional(), readableFontSize: z.string().trim().max(100).optional(), readableLineHeight: z.string().trim().max(100).optional(), readableLineWidth: z.string().trim().max(100).optional(), // archiveAsScreenshot: z.boolean().optional(), // archiveAsMonolith: z.boolean().optional(), // archiveAsPDF: z.boolean().optional(), // archiveAsReadable: z.boolean().optional(), // archiveAsWaybackMachine: z.boolean().optional(), // aiTaggingMethod: z.enum(AiTaggingMethod).optional(), // aiPredefinedTags: z.array(z.string().max(20).trim()).max(20).optional(), // aiTagExistingLinks: z.boolean().optional(), // preventDuplicateLinks: z.boolean().optional(), // linksRouteTo: z.enum(LinksRouteTo).optional(), }); export type UpdateUserPreferenceSchemaType = z.infer< typeof UpdateUserPreferenceSchema >; export const PostSessionSchema = z.object({ username: z.string().min(3).max(50), password: z.string().min(8), sessionName: z.string().trim().max(50).optional(), }); export const PostLinkSchema = z.object({ type: z.enum(["url", "pdf", "image"]).nullish(), url: z.string().trim().max(2048).url().optional(), name: z.string().trim().max(2048).optional(), description: z.string().trim().max(2048).optional(), image: z.enum(["jpeg", "png"]).optional(), collection: z .object({ id: z.number().optional(), name: z.string().trim().max(2048).optional(), }) .optional(), tags: z .array( z.object({ id: z.number().optional(), name: z.string().trim().max(50), }) ) .optional() || [], }); export type PostLinkSchemaType = z.infer; export const UpdateLinkSchema = z.object({ id: z.number(), name: z.string().trim().max(2048).nullish(), url: z.string().trim().max(2048).nullish(), description: z.string().trim().max(2048).nullish(), icon: z.string().trim().max(50).nullish(), iconWeight: z.string().trim().max(50).nullish(), color: z.string().trim().max(50).nullish(), collection: z.object({ id: z.number(), ownerId: z.number(), }), tags: z.array( z.object({ id: z.number().optional(), name: z.string().trim().max(50), }) ), pinnedBy: z .array( z .object({ id: z.number().optional(), }) .optional() ) .optional(), }); export type UpdateLinkSchemaType = z.infer; const ACCEPTED_TYPES = [ "image/jpeg", "image/jpg", "image/png", "application/pdf", "text/plain", "text/html", ]; const NEXT_PUBLIC_MAX_FILE_BUFFER = Number( process.env.NEXT_PUBLIC_MAX_FILE_BUFFER || 10 ); const MAX_FILE_SIZE = NEXT_PUBLIC_MAX_FILE_BUFFER * 1024 * 1024; export const UploadFileSchema = z.object({ file: z .any() .refine((files) => files?.length == 1, "File is required.") .refine( (files) => files?.[0]?.size <= MAX_FILE_SIZE, `Max file size is ${MAX_FILE_SIZE}MB.` ) .refine( (files) => ACCEPTED_TYPES.includes(files?.[0]?.mimetype), `Only ${ACCEPTED_TYPES.join(", ")} files are accepted.` ), id: z.number().optional(), url: z.string().trim().max(2048).url().optional(), format: z.enum(ArchivedFormat), }); export const PostCollectionSchema = z.object({ name: z.string().trim().max(2048), description: z.string().trim().max(2048).optional(), color: z.string().trim().max(50).optional(), icon: z.string().trim().max(50).optional(), iconWeight: z.string().trim().max(50).optional(), parentId: z.number().optional(), }); export type PostCollectionSchemaType = z.infer; export const UpdateCollectionSchema = z.object({ id: z.number(), name: z.string().trim().max(2048), description: z.string().trim().max(2048).optional(), color: z.string().trim().max(50).optional(), isPublic: z.boolean().optional(), icon: z.string().trim().max(50).nullish(), iconWeight: z.string().trim().max(50).nullish(), parentId: z.union([z.number(), z.literal("root")]).nullish(), members: z.array( z.object({ userId: z.number(), canCreate: z.boolean(), canUpdate: z.boolean(), canDelete: z.boolean(), }) ), propagateToSubcollections: z.boolean().optional(), }); export type UpdateCollectionSchemaType = z.infer; export const UpdateTagSchema = z.object({ name: z.string().trim().max(50), }); export type UpdateTagSchemaType = z.infer; export const PostRssSubscriptionSchema = z.object({ name: z.string().max(50), url: z.string().url().max(2048), collectionId: z.number().optional(), collectionName: z.string().max(50).optional(), }); export const PostTagSchema = z.object({ tags: z.array( z.object({ label: z.string().trim().max(50), archiveAsScreenshot: z.boolean().nullish(), archiveAsMonolith: z.boolean().nullish(), archiveAsPDF: z.boolean().nullish(), archiveAsReadable: z.boolean().nullish(), archiveAsWaybackMachine: z.boolean().nullish(), aiTag: z.boolean().nullish(), }) ), }); export type PostTagSchemaType = z.infer; export const TagBulkDeletionSchema = z.object({ tagIds: z.array(z.number()).min(1), }); export type TagBulkDeletionSchemaType = z.infer; export const MergeTagsSchema = z.object({ newTagName: z.string().trim().max(50), tagIds: z.array(z.number()).min(1), }); export type MergeTagsSchemaType = z.infer; export const PostHighlightSchema = z.object({ color: z.string().trim().max(50), comment: z.string().trim().max(2048).nullish(), startOffset: z.number(), endOffset: z.number(), text: z.string().trim().max(2048), linkId: z.number(), }); export type PostHighlightSchemaType = z.infer; export const LinkArchiveActionSchema = z.object({ linkIds: z.array(z.number()).optional(), }); export type LinkArchiveActionSchemaType = z.infer< typeof LinkArchiveActionSchema >; export const DeletePreservationsSchema = z.object({ action: z.enum(["allAndRePreserve", "allBroken"]), }); export type DeletePreservationsSchemaType = z.infer< typeof DeletePreservationsSchema >; export const UpdateDashboardLayoutSchema = z.array( z.object({ type: z.enum(DashboardSectionType), collectionId: z.number().optional(), enabled: z.boolean(), order: z.number().optional(), }) ); export type UpdateDashboardLayoutSchemaType = z.infer< typeof UpdateDashboardLayoutSchema >;