refactor: domain package cleanup

- Remove validation wrapper functions from common/validation.ts (use Zod schemas directly)
- Delete duplicate CheckoutItem/CheckoutTotals/CheckoutCart/OrderCreateResponse from orders/contract.ts
- Delete empty orders/checkout.ts
- Remove unused MIGRATION_STEPS/MIGRATION_TRANSFER_ITEMS UI constants from auth/forms.ts
- Standardize checkout/contract.ts to not re-export schema types
- Fix customer/providers/index.ts to not re-export contract types through providers barrel
This commit is contained in:
barsa 2026-02-24 11:57:43 +09:00
parent 6e51012d21
commit 0c63bc5c33
17 changed files with 33 additions and 178 deletions

View File

@ -1,11 +1,10 @@
import { BadRequestException } from "@nestjs/common";
import { validateUuidV4OrThrow } from "@customer-portal/domain/common";
import { uuidSchema } from "@customer-portal/domain/common";
export function parseUuidOrThrow(id: string, message = "Invalid ID format"): string {
try {
// Domain validator throws its own Error; we translate to a BadRequestException with our message.
return validateUuidV4OrThrow(id);
} catch {
const result = uuidSchema.safeParse(id);
if (!result.success) {
throw new BadRequestException(message);
}
return result.data;
}

View File

@ -3,7 +3,7 @@ import { Logger } from "nestjs-pino";
import { ConfigService } from "@nestjs/config";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { SalesforceConnection } from "./salesforce-connection.service.js";
import type { SalesforceAccountRecord } from "@customer-portal/domain/customer/providers";
import type { SalesforceAccountRecord } from "@customer-portal/domain/customer";
import type { SalesforceResponse } from "@customer-portal/domain/common/providers";
import { customerNumberSchema, salesforceIdSchema } from "@customer-portal/domain/common";

View File

@ -1,7 +1,7 @@
import { Injectable, BadRequestException } from "@nestjs/common";
import type { User as PrismaUser } from "@prisma/client";
import { PrismaService } from "@bff/infra/database/prisma.service.js";
import { normalizeAndValidateEmail } from "@customer-portal/domain/common";
import { emailSchema } from "@customer-portal/domain/common";
import { extractErrorMessage } from "@bff/core/utils/error.util.js";
import { parseUuidOrThrow } from "@bff/core/utils/validation.util.js";
@ -17,7 +17,7 @@ export class UserAuthRepository {
constructor(private readonly prisma: PrismaService) {}
async findByEmail(email: string): Promise<PrismaUser | null> {
const normalized = normalizeAndValidateEmail(email);
const normalized = emailSchema.parse(email);
try {
return await this.prisma.user.findUnique({ where: { email: normalized } });
} catch (error) {
@ -41,7 +41,7 @@ export class UserAuthRepository {
throw new BadRequestException("Email is required to create a user");
}
const normalizedEmail = normalizeAndValidateEmail(data.email);
const normalizedEmail = emailSchema.parse(data.email);
try {
return await this.prisma.user.create({
@ -72,7 +72,7 @@ export class UserAuthRepository {
async updateEmail(id: string, email: string): Promise<void> {
const validId = parseUuidOrThrow(id, INVALID_USER_ID_FORMAT);
const normalized = normalizeAndValidateEmail(email);
const normalized = emailSchema.parse(email);
try {
await this.prisma.user.update({
where: { id: validId },

View File

@ -56,21 +56,3 @@ export function getPasswordStrengthDisplay(strength: number): {
if (strength >= 60) return { label: "Fair", colorClass: "bg-yellow-500" };
return { label: "Weak", colorClass: "bg-red-500" };
}
// ============================================================================
// Migration Info (Business Constants)
// ============================================================================
export const MIGRATION_TRANSFER_ITEMS = [
"All active services",
"Billing history",
"Support cases",
"Account details",
] as const;
export const MIGRATION_STEPS = [
"Enter your legacy portal email and password",
"We verify your account and migrate your data",
"Create a new secure password for the upgraded portal",
"Access your dashboard with all your services ready",
] as const;

View File

@ -107,8 +107,6 @@ export {
PASSWORD_REQUIREMENTS,
checkPasswordStrength,
getPasswordStrengthDisplay,
MIGRATION_TRANSFER_ITEMS,
MIGRATION_STEPS,
type PasswordRequirementKey,
} from "./forms.js";

View File

@ -19,5 +19,5 @@ export const CHECKOUT_ORDER_TYPE = {
export type CheckoutOrderTypeValue = (typeof CHECKOUT_ORDER_TYPE)[keyof typeof CHECKOUT_ORDER_TYPE];
// Re-export types from schema
export type { OrderType, PriceBreakdownItem, CartItem } from "./schema.js";
// Schema-derived types (OrderType, PriceBreakdownItem, CartItem)
// are exported from index.ts, not contract.ts.

View File

@ -1,8 +1,8 @@
/**
* Common Domain - Validation Utilities
*
* Generic validation functions used across all domains.
* These are pure functions with no infrastructure dependencies.
* Generic validation schemas used across all domains.
* These are pure schemas with no infrastructure dependencies.
*/
import { z } from "zod";
@ -23,49 +23,3 @@ export const requiredStringSchema = z.string().min(1, "This field is required").
* Generic schema for customer/account identifiers
*/
export const customerNumberSchema = z.string().min(1, "Customer number is required").trim();
/**
* Normalize and validate an email address
*
* This is a convenience wrapper that throws on invalid input.
* For validation without throwing, use the emailSchema directly with .safeParse()
*
* @throws Error if email format is invalid
*/
export function normalizeAndValidateEmail(email: string): string {
const emailValidationSchema = z
.string()
.email()
.transform(e => e.toLowerCase().trim());
return emailValidationSchema.parse(email);
}
/**
* Validate a UUID (v4)
*
* This is a convenience wrapper that throws on invalid input.
* For validation without throwing, use the uuidSchema directly with .safeParse()
*
* @throws Error if UUID format is invalid
*/
export function validateUuidV4OrThrow(id: string, message = "Invalid UUID format"): string {
const parsed = uuidSchema.safeParse(id);
if (!parsed.success) {
throw new Error(message);
}
return parsed.data;
}
/**
* Check if a string is a valid email (non-throwing)
*/
export function isValidEmail(email: string): boolean {
return z.string().email().safeParse(email).success;
}
/**
* Check if a string is a valid UUID (non-throwing)
*/
export function isValidUuid(id: string): boolean {
return uuidSchema.safeParse(id).success;
}

View File

@ -15,7 +15,13 @@
// Constants
// ============================================================================
export { USER_ROLE, type UserRoleValue } from "./contract.js";
export {
USER_ROLE,
type UserRoleValue,
type SalesforceAccountFieldMap,
type SalesforceAccountRecord,
type SalesforceContactRecord,
} from "./contract.js";
// ============================================================================
// Domain Types (Clean Names - Public API)

View File

@ -9,12 +9,7 @@
export * from "./portal/index.js";
export * from "./whmcs/index.js";
// Provider-specific integration types (BFF-only convenience re-exports)
export type {
SalesforceAccountFieldMap,
SalesforceAccountRecord,
SalesforceContactRecord,
} from "../contract.js";
// Provider-specific WHMCS integration types (BFF-only)
export type {
WhmcsAddClientParams,
WhmcsValidateLoginParams,

View File

@ -1,12 +0,0 @@
/**
* Orders Domain - Checkout Types
*
* Minimal type definitions for checkout flow.
* Frontend handles its own URL param serialization.
*/
// This file is intentionally minimal after cleanup.
// The build/derive/normalize functions were removed as they were
// unnecessary abstractions that should be handled by the frontend.
//
// See CLEANUP_PROPOSAL_NORMALIZERS.md for details.

View File

@ -5,8 +5,6 @@
* Validated types are derived from schemas (see schema.ts).
*/
import type { OrderConfigurations } from "./schema.js";
// ============================================================================
// Order Type Constants
// ============================================================================
@ -132,74 +130,9 @@ export interface UserMapping {
sfAccountId?: string | null | undefined;
}
// ============================================================================
// Checkout Types
// ============================================================================
/**
* Individual item in checkout cart
*/
export interface CheckoutItem {
id: string;
sku: string;
name: string;
description?: string | undefined;
monthlyPrice?: number | undefined;
oneTimePrice?: number | undefined;
quantity: number;
itemType: "plan" | "installation" | "addon" | "activation" | "vpn";
autoAdded?: boolean | undefined;
}
/**
* Checkout totals calculation
*/
export interface CheckoutTotals {
monthlyTotal: number;
oneTimeTotal: number;
}
/**
* Complete checkout cart with items, totals, and configuration
*/
export interface CheckoutCart {
items: CheckoutItem[];
totals: CheckoutTotals;
configuration: OrderConfigurations;
}
/**
* Order creation response from BFF
*/
export interface OrderCreateResponse {
sfOrderId: string;
status: string;
message: string;
}
/**
* Order fulfillment validation result
*/
// NOTE: Provider-specific fulfillment types are intentionally not part of the public orders contract.
// BFF integration code should import them from:
// @customer-portal/domain/orders/providers
// ============================================================================
// Re-export Types from Schema (Schema-First Approach)
// ============================================================================
export type {
// Order item types
OrderItemSummary,
OrderItemDetails,
// Order types
OrderSummary,
OrderDetails,
// Query and creation types
OrderQueryParams,
OrderConfigurationsAddress,
OrderConfigurations,
CreateOrderRequest,
OrderBusinessValidation,
SfOrderIdParam,
} from "./schema.js";
//
// Checkout types (CheckoutItem, CheckoutTotals, CheckoutCart, OrderCreateResponse)
// and other schema-derived types are exported from schema.ts directly.

View File

@ -1,6 +1,7 @@
import {
orderConfigurationsSchema,
orderSelectionsSchema,
type CheckoutTotals,
type OrderConfigurations,
type OrderSelections,
type OrderItemSummary,
@ -10,7 +11,6 @@ import {
type OrderDisplayItemChargeKind,
} from "./schema.js";
import { ORDER_TYPE } from "./contract.js";
import type { CheckoutTotals } from "./contract.js";
import type { SimConfigureFormData } from "../sim/index.js";
import type { WhmcsOrderItem } from "./providers/whmcs/raw.types.js";

View File

@ -15,11 +15,6 @@ export {
type OrderType,
type OrderTypeValue,
type UserMapping,
// Checkout types
type CheckoutItem,
type CheckoutTotals,
type CheckoutCart,
type OrderCreateResponse,
// Constants
ORDER_TYPE,
ORDER_STATUS,

View File

@ -9,7 +9,7 @@ import type {
OrderItemDetails,
OrderItemSummary,
OrderSummary,
} from "../../contract.js";
} from "../../schema.js";
import { normalizeBillingCycle } from "../../helpers.js";
import { orderDetailsSchema, orderSummarySchema, orderItemDetailsSchema } from "../../schema.js";
import type {

View File

@ -4,7 +4,7 @@
* Transforms normalized order data to WHMCS API format.
*/
import type { OrderDetails, OrderItemDetails } from "../../contract.js";
import type { OrderDetails, OrderItemDetails } from "../../schema.js";
import { normalizeBillingCycle } from "../../helpers.js";
import { serializeWhmcsKeyValueMap } from "../../../common/providers/whmcs-utils/index.js";
import {

View File

@ -387,6 +387,10 @@ export type OrderConfigurationsAddress = z.infer<typeof orderConfigurationsAddre
export type OrderConfigurations = z.infer<typeof orderConfigurationsSchema>;
export type CreateOrderRequest = z.infer<typeof createOrderRequestSchema>;
export type OrderBusinessValidation = z.infer<typeof orderBusinessValidationSchema>;
export type CheckoutItem = z.infer<typeof checkoutItemSchema>;
export type CheckoutTotals = z.infer<typeof checkoutTotalsSchema>;
export type CheckoutCart = z.infer<typeof checkoutCartSchema>;
export type OrderCreateResponse = z.infer<typeof orderCreateResponseSchema>;
export type CheckoutBuildCartRequest = z.infer<typeof checkoutBuildCartRequestSchema>;
export type CheckoutBuildCartResponse = z.infer<typeof checkoutBuildCartResponseSchema>;
export type CheckoutSessionCreateOrderRequest = z.infer<

View File

@ -1,11 +1,12 @@
import {
orderConfigurationsSchema,
orderSelectionsSchema,
type CheckoutCart,
type OrderConfigurations,
type CreateOrderRequest,
type OrderSelections,
} from "./schema.js";
import { ORDER_TYPE, type CheckoutCart, type OrderTypeValue } from "./contract.js";
import { ORDER_TYPE, type OrderTypeValue } from "./contract.js";
export type CheckoutOrderTypeValue = Extract<OrderTypeValue, "Internet" | "SIM" | "VPN">;