297 lines
11 KiB
TypeScript
Raw Normal View History

/**
* 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<typeof loginRequestSchema>;
export type SignupRequestInput = z.infer<typeof signupRequestSchema>;
export type PasswordResetRequestInput = z.infer<typeof passwordResetRequestSchema>;
export type PasswordResetInput = z.infer<typeof passwordResetSchema>;
export type SetPasswordRequestInput = z.infer<typeof setPasswordRequestSchema>;
export type ChangePasswordRequestInput = z.infer<typeof changePasswordRequestSchema>;
export type LinkWhmcsRequestInput = z.infer<typeof linkWhmcsRequestSchema>;
export type ValidateSignupRequestInput = z.infer<typeof validateSignupRequestSchema>;
export type AccountStatusRequestInput = z.infer<typeof accountStatusRequestSchema>;
export type SsoLinkRequestInput = z.infer<typeof ssoLinkRequestSchema>;
export type CheckPasswordNeededRequestInput = z.infer<typeof checkPasswordNeededRequestSchema>;
export type RefreshTokenRequestInput = z.infer<typeof refreshTokenRequestSchema>;
// =====================================================
// 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<typeof updateProfileRequestSchema>;
export type UpdateAddressRequest = z.infer<typeof updateAddressRequestSchema>;
export type OrderConfigurations = z.infer<typeof orderConfigurationsSchema>;
export type CreateOrderRequest = z.infer<typeof createOrderRequestSchema>;
export type SimTopupRequest = z.infer<typeof simTopupRequestSchema>;
export type SimCancelRequest = z.infer<typeof simCancelRequestSchema>;
export type SimChangePlanRequest = z.infer<typeof simChangePlanRequestSchema>;
export type SimFeaturesRequest = z.infer<typeof simFeaturesRequestSchema>;
export type ContactRequest = z.infer<typeof contactRequestSchema>;
// =====================================================
// 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<typeof invoiceItemSchema>;
export type Invoice = z.infer<typeof invoiceSchema>;
export type Pagination = z.infer<typeof paginationSchema>;
export type InvoiceList = z.infer<typeof invoiceListSchema>;
// =====================================================
// 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<typeof createMappingRequestSchema>;
export type UpdateMappingRequest = z.infer<typeof updateMappingRequestSchema>;
export type UserIdMapping = z.infer<typeof userIdMappingSchema>;
export type UpdateProfileRequestInput = z.infer<typeof updateProfileRequestSchema>;
export type UpdateAddressRequestInput = z.infer<typeof updateAddressRequestSchema>;
export type ContactRequestInput = z.infer<typeof contactRequestSchema>;