286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
|
|
/**
|
||
|
|
* 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,
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
// =====================================================
|
||
|
|
// 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>;
|
||
|
|
|
||
|
|
// =====================================================
|
||
|
|
// 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'),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const simCancelRequestSchema = z.object({
|
||
|
|
reason: z.string().min(1, 'Cancellation reason is required'),
|
||
|
|
effectiveDate: z.string().date().optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const simChangePlanRequestSchema = z.object({
|
||
|
|
newPlanSku: z.string().min(1, 'New plan SKU is required'),
|
||
|
|
effectiveDate: z.string().date().optional(),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const simFeaturesRequestSchema = z.object({
|
||
|
|
features: z.record(z.string(), z.boolean()),
|
||
|
|
});
|
||
|
|
|
||
|
|
// =====================================================
|
||
|
|
// 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>;
|