import { TemplateStyle, TemplateType } from "@prisma/client"; import { z } from "zod"; const email = z .string({ invalid_type_error: "Email needs to be a string", required_error: "Email is required" }) .email({ message: "Invalid email address" }) .transform((e) => e.toLowerCase()); const password = z.string().min(6, "Password needs to be at least 6 characters long"); const id = z .string({ invalid_type_error: "ID needs to be a string", required_error: "ID is required" }) .uuid({ message: "Id needs to be a valid UUID" }); export const UtilitySchemas = { id: z.object({ id, }), email: z.object({ email, }), pagination: z.object({ page: z .number({ invalid_type_error: "Page needs to be a number", required_error: "Page is required", }) .min(1, "Page needs to be at least 1") .default(1) .or( z.string().transform((s) => { return Number(s); }), ), }), }; export const UserSchemas = { credentials: z.object({ email: email, password: password, }), }; const zodSchema = z.record( z.union( [ z .string({ invalid_type_error: "Metadata can only be a string, array of strings or a non-persistent object (https://docs.useplunk.com/working-with-contacts/metadata#non-persistent-metadata)", }) .transform((s) => { return { persistent: true, value: s }; }), z .array( z.string({ invalid_type_error: "Metadata can only be a string, array of strings or a non-persistent object (https://docs.useplunk.com/working-with-contacts/metadata#non-persistent-metadata)", }), ) .transform((s) => { return { persistent: true, value: s }; }), z.object( { persistent: z.boolean({ invalid_type_error: "Persistent should be a boolean" }).optional().default(true), value: z.union([z.string(), z.array(z.string())], { invalid_type_error: "Metadata can only be a string, array of strings or a non-persistent object (https://docs.useplunk.com/working-with-contacts/metadata#non-persistent-metadata)", }), }, { invalid_type_error: "Metadata can only be a string, array of strings or a non-persistent object (https://docs.useplunk.com/working-with-contacts/metadata#non-persistent-metadata)", }, ), ], { invalid_type_error: "Metadata can only be a string, array of strings or a non-persistent object (https://docs.useplunk.com/working-with-contacts/metadata#non-persistent-metadata)", }, ), { invalid_type_error: "Metadata should be an object (https://docs.useplunk.com/working-with-contacts/metadata)" }, ); export const EventSchemas = { post: z.object({ email, subscribed: z .boolean({ invalid_type_error: "Subscribed should be a boolean. Read more: https://docs.useplunk.com/api-reference/actions/track", }) .nullish(), event: z .string({ required_error: "Event is required. Read more: https://docs.useplunk.com/api-reference/actions/track", invalid_type_error: "Event can only be a string. Read more: https://docs.useplunk.com/api-reference/actions/track", }) .transform((n) => n.toLowerCase()) .transform((n) => n.replace(/ /g, "-")), data: zodSchema.nullish(), }), send: z.object({ subscribed: z.boolean({ invalid_type_error: "Subscribed should be a boolean" }).nullish(), from: email.nullish(), name: z.string().nullish(), reply: email.nullish(), to: z .array(email) .max(5, "You can only send transactional emails to 5 people at a time") .or(email.transform((e) => [e])), subject: z.string({ required_error: "Subject is required. Read more: https://docs.useplunk.com/api-reference/transactional/send", }), body: z.string({ required_error: "Body is required. Read more: https://docs.useplunk.com/api-reference/transactional/send", }), headers: z.record(z.string()).nullish(), }), }; export const CampaignSchemas = { send: z.object({ id, live: z.boolean().default(false), delay: z.number().int("Delay needs to be a whole number").nonnegative("Delay needs to be a positive number"), }), create: z.object({ subject: z .string() .min(1, "Subject needs to be at least 1 character long") .max(70, "Subject needs to be less than 70 characters long"), body: z.string().min(1, "Body needs to be at least 1 character long"), email: email.nullish().or(z.literal("")), from: z.string().nullish(), recipients: z.array(z.string()), style: z.nativeEnum(TemplateStyle).default("PLUNK"), }), update: z.object({ id, subject: z .string() .min(1, "Subject needs to be at least 1 character long") .max(70, "Subject needs to be less than 70 characters long"), body: z.string().min(1, "Body needs to be at least 1 character long"), email: email.nullish().or(z.literal("")), from: z.string().nullish(), recipients: z.array(z.string()), style: z.nativeEnum(TemplateStyle).default("PLUNK"), }), }; export const ActionSchemas = { create: z.object({ name: z.string().min(1, "Name needs to be at least 1 character long"), runOnce: z.boolean().default(false), delay: z.number().int("Delay needs to be a whole number").nonnegative("Delay needs to be a positive number"), template: id, events: z.array(id).min(1, "Select at least one event"), notevents: z.array(id).optional().default([]), }), update: z.object({ id, name: z.string().min(1, "Name needs to be at least 1 character long"), runOnce: z.boolean().default(false), delay: z.number().int("Delay needs to be a whole number").nonnegative("Delay needs to be a positive number"), template: id, events: z.array(id).default([]), notevents: z.array(id).optional().default([]), }), }; export const ContactSchemas = { create: z.object({ email, data: z .object({}) .catchall(z.union([z.string(), z.array(z.string())])) .or(z.string().transform((s) => (s === "" ? null : JSON.parse(s)))) .nullish(), subscribed: z.boolean(), }), manage: z .object({ id: id.optional(), email: email.optional(), data: z .object({}) .catchall(z.union([z.string(), z.array(z.string()), z.null()])) .or(z.string().transform((s) => (s === "" ? null : JSON.parse(s)))) .nullish(), subscribed: z.boolean().nullish(), }) .refine( (data) => { return data.id || data.email; }, { message: "Either id or email should be specified" }, ) .refine( (data) => { // if id and email are both present return !(data.id && data.email); }, { message: "Either id or email should be specified" }, ), }; export const TemplateSchemas = { create: z.object({ subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"), body: z.string().min(1, "Body can't be empty"), email: email.nullish().or(z.literal("")), from: z.string().nullish(), type: z.nativeEnum(TemplateType).default("MARKETING"), style: z.nativeEnum(TemplateStyle).default("PLUNK"), }), update: z.object({ id, subject: z.string().min(1, "Subject can't be empty").max(70, "Subject needs to be less than 70 characters long"), body: z.string().min(1, "Body can't be empty"), email: email.nullish().or(z.literal("")), from: z.string().nullish(), type: z.nativeEnum(TemplateType).default("MARKETING"), style: z.nativeEnum(TemplateStyle).default("PLUNK"), }), }; export const MembershipSchemas = { invite: z.object({ id, email, role: z.enum(["MEMBER", "ADMIN"]).default("MEMBER"), }), kick: z.object({ id, email, }), }; export const ProjectSchemas = { secret: z.object({ secret: z.string(), }), create: z.object({ name: z.string().min(1, "Name can't be empty"), url: z .string() .regex(/^(?:(?:https?):\/\/)?(?:[\w-]+\.)+[a-z]{2,}(?:\/[^\s]*)?$/) .transform((u) => (u.startsWith("http") ? u : `https://${u}`)), }), update: z.object({ id: id, name: z.string().min(1, "Name can't be empty"), url: z .string() .regex(/^(?:(?:https?):\/\/)?(?:[\w-]+\.)+[a-z]{2,}(?:\/[^\s]*)?$/) .transform((u) => (u.startsWith("http") ? u : `https://${u}`)), }), analytics: z.object({ method: z.enum(["week", "month", "year"]).default("week"), }), }; export const IdentitySchemas = { create: z.object({ id: id, email: email.refine( (e) => { return !["gmail.com", "outlook.com", "hotmail.com", "yahoo.com", "useplunk.com", "useplunk.dev"].includes( e.split("@")[1], ); }, { message: "Please use your own domain" }, ), }), update: z.object({ id: id, from: z.string().min(1, "Name can't be empty"), }), };