/** * API Request Schemas * Schemas for data that the backend receives and validates * These are the "source of truth" for business logic validation */ import { z } from 'zod'; import { emailSchema, passwordSchema, nameSchema, phoneSchema, addressSchema, requiredAddressSchema, genderEnum, } from '../shared/primitives'; // ===================================================== // AUTH REQUEST SCHEMAS // ===================================================== export const loginRequestSchema = z.object({ email: emailSchema, password: z.string().min(1, 'Password is required'), }); export const signupRequestSchema = z.object({ email: emailSchema, password: passwordSchema, firstName: nameSchema, lastName: nameSchema, company: z.string().optional(), phone: phoneSchema, sfNumber: z.string().min(6, 'Customer number must be at least 6 characters'), address: requiredAddressSchema, nationality: z.string().optional(), dateOfBirth: z.string().date().optional(), gender: genderEnum.optional(), }); export const passwordResetRequestSchema = z.object({ email: emailSchema, }); export const passwordResetSchema = z.object({ token: z.string().min(1, 'Reset token is required'), password: passwordSchema, }); export const setPasswordRequestSchema = z.object({ email: emailSchema, password: passwordSchema, }); export const changePasswordRequestSchema = z.object({ currentPassword: z.string().min(1, 'Current password is required'), newPassword: passwordSchema, }); export const linkWhmcsRequestSchema = z.object({ email: emailSchema, password: z.string().min(1, 'Password is required'), }); export const validateSignupRequestSchema = z.object({ sfNumber: z.string().min(1, 'Customer number is required'), }); export const accountStatusRequestSchema = z.object({ email: emailSchema, }); export const ssoLinkRequestSchema = z.object({ destination: z.string().optional(), }); export const checkPasswordNeededRequestSchema = z.object({ email: emailSchema, }); export const refreshTokenRequestSchema = z.object({ refreshToken: z.string().min(1, 'Refresh token is required'), deviceId: z.string().optional(), }); // ===================================================== // TYPE EXPORTS // ===================================================== export type LoginRequestInput = z.infer; export type SignupRequestInput = z.infer; export type PasswordResetRequestInput = z.infer; export type PasswordResetInput = z.infer; export type SetPasswordRequestInput = z.infer; export type ChangePasswordRequestInput = z.infer; export type LinkWhmcsRequestInput = z.infer; export type ValidateSignupRequestInput = z.infer; export type AccountStatusRequestInput = z.infer; export type SsoLinkRequestInput = z.infer; export type CheckPasswordNeededRequestInput = z.infer; export type RefreshTokenRequestInput = z.infer; // ===================================================== // USER MANAGEMENT REQUEST SCHEMAS // ===================================================== export const updateProfileRequestSchema = z.object({ firstName: nameSchema.optional(), lastName: nameSchema.optional(), phone: phoneSchema.optional(), company: z.string().max(200).optional(), }); export const updateAddressRequestSchema = addressSchema; // ===================================================== // ORDER REQUEST SCHEMAS // ===================================================== export const orderConfigurationsSchema = z.object({ // Activation (All order types) activationType: z.enum(['Immediate', 'Scheduled']).optional(), scheduledAt: z.string().datetime().optional(), // Internet specific accessMode: z.enum(['IPoE-BYOR', 'IPoE-HGW', 'PPPoE']).optional(), // SIM specific simType: z.enum(['eSIM', 'Physical SIM']).optional(), eid: z.string().optional(), // Required for eSIM // MNP/Porting isMnp: z.string().optional(), // "true" | "false" mnpNumber: z.string().optional(), mnpExpiry: z.string().optional(), mnpPhone: z.string().optional(), mvnoAccountNumber: z.string().optional(), portingLastName: z.string().optional(), portingFirstName: z.string().optional(), portingLastNameKatakana: z.string().optional(), portingFirstNameKatakana: z.string().optional(), portingGender: z.enum(['Male', 'Female', 'Corporate/Other']).optional(), portingDateOfBirth: z.string().date().optional(), // Optional address override captured at checkout address: addressSchema.optional(), }); export const createOrderRequestSchema = z.object({ orderType: z.enum(['Internet', 'SIM', 'VPN', 'Other']), skus: z.array(z.string().min(1, 'SKU cannot be empty')), configurations: orderConfigurationsSchema.optional(), }); // ===================================================== // SUBSCRIPTION MANAGEMENT REQUEST SCHEMAS // ===================================================== export const simTopupRequestSchema = z.object({ amount: z.number().positive('Amount must be positive'), currency: z.string().length(3, 'Currency must be 3 characters').default('JPY'), quotaMb: z.number().positive('Quota in MB must be positive'), }); export const simCancelRequestSchema = z.object({ reason: z.string().min(1, 'Cancellation reason is required').optional(), scheduledAt: z.string().regex(/^\d{8}$/, 'Scheduled date must be in YYYYMMDD format').optional(), }); export const simChangePlanRequestSchema = z.object({ newPlanSku: z.string().min(1, 'New plan SKU is required'), newPlanCode: z.string().min(1, 'New plan code is required'), effectiveDate: z.string().date().optional(), }); export const simFeaturesRequestSchema = z.object({ voiceMailEnabled: z.boolean().optional(), callWaitingEnabled: z.boolean().optional(), internationalRoamingEnabled: z.boolean().optional(), networkType: z.enum(['4G', '5G']).optional(), }); // ===================================================== // CONTACT REQUEST SCHEMAS // ===================================================== export const contactRequestSchema = z.object({ subject: z.string().min(1, 'Subject is required').max(200, 'Subject is too long'), message: z.string().min(1, 'Message is required').max(2000, 'Message is too long'), category: z.enum(['technical', 'billing', 'account', 'general']), priority: z.enum(['low', 'medium', 'high', 'urgent']).default('medium'), }); // ===================================================== // TYPE EXPORTS // ===================================================== export type UpdateProfileRequest = z.infer; export type UpdateAddressRequest = z.infer; export type OrderConfigurations = z.infer; export type CreateOrderRequest = z.infer; export type SimTopupRequest = z.infer; export type SimCancelRequest = z.infer; export type SimChangePlanRequest = z.infer; export type SimFeaturesRequest = z.infer; export type ContactRequest = z.infer; // ===================================================== // INVOICE SCHEMAS // ===================================================== export const invoiceItemSchema = z.object({ id: z.number().int().positive(), description: z.string().min(1, 'Description is required'), amount: z.number().nonnegative('Amount must be non-negative'), quantity: z.number().int().positive().optional().default(1), type: z.string().min(1, 'Type is required'), serviceId: z.number().int().positive().optional(), }); export const invoiceSchema = z.object({ id: z.number().int().positive(), number: z.string().min(1, 'Invoice number is required'), status: z.string().min(1, 'Status is required'), currency: z.string().length(3, 'Currency must be 3 characters'), currencySymbol: z.string().optional(), total: z.number().nonnegative('Total must be non-negative'), subtotal: z.number().nonnegative('Subtotal must be non-negative'), tax: z.number().nonnegative('Tax must be non-negative'), issuedAt: z.string().datetime().optional(), dueDate: z.string().datetime().optional(), paidDate: z.string().datetime().optional(), pdfUrl: z.string().url().optional(), paymentUrl: z.string().url().optional(), description: z.string().optional(), items: z.array(invoiceItemSchema).optional(), }); export const paginationSchema = z.object({ page: z.number().int().min(1), totalPages: z.number().int().min(0), totalItems: z.number().int().min(0), nextCursor: z.string().optional(), }); export const invoiceListSchema = z.object({ invoices: z.array(invoiceSchema), pagination: paginationSchema, }); // ===================================================== // INVOICE TYPE EXPORTS // ===================================================== export type InvoiceItem = z.infer; export type Invoice = z.infer; export type Pagination = z.infer; export type InvoiceList = z.infer; // ===================================================== // ID MAPPING SCHEMAS // ===================================================== export const createMappingRequestSchema = z.object({ userId: z.string().uuid('User ID must be a valid UUID'), whmcsClientId: z.number().int().positive('WHMCS client ID must be a positive integer'), sfAccountId: z.string().regex(/^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/, 'Salesforce account ID must be a valid 15 or 18 character ID').optional(), }); export const updateMappingRequestSchema = z.object({ whmcsClientId: z.number().int().positive('WHMCS client ID must be a positive integer').optional(), sfAccountId: z.string().regex(/^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/, 'Salesforce account ID must be a valid 15 or 18 character ID').optional(), }).refine( (data) => data.whmcsClientId !== undefined || data.sfAccountId !== undefined, { message: 'At least one field must be provided for update' } ); export const userIdMappingSchema = z.object({ userId: z.string().uuid('User ID must be a valid UUID'), whmcsClientId: z.number().int().positive('WHMCS client ID must be a positive integer'), sfAccountId: z.string().regex(/^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/, 'Salesforce account ID must be a valid 15 or 18 character ID').optional(), createdAt: z.string().datetime().optional(), updatedAt: z.string().datetime().optional(), }); // ===================================================== // ID MAPPING TYPE EXPORTS // ===================================================== export type CreateMappingRequest = z.infer; export type UpdateMappingRequest = z.infer; export type UserIdMapping = z.infer; export type UpdateProfileRequestInput = z.infer; export type UpdateAddressRequestInput = z.infer; export type ContactRequestInput = z.infer;