380 lines
13 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";
import { invoiceStatusSchema } from "../shared/entities";
const invoiceStatusEnum = z.enum(["Paid", "Unpaid", "Overdue", "Cancelled", "Collections"]);
const subscriptionStatusEnum = z.enum([
"Active",
"Suspended",
"Terminated",
"Cancelled",
"Pending",
]);
export const paginationQuerySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(10),
});
export type PaginationQuery = z.infer<typeof paginationQuerySchema>;
export const auditLogQuerySchema = paginationQuerySchema.extend({
action: z.string().optional(),
userId: z.string().uuid().optional(),
});
export type AuditLogQuery = z.infer<typeof auditLogQuerySchema>;
export const dryRunQuerySchema = z.object({
dryRun: z.coerce.boolean().optional(),
});
export type DryRunQuery = z.infer<typeof dryRunQuerySchema>;
export const invoiceListQuerySchema = paginationQuerySchema.extend({
status: invoiceStatusEnum.optional(),
});
export type InvoiceListQuery = z.infer<typeof invoiceListQuerySchema>;
export const subscriptionQuerySchema = z.object({
status: subscriptionStatusEnum.optional(),
});
export type SubscriptionQuery = z.infer<typeof subscriptionQuerySchema>;
// =====================================================
// 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().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").optional(),
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(),
});
export const orderIdParamSchema = z.object({
id: z.coerce.number().int().positive("Order ID must be positive"),
});
// =====================================================
// 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: invoiceStatusSchema,
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>;
export const invoicePaymentLinkSchema = z.object({
paymentMethodId: z.coerce.number().int().positive().optional(),
gatewayName: z.string().min(1).optional(),
});
export type InvoicePaymentLinkInput = z.infer<typeof invoicePaymentLinkSchema>;
export const sfOrderIdParamSchema = z.object({
sfOrderId: z.string().min(1, "Salesforce order ID is required"),
});
export type SfOrderIdParam = z.infer<typeof sfOrderIdParamSchema>;
// =====================================================
// 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>;