From a3dbd0718343e8660f4df55cdaaf47d2864b452c Mon Sep 17 00:00:00 2001 From: barsa Date: Fri, 26 Dec 2025 14:53:03 +0900 Subject: [PATCH] Enhance ESLint Rules and Refactor Domain Imports - Updated ESLint configuration to enforce stricter import rules for the @customer-portal/domain package, promoting better import hygiene and preventing deep imports. - Refactored various files across the BFF and portal applications to comply with the new import rules, ensuring that only the appropriate modules are imported from the domain. - Cleaned up unused imports and optimized code structure for improved maintainability and clarity. - Updated documentation to reflect changes in import practices and domain structure. --- .cursor/rules/portal-rule.mdc | 3 +- apps/bff/src/infra/mappers/user.mapper.ts | 2 +- .../salesforce/salesforce.service.ts | 2 +- .../services/salesforce-account.service.ts | 4 +- .../services/salesforce-case.service.ts | 11 +- .../salesforce-opportunity.service.ts | 2 +- .../services/salesforce-order.service.ts | 15 +- .../whmcs-connection-orchestrator.service.ts | 18 +-- .../services/whmcs-error-handler.service.ts | 7 +- .../services/whmcs-http-client.service.ts | 2 +- .../whmcs-account-discovery.service.ts | 2 +- .../whmcs/services/whmcs-client.service.ts | 4 +- .../whmcs/services/whmcs-currency.service.ts | 26 ++-- .../whmcs/services/whmcs-invoice.service.ts | 7 +- .../whmcs/services/whmcs-order.service.ts | 6 +- .../whmcs/services/whmcs-payment.service.ts | 14 +- .../whmcs/services/whmcs-sso.service.ts | 6 +- .../services/whmcs-subscription.service.ts | 4 +- .../src/integrations/whmcs/whmcs.service.ts | 21 +-- .../workflows/signup-workflow.service.ts | 2 +- .../workflows/whmcs-link-workflow.service.ts | 2 +- .../modules/currency/currency.controller.ts | 6 +- .../orders/config/order-field-map.service.ts | 2 +- .../order-fulfillment-orchestrator.service.ts | 8 +- .../order-fulfillment-validator.service.ts | 4 +- .../services/order-pricebook.service.ts | 4 +- .../services/order-validator.service.ts | 2 +- .../services/base-services.service.ts | 8 +- .../services/internet-services.service.ts | 6 +- .../services/services/sim-services.service.ts | 4 +- .../services/services/vpn-services.service.ts | 8 +- .../services/sim-topup-pricing.service.ts | 2 +- .../subscriptions/subscriptions.controller.ts | 4 +- .../subscriptions/subscriptions.service.ts | 2 +- .../users/infra/user-profile.service.ts | 2 +- .../verification/residence-card.service.ts | 2 +- apps/portal/src/components/atoms/README.md | 8 +- .../dashboard/utils/dashboard.utils.ts | 4 +- apps/portal/src/lib/hooks/useCurrency.ts | 4 +- .../portal/src/lib/hooks/useFormatCurrency.ts | 4 +- .../src/lib/services/currency.service.ts | 10 +- docs/README.md | 11 +- docs/architecture/system-overview.md | 7 +- docs/development/domain/import-hygiene.md | 53 +++++++ docs/development/domain/structure.md | 10 +- eslint.config.mjs | 62 +++++++++ packages/domain/billing/constants.ts | 4 +- packages/domain/billing/index.ts | 21 +-- packages/domain/billing/schema.ts | 19 +++ packages/domain/common/index.ts | 7 - packages/domain/common/providers/index.ts | 8 +- packages/domain/customer/index.ts | 34 +---- packages/domain/customer/providers/index.ts | 12 ++ packages/domain/orders/contract.ts | 10 +- packages/domain/orders/index.ts | 10 +- packages/domain/orders/providers/index.ts | 1 + packages/domain/orders/providers/types.ts | 14 ++ packages/domain/package.json | 98 ++++--------- packages/domain/payments/index.ts | 14 -- packages/domain/services/index.ts | 24 +--- packages/domain/sim/index.ts | 3 - packages/domain/subscriptions/index.ts | 13 -- packages/domain/support/index.ts | 7 - packages/domain/toolkit/README.md | 46 +++---- packages/domain/toolkit/index.ts | 17 --- scripts/domain/check-dist.sh | 6 + scripts/domain/check-exports.mjs | 26 ++++ scripts/domain/check-import-contract.sh | 64 +++++++++ scripts/domain/codemod-domain-imports.mjs | 129 ++++++++++++++++++ scripts/migrate-imports.sh | 2 +- 70 files changed, 597 insertions(+), 389 deletions(-) create mode 100644 docs/development/domain/import-hygiene.md create mode 100644 packages/domain/orders/providers/types.ts create mode 100644 scripts/domain/check-exports.mjs create mode 100644 scripts/domain/check-import-contract.sh create mode 100644 scripts/domain/codemod-domain-imports.mjs diff --git a/.cursor/rules/portal-rule.mdc b/.cursor/rules/portal-rule.mdc index 4c23b256..6286754c 100644 --- a/.cursor/rules/portal-rule.mdc +++ b/.cursor/rules/portal-rule.mdc @@ -6,4 +6,5 @@ alwaysApply: true 1. Have types and validation in the shared domain layer. 2. Keep business logic out of the frontend; use services and APIs instead. 3. Reuse existing types and functions; extend them when additional behavior is needed. -4. Follow the established folder structures documented in docs/STRUCTURE.md. \ No newline at end of file +4. Read and understand the structures and workflows documented in docs. Start from docs/README.md +5. Follow structures and our codebase rules inside docs/development directory \ No newline at end of file diff --git a/apps/bff/src/infra/mappers/user.mapper.ts b/apps/bff/src/infra/mappers/user.mapper.ts index f5c7a4ce..80047bcc 100644 --- a/apps/bff/src/infra/mappers/user.mapper.ts +++ b/apps/bff/src/infra/mappers/user.mapper.ts @@ -9,7 +9,7 @@ import type { User as PrismaUser } from "@prisma/client"; import type { UserAuth } from "@customer-portal/domain/customer"; -import { Providers as CustomerProviders } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; type PrismaUserRaw = Parameters[0]; diff --git a/apps/bff/src/integrations/salesforce/salesforce.service.ts b/apps/bff/src/integrations/salesforce/salesforce.service.ts index 00531ab4..ec0d6c30 100644 --- a/apps/bff/src/integrations/salesforce/salesforce.service.ts +++ b/apps/bff/src/integrations/salesforce/salesforce.service.ts @@ -9,7 +9,7 @@ import { type SalesforceAccountPortalUpdate, } from "./services/salesforce-account.service.js"; import { SalesforceOperationException } from "@bff/core/exceptions/domain-exceptions.js"; -import type { SalesforceOrderRecord } from "@customer-portal/domain/orders"; +import type { SalesforceOrderRecord } from "@customer-portal/domain/orders/providers"; /** * Salesforce Service - Facade for Salesforce operations diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts index 1ef17bfe..d9d21718 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-account.service.ts @@ -3,8 +3,8 @@ import { Logger } from "nestjs-pino"; import { ConfigService } from "@nestjs/config"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { SalesforceConnection } from "./salesforce-connection.service.js"; -import type { SalesforceAccountRecord } from "@customer-portal/domain/customer"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceAccountRecord } from "@customer-portal/domain/customer/providers"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { customerNumberSchema, salesforceIdSchema } from "@customer-portal/domain/common"; /** diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-case.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-case.service.ts index a4d01721..fca05890 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-case.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-case.service.ts @@ -14,16 +14,15 @@ import { Logger } from "nestjs-pino"; import { SalesforceConnection } from "./salesforce-connection.service.js"; import { assertSalesforceId } from "../utils/soql.util.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; +import type { SupportCase, CreateCaseRequest } from "@customer-portal/domain/support"; +import type { SalesforceCaseRecord } from "@customer-portal/domain/support/providers"; import { - type SupportCase, - type SalesforceCaseRecord, - type CreateCaseRequest, SALESFORCE_CASE_ORIGIN, SALESFORCE_CASE_STATUS, SALESFORCE_CASE_PRIORITY, - Providers, -} from "@customer-portal/domain/support"; +} from "@customer-portal/domain/support/providers"; +import * as Providers from "@customer-portal/domain/support/providers"; // Access the mapper directly to avoid unbound method issues const salesforceMapper = Providers.Salesforce; diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-opportunity.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-opportunity.service.ts index 20255bd0..90aad61c 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-opportunity.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-opportunity.service.ts @@ -21,7 +21,7 @@ import { Logger } from "nestjs-pino"; import { SalesforceConnection } from "./salesforce-connection.service.js"; import { assertSalesforceId } from "../utils/soql.util.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { type OpportunityStageValue, type OpportunityProductTypeValue, diff --git a/apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts b/apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts index e3935f96..7aa15d93 100644 --- a/apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts +++ b/apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts @@ -16,14 +16,13 @@ import { Logger } from "nestjs-pino"; import { SalesforceConnection } from "./salesforce-connection.service.js"; import { assertSalesforceId, buildInClause } from "../utils/soql.util.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; -import { - Providers as OrderProviders, - type OrderDetails, - type OrderSummary, - type SalesforceOrderRecord, - type SalesforceOrderItemRecord, -} from "@customer-portal/domain/orders"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { OrderDetails, OrderSummary } from "@customer-portal/domain/orders"; +import type { + SalesforceOrderItemRecord, + SalesforceOrderRecord, +} from "@customer-portal/domain/orders/providers"; +import * as OrderProviders from "@customer-portal/domain/orders/providers"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { OrderFieldMapService } from "@bff/modules/orders/config/order-field-map.service.js"; /** diff --git a/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts b/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts index c662ecad..c8bf171a 100644 --- a/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts +++ b/apps/bff/src/integrations/whmcs/connection/services/whmcs-connection-orchestrator.service.ts @@ -14,7 +14,7 @@ import type { WhmcsAddClientResponse, WhmcsValidateLoginResponse, WhmcsSsoResponse, -} from "@customer-portal/domain/customer"; +} from "@customer-portal/domain/customer/providers"; import type { WhmcsGetInvoicesParams, WhmcsCreateInvoiceParams, @@ -25,16 +25,18 @@ import type { WhmcsCreateInvoiceResponse, WhmcsUpdateInvoiceResponse, WhmcsCapturePaymentResponse, -} from "@customer-portal/domain/billing"; -import type { WhmcsGetPayMethodsParams } from "@customer-portal/domain/payments"; +} from "@customer-portal/domain/billing/providers"; +import type { WhmcsGetPayMethodsParams } from "@customer-portal/domain/payments/providers"; import type { WhmcsPaymentMethodListResponse, WhmcsPaymentGatewayListResponse, -} from "@customer-portal/domain/payments"; -import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions"; -import type { WhmcsProductListResponse } from "@customer-portal/domain/subscriptions"; -import type { WhmcsCatalogProductListResponse } from "@customer-portal/domain/services"; -import type { WhmcsErrorResponse } from "@customer-portal/domain/common"; +} from "@customer-portal/domain/payments/providers"; +import type { + WhmcsGetClientsProductsParams, + WhmcsProductListResponse, +} from "@customer-portal/domain/subscriptions/providers"; +import type { WhmcsCatalogProductListResponse } from "@customer-portal/domain/services/providers"; +import type { WhmcsErrorResponse } from "@customer-portal/domain/common/providers"; import type { WhmcsRequestOptions, WhmcsConnectionStats } from "../types/connection.types.js"; /** diff --git a/apps/bff/src/integrations/whmcs/connection/services/whmcs-error-handler.service.ts b/apps/bff/src/integrations/whmcs/connection/services/whmcs-error-handler.service.ts index a4d8a19f..1f2ac048 100644 --- a/apps/bff/src/integrations/whmcs/connection/services/whmcs-error-handler.service.ts +++ b/apps/bff/src/integrations/whmcs/connection/services/whmcs-error-handler.service.ts @@ -1,9 +1,6 @@ import { Injectable, HttpStatus } from "@nestjs/common"; -import { - ErrorCode, - type ErrorCodeType, - type WhmcsErrorResponse, -} from "@customer-portal/domain/common"; +import { ErrorCode, type ErrorCodeType } from "@customer-portal/domain/common"; +import type { WhmcsErrorResponse } from "@customer-portal/domain/common/providers"; import { DomainHttpException } from "@bff/core/http/domain-http.exception.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; diff --git a/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts b/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts index b8673fc5..09ad0638 100644 --- a/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts +++ b/apps/bff/src/integrations/whmcs/connection/services/whmcs-http-client.service.ts @@ -1,7 +1,7 @@ import { Injectable, Inject } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; -import type { WhmcsResponse } from "@customer-portal/domain/common"; +import type { WhmcsResponse } from "@customer-portal/domain/common/providers"; import type { WhmcsApiConfig, WhmcsRequestOptions, diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-account-discovery.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-account-discovery.service.ts index 94355b62..d56b6330 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-account-discovery.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-account-discovery.service.ts @@ -3,7 +3,7 @@ import { Logger } from "nestjs-pino"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; import { WhmcsCacheService } from "../cache/whmcs-cache.service.js"; -import { Providers as CustomerProviders } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import type { WhmcsClient } from "@customer-portal/domain/customer"; /** diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-client.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-client.service.ts index a36d7c7f..7ddd77d6 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-client.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-client.service.ts @@ -9,8 +9,8 @@ import type { WhmcsClientResponse, WhmcsAddClientResponse, WhmcsValidateLoginResponse, -} from "@customer-portal/domain/customer"; -import { Providers as CustomerProviders } from "@customer-portal/domain/customer"; +} from "@customer-portal/domain/customer/providers"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import type { WhmcsClient } from "@customer-portal/domain/customer"; @Injectable() diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-currency.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-currency.service.ts index 738e09d3..97b47a14 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-currency.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-currency.service.ts @@ -4,16 +4,14 @@ import { Logger } from "nestjs-pino"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { WhmcsOperationException } from "@bff/core/exceptions/domain-exceptions.js"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; -import { - FALLBACK_CURRENCY, - type WhmcsCurrenciesResponse, - type WhmcsCurrency, -} from "@customer-portal/domain/billing"; +import { FALLBACK_CURRENCY } from "@customer-portal/domain/billing"; +import type { Currency } from "@customer-portal/domain/billing"; +import type { WhmcsCurrenciesResponse } from "@customer-portal/domain/billing/providers"; @Injectable() export class WhmcsCurrencyService implements OnModuleInit, OnModuleDestroy { - private defaultCurrency: WhmcsCurrency | null = null; - private currencies: WhmcsCurrency[] = []; + private defaultCurrency: Currency | null = null; + private currencies: Currency[] = []; private readonly REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours private refreshTimer: NodeJS.Timeout | null = null; @@ -70,21 +68,21 @@ export class WhmcsCurrencyService implements OnModuleInit, OnModuleDestroy { /** * Get the default currency (first currency from WHMCS or JPY fallback) */ - getDefaultCurrency(): WhmcsCurrency { + getDefaultCurrency(): Currency { return this.defaultCurrency ? { ...this.defaultCurrency } : { ...FALLBACK_CURRENCY }; } /** * Get all available currencies */ - getAllCurrencies(): WhmcsCurrency[] { + getAllCurrencies(): Currency[] { return this.currencies; } /** * Find currency by code */ - getCurrencyByCode(code: string): WhmcsCurrency | null { + getCurrencyByCode(code: string): Currency | null { return this.currencies.find(c => c.code.toUpperCase() === code.toUpperCase()) || null; } @@ -140,8 +138,8 @@ export class WhmcsCurrencyService implements OnModuleInit, OnModuleDestroy { * Parse WHMCS response format into currency objects * Handles both flat format (currencies[currency][0][id]) and nested format (currencies.currency[]) */ - private parseWhmcsCurrenciesResponse(response: WhmcsCurrenciesResponse): WhmcsCurrency[] { - const currencies: WhmcsCurrency[] = []; + private parseWhmcsCurrenciesResponse(response: WhmcsCurrenciesResponse): Currency[] { + const currencies: Currency[] = []; // Check if response has nested currency structure if ( @@ -154,7 +152,7 @@ export class WhmcsCurrencyService implements OnModuleInit, OnModuleDestroy { : [response.currencies.currency]; for (const currencyData of currencyArray) { - const currency: WhmcsCurrency = { + const currency: Currency = { id: parseInt(String(currencyData.id)) || 0, code: String(currencyData.code || ""), prefix: String(currencyData.prefix || ""), @@ -184,7 +182,7 @@ export class WhmcsCurrencyService implements OnModuleInit, OnModuleDestroy { // Build currency objects from the flat response for (const index of currencyIndices) { - const currency: WhmcsCurrency = { + const currency: Currency = { id: parseInt(String(response[`currencies[currency][${index}][id]`])) || 0, code: String(response[`currencies[currency][${index}][code]`] || ""), prefix: String(response[`currencies[currency][${index}][prefix]`] || ""), diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-invoice.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-invoice.service.ts index ac84a4ba..ba4cfafc 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-invoice.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-invoice.service.ts @@ -2,8 +2,9 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { Logger } from "nestjs-pino"; import { Injectable, NotFoundException, Inject } from "@nestjs/common"; import { WhmcsOperationException } from "@bff/core/exceptions/domain-exceptions.js"; -import { invoiceListSchema, invoiceSchema, Providers } from "@customer-portal/domain/billing"; +import { invoiceListSchema, invoiceSchema } from "@customer-portal/domain/billing"; import type { Invoice, InvoiceList } from "@customer-portal/domain/billing"; +import * as Providers from "@customer-portal/domain/billing/providers"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; import { WhmcsCurrencyService } from "./whmcs-currency.service.js"; import { WhmcsCacheService } from "../cache/whmcs-cache.service.js"; @@ -12,14 +13,14 @@ import type { WhmcsCreateInvoiceParams, WhmcsUpdateInvoiceParams, WhmcsCapturePaymentParams, -} from "@customer-portal/domain/billing"; +} from "@customer-portal/domain/billing/providers"; import type { WhmcsInvoiceListResponse, WhmcsInvoiceResponse, WhmcsCreateInvoiceResponse, WhmcsUpdateInvoiceResponse, WhmcsCapturePaymentResponse, -} from "@customer-portal/domain/billing"; +} from "@customer-portal/domain/billing/providers"; export type InvoiceFilters = Partial<{ status: "Paid" | "Unpaid" | "Cancelled" | "Overdue" | "Collections"; diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts index e822c6f2..69e8f03b 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-order.service.ts @@ -9,12 +9,12 @@ import type { WhmcsAddOrderParams, WhmcsAddOrderResponse, WhmcsOrderResult, -} from "@customer-portal/domain/orders"; +} from "@customer-portal/domain/orders/providers"; +import * as Providers from "@customer-portal/domain/orders/providers"; import { - Providers, whmcsAddOrderResponseSchema, whmcsAcceptOrderResponseSchema, -} from "@customer-portal/domain/orders"; +} from "@customer-portal/domain/orders/providers"; export type { WhmcsOrderItem, WhmcsAddOrderParams, WhmcsOrderResult }; diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-payment.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-payment.service.ts index 13409a87..3c4dba12 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-payment.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-payment.service.ts @@ -1,27 +1,25 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { Logger } from "nestjs-pino"; import { Injectable, Inject } from "@nestjs/common"; -import { Providers } from "@customer-portal/domain/payments"; +import * as Providers from "@customer-portal/domain/payments/providers"; import type { PaymentMethodList, PaymentGateway, PaymentGatewayList, PaymentMethod, } from "@customer-portal/domain/payments"; -import { - Providers as CatalogProviders, - type WhmcsCatalogProductNormalized, -} from "@customer-portal/domain/services"; +import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/services/providers"; +import * as CatalogProviders from "@customer-portal/domain/services/providers"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; import { WhmcsCacheService } from "../cache/whmcs-cache.service.js"; -import type { WhmcsCreateSsoTokenParams } from "@customer-portal/domain/customer"; -import type { WhmcsGetPayMethodsParams } from "@customer-portal/domain/payments"; +import type { WhmcsCreateSsoTokenParams } from "@customer-portal/domain/customer/providers"; +import type { WhmcsGetPayMethodsParams } from "@customer-portal/domain/payments/providers"; import type { WhmcsPaymentMethod, WhmcsPaymentMethodListResponse, WhmcsPaymentGateway, WhmcsPaymentGatewayListResponse, -} from "@customer-portal/domain/payments"; +} from "@customer-portal/domain/payments/providers"; @Injectable() export class WhmcsPaymentService { diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-sso.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-sso.service.ts index 6a14a7f9..4e3e921d 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-sso.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-sso.service.ts @@ -2,8 +2,10 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { Logger } from "nestjs-pino"; import { Injectable, Inject } from "@nestjs/common"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; -import type { WhmcsCreateSsoTokenParams } from "@customer-portal/domain/customer"; -import type { WhmcsSsoResponse } from "@customer-portal/domain/customer"; +import type { + WhmcsCreateSsoTokenParams, + WhmcsSsoResponse, +} from "@customer-portal/domain/customer/providers"; @Injectable() export class WhmcsSsoService { diff --git a/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts b/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts index 7ad3e55f..652169a4 100644 --- a/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts +++ b/apps/bff/src/integrations/whmcs/services/whmcs-subscription.service.ts @@ -2,12 +2,12 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { Logger } from "nestjs-pino"; import { Injectable, NotFoundException, Inject } from "@nestjs/common"; import { WhmcsOperationException } from "@bff/core/exceptions/domain-exceptions.js"; -import { Providers } from "@customer-portal/domain/subscriptions"; +import * as Providers from "@customer-portal/domain/subscriptions/providers"; import type { Subscription, SubscriptionList } from "@customer-portal/domain/subscriptions"; import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js"; import { WhmcsCurrencyService } from "./whmcs-currency.service.js"; import { WhmcsCacheService } from "../cache/whmcs-cache.service.js"; -import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions"; +import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions/providers"; export interface SubscriptionFilters { status?: string; diff --git a/apps/bff/src/integrations/whmcs/whmcs.service.ts b/apps/bff/src/integrations/whmcs/whmcs.service.ts index 4f239f93..2ffe0028 100644 --- a/apps/bff/src/integrations/whmcs/whmcs.service.ts +++ b/apps/bff/src/integrations/whmcs/whmcs.service.ts @@ -2,12 +2,8 @@ import { Injectable, Inject } from "@nestjs/common"; import type { Invoice, InvoiceList } from "@customer-portal/domain/billing"; import type { Subscription, SubscriptionList } from "@customer-portal/domain/subscriptions"; import type { PaymentMethodList, PaymentGatewayList } from "@customer-portal/domain/payments"; -import { - Providers as CustomerProviders, - addressSchema, - type Address, - type WhmcsClient, -} from "@customer-portal/domain/customer"; +import { addressSchema, type Address, type WhmcsClient } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import { WhmcsConnectionOrchestratorService } from "./connection/services/whmcs-connection-orchestrator.service.js"; import { WhmcsInvoiceService } from "./services/whmcs-invoice.service.js"; import type { InvoiceFilters } from "./services/whmcs-invoice.service.js"; @@ -17,10 +13,15 @@ import { WhmcsClientService } from "./services/whmcs-client.service.js"; import { WhmcsPaymentService } from "./services/whmcs-payment.service.js"; import { WhmcsSsoService } from "./services/whmcs-sso.service.js"; import { WhmcsOrderService } from "./services/whmcs-order.service.js"; -import type { WhmcsAddClientParams, WhmcsClientResponse } from "@customer-portal/domain/customer"; -import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions"; -import type { WhmcsProductListResponse } from "@customer-portal/domain/subscriptions"; -import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/services"; +import type { + WhmcsAddClientParams, + WhmcsClientResponse, +} from "@customer-portal/domain/customer/providers"; +import type { + WhmcsGetClientsProductsParams, + WhmcsProductListResponse, +} from "@customer-portal/domain/subscriptions/providers"; +import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/services/providers"; import { Logger } from "nestjs-pino"; @Injectable() diff --git a/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts index e15240fc..c758874a 100644 --- a/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts @@ -27,7 +27,7 @@ import { type ValidateSignupRequest, } from "@customer-portal/domain/auth"; import { ErrorCode } from "@customer-portal/domain/common"; -import { Providers as CustomerProviders } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js"; import type { User as PrismaUser } from "@prisma/client"; import { CacheService } from "@bff/infra/cache/cache.service.js"; diff --git a/apps/bff/src/modules/auth/infra/workflows/workflows/whmcs-link-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/workflows/whmcs-link-workflow.service.ts index 024f1ef6..ddf185b8 100644 --- a/apps/bff/src/modules/auth/infra/workflows/workflows/whmcs-link-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/workflows/whmcs-link-workflow.service.ts @@ -14,7 +14,7 @@ import { WhmcsAccountDiscoveryService } from "@bff/integrations/whmcs/services/w import { SalesforceService } from "@bff/integrations/salesforce/salesforce.service.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { mapPrismaUserToDomain } from "@bff/infra/mappers/index.js"; -import { Providers as CustomerProviders } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import type { User } from "@customer-portal/domain/customer"; import { PORTAL_SOURCE_MIGRATED, diff --git a/apps/bff/src/modules/currency/currency.controller.ts b/apps/bff/src/modules/currency/currency.controller.ts index 0fdf53a3..2181ea2c 100644 --- a/apps/bff/src/modules/currency/currency.controller.ts +++ b/apps/bff/src/modules/currency/currency.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get } from "@nestjs/common"; import { Public } from "@bff/modules/auth/decorators/public.decorator.js"; import { WhmcsCurrencyService } from "../../integrations/whmcs/services/whmcs-currency.service.js"; -import type { WhmcsCurrency } from "@customer-portal/domain/billing"; +import type { Currency } from "@customer-portal/domain/billing"; @Controller("currency") export class CurrencyController { @@ -9,13 +9,13 @@ export class CurrencyController { @Public() @Get("default") - getDefaultCurrency(): WhmcsCurrency { + getDefaultCurrency(): Currency { return this.currencyService.getDefaultCurrency(); } @Public() @Get("all") - getAllCurrencies(): WhmcsCurrency[] { + getAllCurrencies(): Currency[] { return this.currencyService.getAllCurrencies(); } } diff --git a/apps/bff/src/modules/orders/config/order-field-map.service.ts b/apps/bff/src/modules/orders/config/order-field-map.service.ts index 53d9bf29..df1c8406 100644 --- a/apps/bff/src/modules/orders/config/order-field-map.service.ts +++ b/apps/bff/src/modules/orders/config/order-field-map.service.ts @@ -5,7 +5,7 @@ import { defaultSalesforceOrderFieldMap, type PartialSalesforceOrderFieldMap, type SalesforceOrderFieldMap, -} from "@customer-portal/domain/orders"; +} from "@customer-portal/domain/orders/providers"; const unique = (values: T[]): T[] => Array.from(new Set(values)); diff --git a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts index eb6c187f..3b0962f8 100644 --- a/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts +++ b/apps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts @@ -14,11 +14,9 @@ import { OrderEventsService } from "./order-events.service.js"; import { OrdersCacheService } from "./orders-cache.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { NotificationService } from "@bff/modules/notifications/notifications.service.js"; -import { - type OrderDetails, - type OrderFulfillmentValidationResult, - Providers as OrderProviders, -} from "@customer-portal/domain/orders"; +import type { OrderDetails } from "@customer-portal/domain/orders"; +import type { OrderFulfillmentValidationResult } from "@customer-portal/domain/orders/providers"; +import * as OrderProviders from "@customer-portal/domain/orders/providers"; import { OPPORTUNITY_STAGE } from "@customer-portal/domain/opportunity"; import { NOTIFICATION_SOURCE, NOTIFICATION_TYPE } from "@customer-portal/domain/notifications"; import { salesforceAccountIdSchema } from "@customer-portal/domain/common"; diff --git a/apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts b/apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts index 8307e2df..af3861e6 100644 --- a/apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts +++ b/apps/bff/src/modules/orders/services/order-fulfillment-validator.service.ts @@ -4,9 +4,9 @@ import { SalesforceService } from "@bff/integrations/salesforce/salesforce.servi import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { sfOrderIdParamSchema } from "@customer-portal/domain/orders"; -import type { OrderFulfillmentValidationResult } from "@customer-portal/domain/orders"; +import type { OrderFulfillmentValidationResult } from "@customer-portal/domain/orders/providers"; import { salesforceAccountIdSchema } from "@customer-portal/domain/common"; -import type { SalesforceOrderRecord } from "@customer-portal/domain/orders/providers/salesforce/raw.types"; +import type { SalesforceOrderRecord } from "@customer-portal/domain/orders/providers"; import { PaymentValidatorService } from "./payment-validator.service.js"; /** diff --git a/apps/bff/src/modules/orders/services/order-pricebook.service.ts b/apps/bff/src/modules/orders/services/order-pricebook.service.ts index ec650e3b..329740e2 100644 --- a/apps/bff/src/modules/orders/services/order-pricebook.service.ts +++ b/apps/bff/src/modules/orders/services/order-pricebook.service.ts @@ -5,8 +5,8 @@ import { SalesforceConnection } from "@bff/integrations/salesforce/services/sale import type { SalesforceProduct2Record, SalesforcePricebookEntryRecord, -} from "@customer-portal/domain/services"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +} from "@customer-portal/domain/services/providers"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { assertSalesforceId, buildInClause, diff --git a/apps/bff/src/modules/orders/services/order-validator.service.ts b/apps/bff/src/modules/orders/services/order-validator.service.ts index 80300dbc..50d15be4 100644 --- a/apps/bff/src/modules/orders/services/order-validator.service.ts +++ b/apps/bff/src/modules/orders/services/order-validator.service.ts @@ -9,7 +9,7 @@ import { type CreateOrderRequest, type OrderBusinessValidation, } from "@customer-portal/domain/orders"; -import type { Providers } from "@customer-portal/domain/subscriptions"; +import type * as Providers from "@customer-portal/domain/subscriptions/providers"; type WhmcsProduct = Providers.WhmcsRaw.WhmcsProductRaw; import { SimServicesService } from "@bff/modules/services/services/sim-services.service.js"; diff --git a/apps/bff/src/modules/services/services/base-services.service.ts b/apps/bff/src/modules/services/services/base-services.service.ts index 28577993..45908ed2 100644 --- a/apps/bff/src/modules/services/services/base-services.service.ts +++ b/apps/bff/src/modules/services/services/base-services.service.ts @@ -12,11 +12,11 @@ import { } from "@bff/integrations/salesforce/utils/services-query-builder.js"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import type { - SalesforceProduct2WithPricebookEntries, SalesforcePricebookEntryRecord, -} from "@customer-portal/domain/services"; -import { Providers as CatalogProviders } from "@customer-portal/domain/services"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; + SalesforceProduct2WithPricebookEntries, +} from "@customer-portal/domain/services/providers"; +import * as CatalogProviders from "@customer-portal/domain/services/providers"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; @Injectable() export class BaseServicesService { diff --git a/apps/bff/src/modules/services/services/internet-services.service.ts b/apps/bff/src/modules/services/services/internet-services.service.ts index d68c7b16..bc80f0f8 100644 --- a/apps/bff/src/modules/services/services/internet-services.service.ts +++ b/apps/bff/src/modules/services/services/internet-services.service.ts @@ -3,20 +3,20 @@ import { ConfigService } from "@nestjs/config"; import { BaseServicesService } from "./base-services.service.js"; import { ServicesCacheService } from "./services-cache.service.js"; import type { - SalesforceProduct2WithPricebookEntries, InternetPlanCatalogItem, InternetInstallationCatalogItem, InternetAddonCatalogItem, InternetEligibilityDetails, InternetEligibilityStatus, } from "@customer-portal/domain/services"; +import type { SalesforceProduct2WithPricebookEntries } from "@customer-portal/domain/services/providers"; import { - Providers as CatalogProviders, enrichInternetPlanMetadata, inferAddonTypeFromSku, inferInstallationTermFromSku, internetEligibilityDetailsSchema, } from "@customer-portal/domain/services"; +import * as CatalogProviders from "@customer-portal/domain/services/providers"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js"; import { OpportunityResolutionService } from "@bff/integrations/salesforce/services/opportunity-resolution.service.js"; @@ -27,7 +27,7 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { assertSalesforceId } from "@bff/integrations/salesforce/utils/soql.util.js"; import { assertSoqlFieldName } from "@bff/integrations/salesforce/utils/soql.util.js"; import type { InternetEligibilityCheckRequest } from "./internet-eligibility.types.js"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; // (removed unused opportunity constants import) @Injectable() diff --git a/apps/bff/src/modules/services/services/sim-services.service.ts b/apps/bff/src/modules/services/services/sim-services.service.ts index a1f150aa..9cdc57b1 100644 --- a/apps/bff/src/modules/services/services/sim-services.service.ts +++ b/apps/bff/src/modules/services/services/sim-services.service.ts @@ -3,11 +3,11 @@ import { ConfigService } from "@nestjs/config"; import { BaseServicesService } from "./base-services.service.js"; import { ServicesCacheService } from "./services-cache.service.js"; import type { - SalesforceProduct2WithPricebookEntries, SimCatalogProduct, SimActivationFeeCatalogItem, } from "@customer-portal/domain/services"; -import { Providers as CatalogProviders } from "@customer-portal/domain/services"; +import type { SalesforceProduct2WithPricebookEntries } from "@customer-portal/domain/services/providers"; +import * as CatalogProviders from "@customer-portal/domain/services/providers"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js"; import { Logger } from "nestjs-pino"; diff --git a/apps/bff/src/modules/services/services/vpn-services.service.ts b/apps/bff/src/modules/services/services/vpn-services.service.ts index 3ccda343..531db7c5 100644 --- a/apps/bff/src/modules/services/services/vpn-services.service.ts +++ b/apps/bff/src/modules/services/services/vpn-services.service.ts @@ -4,11 +4,9 @@ import { Logger } from "nestjs-pino"; import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js"; import { BaseServicesService } from "./base-services.service.js"; import { ServicesCacheService } from "./services-cache.service.js"; -import type { - SalesforceProduct2WithPricebookEntries, - VpnCatalogProduct, -} from "@customer-portal/domain/services"; -import { Providers as CatalogProviders } from "@customer-portal/domain/services"; +import type { VpnCatalogProduct } from "@customer-portal/domain/services"; +import type { SalesforceProduct2WithPricebookEntries } from "@customer-portal/domain/services/providers"; +import * as CatalogProviders from "@customer-portal/domain/services/providers"; @Injectable() export class VpnServicesService extends BaseServicesService { diff --git a/apps/bff/src/modules/subscriptions/sim-management/services/sim-topup-pricing.service.ts b/apps/bff/src/modules/subscriptions/sim-management/services/sim-topup-pricing.service.ts index 0fbd9730..9d46aa35 100644 --- a/apps/bff/src/modules/subscriptions/sim-management/services/sim-topup-pricing.service.ts +++ b/apps/bff/src/modules/subscriptions/sim-management/services/sim-topup-pricing.service.ts @@ -3,7 +3,7 @@ import { ConfigService } from "@nestjs/config"; import { Logger } from "nestjs-pino"; import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js"; import type { SimTopUpPricing, SimTopUpPricingPreviewResponse } from "@customer-portal/domain/sim"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { sanitizeSoqlLiteral } from "@bff/integrations/salesforce/utils/soql.util.js"; @Injectable() diff --git a/apps/bff/src/modules/subscriptions/subscriptions.controller.ts b/apps/bff/src/modules/subscriptions/subscriptions.controller.ts index 24fee272..429a5d98 100644 --- a/apps/bff/src/modules/subscriptions/subscriptions.controller.ts +++ b/apps/bff/src/modules/subscriptions/subscriptions.controller.ts @@ -33,7 +33,7 @@ import type { SimPlanChangeResult, } from "@customer-portal/domain/subscriptions"; import type { InvoiceList } from "@customer-portal/domain/billing"; -import { createPaginationSchema } from "@customer-portal/domain/toolkit/validation/helpers"; +import { Validation } from "@customer-portal/domain/toolkit"; import { simTopupRequestSchema, simChangePlanRequestSchema, @@ -82,7 +82,7 @@ import { } from "@customer-portal/domain/subscriptions"; import { invoiceListSchema } from "@customer-portal/domain/billing"; -const subscriptionInvoiceQuerySchema = createPaginationSchema({ +const subscriptionInvoiceQuerySchema = Validation.createPaginationSchema({ defaultLimit: 10, maxLimit: 100, minLimit: 1, diff --git a/apps/bff/src/modules/subscriptions/subscriptions.service.ts b/apps/bff/src/modules/subscriptions/subscriptions.service.ts index 557a88a8..09cef0a8 100644 --- a/apps/bff/src/modules/subscriptions/subscriptions.service.ts +++ b/apps/bff/src/modules/subscriptions/subscriptions.service.ts @@ -15,7 +15,7 @@ import { WhmcsService } from "@bff/integrations/whmcs/whmcs.service.js"; import { WhmcsCacheService } from "@bff/integrations/whmcs/cache/whmcs-cache.service.js"; import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js"; import { Logger } from "nestjs-pino"; -import type { Providers } from "@customer-portal/domain/subscriptions"; +import type * as Providers from "@customer-portal/domain/subscriptions/providers"; type WhmcsProduct = Providers.WhmcsRaw.WhmcsProductRaw; diff --git a/apps/bff/src/modules/users/infra/user-profile.service.ts b/apps/bff/src/modules/users/infra/user-profile.service.ts index da214de0..8562b350 100644 --- a/apps/bff/src/modules/users/infra/user-profile.service.ts +++ b/apps/bff/src/modules/users/infra/user-profile.service.ts @@ -10,12 +10,12 @@ import { Logger } from "nestjs-pino"; import type { User as PrismaUser } from "@prisma/client"; import { getErrorMessage } from "@bff/core/utils/error.util.js"; import { - Providers as CustomerProviders, addressSchema, combineToUser, type Address, type User, } from "@customer-portal/domain/customer"; +import * as CustomerProviders from "@customer-portal/domain/customer/providers"; import { updateCustomerProfileRequestSchema, type UpdateCustomerProfileRequest, diff --git a/apps/bff/src/modules/verification/residence-card.service.ts b/apps/bff/src/modules/verification/residence-card.service.ts index 11b79001..6b3fad04 100644 --- a/apps/bff/src/modules/verification/residence-card.service.ts +++ b/apps/bff/src/modules/verification/residence-card.service.ts @@ -7,7 +7,7 @@ import { assertSalesforceId, assertSoqlFieldName, } from "@bff/integrations/salesforce/utils/soql.util.js"; -import type { SalesforceResponse } from "@customer-portal/domain/common"; +import type { SalesforceResponse } from "@customer-portal/domain/common/providers"; import { residenceCardVerificationSchema, type ResidenceCardVerification, diff --git a/apps/portal/src/components/atoms/README.md b/apps/portal/src/components/atoms/README.md index 99ad1357..1f025018 100644 --- a/apps/portal/src/components/atoms/README.md +++ b/apps/portal/src/components/atoms/README.md @@ -206,17 +206,17 @@ The application uses Zod for type-safe form validation. Use the `useZodForm` hoo ```tsx import { useZodForm } from "@/core/forms"; -import { loginFormSchema, type LoginFormData } from "@customer-portal/domain"; +import { loginRequestSchema, type LoginRequest } from "@customer-portal/domain/auth"; function MyForm() { const { values, errors, touched, isSubmitting, setValue, setTouchedField, handleSubmit } = useZodForm({ - schema: loginFormSchema, + schema: loginRequestSchema, initialValues: { email: "", password: "", }, - onSubmit: async data => { + onSubmit: async (data: LoginRequest) => { // Handle form submission await submitLogin(data); }, @@ -241,7 +241,7 @@ function MyForm() { **Available Zod schemas in `@customer-portal/domain`:** -- `loginFormSchema` - Login form validation +- `loginRequestSchema` - Login request validation (shared domain schema) - `signupFormSchema` - User registration - `profileEditFormSchema` - Profile updates - `addressFormSchema` - Address validation diff --git a/apps/portal/src/features/dashboard/utils/dashboard.utils.ts b/apps/portal/src/features/dashboard/utils/dashboard.utils.ts index 24ea6b7d..1683b1b0 100644 --- a/apps/portal/src/features/dashboard/utils/dashboard.utils.ts +++ b/apps/portal/src/features/dashboard/utils/dashboard.utils.ts @@ -15,7 +15,7 @@ import { type QuickActionTask, type DashboardTaskSummary, } from "@customer-portal/domain/dashboard"; -import { formatCurrency as formatCurrencyUtil } from "@customer-portal/domain/toolkit"; +import { Formatting } from "@customer-portal/domain/toolkit"; // Re-export domain business logic for backward compatibility export { @@ -27,6 +27,8 @@ export { type DashboardTaskSummary, }; +const formatCurrencyUtil = Formatting.formatCurrency; + /** * Get navigation path for an activity */ diff --git a/apps/portal/src/lib/hooks/useCurrency.ts b/apps/portal/src/lib/hooks/useCurrency.ts index 3ab0f5c4..00f0259f 100644 --- a/apps/portal/src/lib/hooks/useCurrency.ts +++ b/apps/portal/src/lib/hooks/useCurrency.ts @@ -3,10 +3,10 @@ import { useQuery } from "@tanstack/react-query"; import { queryKeys } from "@/lib/api"; import { currencyService } from "@/lib/services/currency.service"; -import { FALLBACK_CURRENCY, type WhmcsCurrency } from "@customer-portal/domain/billing"; +import { FALLBACK_CURRENCY, type Currency } from "@customer-portal/domain/billing"; export function useCurrency() { - const { data, isLoading, isError, error } = useQuery({ + const { data, isLoading, isError, error } = useQuery({ queryKey: queryKeys.currency.default(), queryFn: () => currencyService.getDefaultCurrency(), retry: 2, diff --git a/apps/portal/src/lib/hooks/useFormatCurrency.ts b/apps/portal/src/lib/hooks/useFormatCurrency.ts index 340e9f4f..359d30b1 100644 --- a/apps/portal/src/lib/hooks/useFormatCurrency.ts +++ b/apps/portal/src/lib/hooks/useFormatCurrency.ts @@ -2,7 +2,7 @@ import { useCurrency } from "@/lib/hooks/useCurrency"; import { FALLBACK_CURRENCY } from "@customer-portal/domain/billing"; -import { formatCurrency as baseFormatCurrency } from "@customer-portal/domain/toolkit"; +import { Formatting } from "@customer-portal/domain/toolkit"; export type FormatCurrencyOptions = { currency?: string; @@ -44,7 +44,7 @@ export function useFormatCurrency() { ? currencyOrOptions.showSymbol : undefined) ?? options?.showSymbol; - return baseFormatCurrency(amount, overrideCurrency ?? fallbackCurrency, { + return Formatting.formatCurrency(amount, overrideCurrency ?? fallbackCurrency, { currencySymbol: overrideSymbol ?? fallbackSymbol, locale, showSymbol, diff --git a/apps/portal/src/lib/services/currency.service.ts b/apps/portal/src/lib/services/currency.service.ts index aa47c5b7..6f7475ae 100644 --- a/apps/portal/src/lib/services/currency.service.ts +++ b/apps/portal/src/lib/services/currency.service.ts @@ -1,16 +1,16 @@ import { apiClient, getDataOrThrow } from "@/lib/api"; -import { FALLBACK_CURRENCY, type WhmcsCurrency } from "@customer-portal/domain/billing"; +import { FALLBACK_CURRENCY, type Currency } from "@customer-portal/domain/billing"; export { FALLBACK_CURRENCY }; export const currencyService = { - async getDefaultCurrency(): Promise { - const response = await apiClient.GET("/api/currency/default"); + async getDefaultCurrency(): Promise { + const response = await apiClient.GET("/api/currency/default"); return getDataOrThrow(response, "Failed to get default currency"); }, - async getAllCurrencies(): Promise { - const response = await apiClient.GET("/api/currency/all"); + async getAllCurrencies(): Promise { + const response = await apiClient.GET("/api/currency/all"); return getDataOrThrow(response, "Failed to get currencies"); }, }; diff --git a/docs/README.md b/docs/README.md index ef1af176..b3e841f7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -101,12 +101,11 @@ Feature guides explaining how the portal functions: ### BFF (Backend for Frontend) -| Document | Description | -| ----------------------------------------------------------------- | ---------------------------- | -| [Integration Patterns](./development/bff/integration-patterns.md) | Clean architecture patterns | -| [Validation Standard](./development/bff/validation.md) | DTO validation + global pipe | -| [DB Mappers](./development/bff/db-mappers.md) | Database mapping | -| [Order Status Updates](./development/bff/order-status-updates.md) | Status update strategy | +| Document | Description | +| ----------------------------------------------------------------- | --------------------------- | +| [Integration Patterns](./development/bff/integration-patterns.md) | Clean architecture patterns | +| [DB Mappers](./development/bff/db-mappers.md) | Database mapping | +| [Order Status Updates](./development/bff/order-status-updates.md) | Status update strategy | ### Portal (Frontend) diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md index 88d09f1d..69f68ae5 100644 --- a/docs/architecture/system-overview.md +++ b/docs/architecture/system-overview.md @@ -140,11 +140,12 @@ packages/domain/ #### **Usage** -Import via `@customer-portal/domain`: +Import via module entrypoints: ```typescript -import { Invoice, SIM_LIFECYCLE_STAGE, OrderStatus } from "@customer-portal/domain"; -import { invoiceSchema, orderSchema } from "@customer-portal/domain/validation"; +import { invoiceSchema, type Invoice } from "@customer-portal/domain/billing"; +import { orderSummarySchema, type OrderSummary } from "@customer-portal/domain/orders"; +import { SIM_STATUS } from "@customer-portal/domain/sim"; ``` #### **Integration with BFF** diff --git a/docs/development/domain/import-hygiene.md b/docs/development/domain/import-hygiene.md new file mode 100644 index 00000000..3aed39fc --- /dev/null +++ b/docs/development/domain/import-hygiene.md @@ -0,0 +1,53 @@ +## Import Hygiene Guide (Domain) + +### Principles + +- **No deep imports**: internal file layout is not part of the contract. +- **Barrels define the public API**: if it’s not exported from the entrypoint, it’s not public. +- **Providers are integration-only**: Portal must never import provider adapters/types. + +### Allowed import levels + +- **Default (Portal + BFF)**: + - `@customer-portal/domain/` + - `@customer-portal/domain/toolkit` +- **BFF-only (integration/infrastructure)**: + - `@customer-portal/domain//providers` + +### Never + +- `@customer-portal/domain//**` (anything deeper than the module entrypoint) +- `@customer-portal/domain//providers/**` (anything deeper than `/providers`) +- `apps/portal/**` importing any `@customer-portal/domain/*/providers` + +### Rule of thumb + +Import from the **highest stable entrypoint** that contains what you need. + +- If it exists in `@customer-portal/domain/`, don’t import a deeper file. +- If it’s provider-specific, use `@customer-portal/domain//providers` (BFF-only). +- If it’s cross-domain utility, use `@customer-portal/domain/toolkit`. + +### When to create a new explicit entrypoint (instead of deep-importing) + +Create/adjust exports when: + +- The symbol is used in 2+ apps (Portal + BFF), or many call sites. +- The symbol is part of a workflow that should remain stable (pagination, formatting, shared validation helpers). + +Where to export it: + +- **Module root** (`@customer-portal/domain/`): normalized domain types/models, schemas, provider-agnostic helpers. +- **Providers entrypoint** (`...//providers`): provider adapters, mapper/query builder logic, raw provider shapes (if truly needed). +- **Toolkit** (`@customer-portal/domain/toolkit`): shared utilities (`Formatting`, `Validation`, `Typing` namespaces). + +### Naming conventions + +- **Module root**: `Invoice`, `invoiceSchema`, `validateOrderBusinessRules` +- **Providers**: `Whmcs`, `Salesforce`, `Freebit` namespaces; raw shapes should be obviously integration-only. + +### PR checklist + +- No `@customer-portal/domain/*/*` imports (except exact `...//providers` in BFF). +- Portal has **zero** `.../providers` imports. +- No wildcard subpath exports added to `packages/domain/package.json#exports`. diff --git a/docs/development/domain/structure.md b/docs/development/domain/structure.md index a0be9441..7635ad73 100644 --- a/docs/development/domain/structure.md +++ b/docs/development/domain/structure.md @@ -20,6 +20,8 @@ - Adding new providers = adding new folders (no refactoring) - Single package (`@customer-portal/domain`) for all types +**Import rules**: See [`docs/development/domain/import-hygiene.md`](import-hygiene.md). + --- ## 📦 Package Structure @@ -130,9 +132,9 @@ const validated = invoiceSchema.parse(rawData); import { Invoice, invoiceSchema } from "@customer-portal/domain/billing"; import { transformWhmcsInvoice, + whmcsInvoiceRawSchema, type WhmcsInvoiceRaw, -} from "@customer-portal/domain/billing/providers/whmcs/mapper"; -import { whmcsInvoiceRawSchema } from "@customer-portal/domain/billing/providers/whmcs/raw.types"; +} from "@customer-portal/domain/billing/providers"; // Transform raw API data const whmcsData: WhmcsInvoiceRaw = await whmcsApi.getInvoice(id); @@ -256,7 +258,7 @@ Raw types and mappers stay in `providers/`: ```typescript // ✅ GOOD - Isolated -import { transformWhmcsInvoice } from "@customer-portal/domain/billing/providers/whmcs/mapper"; +import { transformWhmcsInvoice } from "@customer-portal/domain/billing/providers"; // ❌ BAD - Would leak WHMCS details into app code import { WhmcsInvoiceRaw } from "@somewhere/global"; @@ -335,7 +337,7 @@ export function transformStripeInvoice(raw: unknown): Invoice { ```typescript // No changes to domain contract needed! -import { transformStripeInvoice } from "@customer-portal/domain/billing/providers/stripe/mapper"; +import { transformStripeInvoice } from "@customer-portal/domain/billing/providers"; const invoice = transformStripeInvoice(stripeData); ``` diff --git a/eslint.config.mjs b/eslint.config.mjs index 775d1b4b..58ae95e3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -155,17 +155,79 @@ export default [ "no-restricted-imports": [ "error", { + paths: [ + { + name: "@customer-portal/domain", + message: + "Do not import @customer-portal/domain (root). Use @customer-portal/domain/ instead.", + }, + ], patterns: [ { group: ["@customer-portal/domain/**/src/**"], message: "Import from @customer-portal/domain/ instead of internals.", }, + { + group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"], + message: + "No deep @customer-portal/domain imports. Use @customer-portal/domain/ (or BFF-only: ...//providers).", + }, + { + group: ["@customer-portal/domain/*/providers/*"], + message: + "Do not deep-import provider internals. Import from @customer-portal/domain//providers only.", + }, ], }, ], }, }, + // ============================================================================= + // Portal: hard boundary — must not import provider adapters/types + // ============================================================================= + { + files: ["apps/portal/src/**/*.{ts,tsx}"], + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "@customer-portal/domain", + message: + "Do not import @customer-portal/domain (root). Use @customer-portal/domain/ instead.", + }, + ], + patterns: [ + { + group: ["@customer-portal/domain/**/src/**"], + message: "Import from @customer-portal/domain/ instead of internals.", + }, + { + group: ["@customer-portal/domain/*/providers"], + message: + "Portal must not import provider adapters/types. Import normalized domain models from @customer-portal/domain/ instead.", + }, + { + group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"], + message: "No deep @customer-portal/domain imports. Use @customer-portal/domain/ only.", + }, + { + group: ["@customer-portal/domain/*/providers/*"], + message: + "Do not deep-import provider internals. Import from @customer-portal/domain//providers only (BFF-only).", + }, + ], + }, + ], + }, + }, + { + files: ["apps/portal/src/app/(authenticated)/layout.tsx"], + rules: { "no-restricted-imports": "off" }, + }, + // ============================================================================= // BFF: stricter type safety (type-aware) // ============================================================================= diff --git a/packages/domain/billing/constants.ts b/packages/domain/billing/constants.ts index cba0b650..ce52c55b 100644 --- a/packages/domain/billing/constants.ts +++ b/packages/domain/billing/constants.ts @@ -4,7 +4,7 @@ * Domain constants for billing validation and business rules. */ -import type { WhmcsCurrency } from "./providers/whmcs/raw.types.js"; +import type { Currency } from "./schema.js"; // ============================================================================ // Currency Defaults @@ -15,7 +15,7 @@ import type { WhmcsCurrency } from "./providers/whmcs/raw.types.js"; * is unavailable. This ensures a single source of truth for default currency * formatting behaviour. */ -export const FALLBACK_CURRENCY: WhmcsCurrency = { +export const FALLBACK_CURRENCY: Currency = { id: 1, code: "JPY", prefix: "¥", diff --git a/packages/domain/billing/index.ts b/packages/domain/billing/index.ts index 374f393e..b0c68045 100644 --- a/packages/domain/billing/index.ts +++ b/packages/domain/billing/index.ts @@ -15,6 +15,7 @@ export * from "./schema.js"; // Re-export types for convenience export type { + Currency, InvoiceStatus, InvoiceItem, Invoice, @@ -29,23 +30,3 @@ export type { InvoiceSsoQuery, InvoicePaymentLinkQuery, } from "./schema.js"; - -// Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider raw types (request and response) -export type { - // Request params - WhmcsGetInvoicesParams, - WhmcsCreateInvoiceParams, - WhmcsUpdateInvoiceParams, - WhmcsCapturePaymentParams, - // Response types - WhmcsInvoiceListResponse, - WhmcsInvoiceResponse, - WhmcsCreateInvoiceResponse, - WhmcsUpdateInvoiceResponse, - WhmcsCapturePaymentResponse, - WhmcsCurrency, - WhmcsCurrenciesResponse, -} from "./providers/whmcs/raw.types.js"; diff --git a/packages/domain/billing/schema.ts b/packages/domain/billing/schema.ts index 0d3fb55a..19ecaf30 100644 --- a/packages/domain/billing/schema.ts +++ b/packages/domain/billing/schema.ts @@ -7,6 +7,25 @@ import { z } from "zod"; +// ============================================================================ +// Currency (Domain Model) +// ============================================================================ + +/** + * Normalized currency model used across the Portal and BFF. + * This is intentionally provider-agnostic (even if sourced from WHMCS). + */ +export const currencySchema = z.object({ + id: z.number().int().positive(), + code: z.string().min(1), + prefix: z.string(), + suffix: z.string().optional(), + format: z.string().optional(), + rate: z.string().optional(), +}); + +export type Currency = z.infer; + // Invoice Status Schema export const invoiceStatusSchema = z.enum([ "Draft", diff --git a/packages/domain/common/index.ts b/packages/domain/common/index.ts index 3cc371aa..8435ccb3 100644 --- a/packages/domain/common/index.ts +++ b/packages/domain/common/index.ts @@ -8,10 +8,3 @@ export * from "./types.js"; export * from "./schema.js"; export * from "./validation.js"; export * from "./errors.js"; - -// Common provider types (generic wrappers used across domains) -export * as CommonProviders from "./providers/index.js"; - -// Re-export provider types for convenience -export type { WhmcsResponse, WhmcsErrorResponse } from "./providers/whmcs.js"; -export type { SalesforceResponse } from "./providers/salesforce.js"; diff --git a/packages/domain/common/providers/index.ts b/packages/domain/common/providers/index.ts index d1e03d72..b93a41a6 100644 --- a/packages/domain/common/providers/index.ts +++ b/packages/domain/common/providers/index.ts @@ -4,5 +4,11 @@ * Generic provider-specific response structures used across multiple domains. */ -export * as Whmcs from "./whmcs.js"; +export type { WhmcsResponse, WhmcsErrorResponse } from "./whmcs.js"; +export { whmcsResponseSchema, whmcsErrorResponseSchema } from "./whmcs.js"; + +export type { SalesforceResponse } from "./salesforce.js"; +export { salesforceResponseSchema } from "./salesforce.js"; + +// Salesforce raw types (integration-only utilities) export * as Salesforce from "./salesforce/index.js"; diff --git a/packages/domain/customer/index.ts b/packages/domain/customer/index.ts index 20bcd236..d527bb93 100644 --- a/packages/domain/customer/index.ts +++ b/packages/domain/customer/index.ts @@ -68,34 +68,6 @@ export { * - Providers.Whmcs.transformWhmcsClientResponse() * - Providers.Portal.mapPrismaUserToUserAuth() */ -export * as Providers from "./providers/index.js"; - -// ============================================================================ -// Provider Raw Response Types (Selective Exports) -// ============================================================================ - -/** - * WHMCS API raw types (request and response) - * Only exported for BFF integration convenience - */ -export type { - // Request params - WhmcsAddClientParams, - WhmcsValidateLoginParams, - WhmcsCreateSsoTokenParams, - // Response types - WhmcsClientResponse, - WhmcsAddClientResponse, - WhmcsValidateLoginResponse, - WhmcsSsoResponse, -} from "./providers/whmcs/raw.types.js"; - -// ============================================================================ -// Provider-Specific Types (For Integrations) -// ============================================================================ - -/** - * Salesforce integration types - * Provider-specific, not validated at runtime - */ -export type { SalesforceAccountFieldMap, SalesforceAccountRecord } from "./contract.js"; +// NOTE: Provider adapters and provider-specific types are intentionally not exported +// from the module root. Import BFF-only provider APIs from: +// @customer-portal/domain/customer/providers diff --git a/packages/domain/customer/providers/index.ts b/packages/domain/customer/providers/index.ts index a552f7be..daaeb747 100644 --- a/packages/domain/customer/providers/index.ts +++ b/packages/domain/customer/providers/index.ts @@ -8,3 +8,15 @@ export * as Portal from "./portal/index.js"; export * as Whmcs from "./whmcs/index.js"; + +// Provider-specific integration types (BFF-only) +export type { SalesforceAccountFieldMap, SalesforceAccountRecord } from "../contract.js"; +export type { + WhmcsAddClientParams, + WhmcsValidateLoginParams, + WhmcsCreateSsoTokenParams, + WhmcsClientResponse, + WhmcsAddClientResponse, + WhmcsValidateLoginResponse, + WhmcsSsoResponse, +} from "./whmcs/raw.types.js"; diff --git a/packages/domain/orders/contract.ts b/packages/domain/orders/contract.ts index 9eaf1329..046e3e34 100644 --- a/packages/domain/orders/contract.ts +++ b/packages/domain/orders/contract.ts @@ -6,7 +6,6 @@ */ import type { UserIdMapping } from "../mappings/contract.js"; -import type { SalesforceOrderRecord } from "./providers/salesforce/raw.types.js"; import type { OrderConfigurations } from "./schema.js"; // ============================================================================ @@ -178,12 +177,9 @@ export interface OrderCreateResponse { /** * Order fulfillment validation result */ -export interface OrderFulfillmentValidationResult { - sfOrder: SalesforceOrderRecord; - clientId: number; - isAlreadyProvisioned: boolean; - whmcsOrderId?: string; -} +// 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) diff --git a/packages/domain/orders/index.ts b/packages/domain/orders/index.ts index 0c9c9785..3ee120c9 100644 --- a/packages/domain/orders/index.ts +++ b/packages/domain/orders/index.ts @@ -18,7 +18,6 @@ export { type CheckoutTotals, type CheckoutCart, type OrderCreateResponse, - type OrderFulfillmentValidationResult, // Constants ORDER_TYPE, ORDER_STATUS, @@ -83,9 +82,6 @@ export type { } from "./schema.js"; // Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider types for convenience -export * from "./providers/whmcs/raw.types.js"; -export * from "./providers/salesforce/raw.types.js"; -export * from "./providers/salesforce/field-map.js"; +// NOTE: Provider adapters are intentionally not exported from the module root. +// Import BFF-only provider adapters from: +// @customer-portal/domain/orders/providers diff --git a/packages/domain/orders/providers/index.ts b/packages/domain/orders/providers/index.ts index 00084bb6..ee16431d 100644 --- a/packages/domain/orders/providers/index.ts +++ b/packages/domain/orders/providers/index.ts @@ -7,6 +7,7 @@ import * as WhmcsRaw from "./whmcs/raw.types.js"; import * as SalesforceFieldMap from "./salesforce/field-map.js"; import * as SalesforceMapper from "./salesforce/mapper.js"; import * as SalesforceRaw from "./salesforce/raw.types.js"; +export * from "./types.js"; export const Whmcs = { ...WhmcsMapper, diff --git a/packages/domain/orders/providers/types.ts b/packages/domain/orders/providers/types.ts new file mode 100644 index 00000000..df6c3677 --- /dev/null +++ b/packages/domain/orders/providers/types.ts @@ -0,0 +1,14 @@ +import type { SalesforceOrderRecord } from "./salesforce/raw.types.js"; + +/** + * Order fulfillment validation result (integration-only) + * + * This intentionally lives under the providers entrypoint to avoid leaking + * raw provider shapes into the public orders module API. + */ +export interface OrderFulfillmentValidationResult { + sfOrder: SalesforceOrderRecord; + clientId: number; + isAlreadyProvisioned: boolean; + whmcsOrderId?: string; +} diff --git a/packages/domain/package.json b/packages/domain/package.json index b4ca43eb..0055f5c7 100644 --- a/packages/domain/package.json +++ b/packages/domain/package.json @@ -11,157 +11,113 @@ "dist" ], "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - }, "./auth": { "import": "./dist/auth/index.js", "types": "./dist/auth/index.d.ts" }, - "./auth/*": { - "import": "./dist/auth/*.js", - "types": "./dist/auth/*.d.ts" - }, "./billing": { "import": "./dist/billing/index.js", "types": "./dist/billing/index.d.ts" }, - "./billing/*": { - "import": "./dist/billing/*.js", - "types": "./dist/billing/*.d.ts" + "./billing/providers": { + "import": "./dist/billing/providers/index.js", + "types": "./dist/billing/providers/index.d.ts" }, "./services": { "import": "./dist/services/index.js", "types": "./dist/services/index.d.ts" }, - "./services/*": { - "import": "./dist/services/*.js", - "types": "./dist/services/*.d.ts" + "./services/providers": { + "import": "./dist/services/providers/index.js", + "types": "./dist/services/providers/index.d.ts" }, "./checkout": { "import": "./dist/checkout/index.js", "types": "./dist/checkout/index.d.ts" }, - "./checkout/*": { - "import": "./dist/checkout/*.js", - "types": "./dist/checkout/*.d.ts" - }, "./common": { "import": "./dist/common/index.js", "types": "./dist/common/index.d.ts" }, - "./common/*": { - "import": "./dist/common/*.js", - "types": "./dist/common/*.d.ts" + "./common/providers": { + "import": "./dist/common/providers/index.js", + "types": "./dist/common/providers/index.d.ts" }, "./customer": { "import": "./dist/customer/index.js", "types": "./dist/customer/index.d.ts" }, - "./customer/*": { - "import": "./dist/customer/*.js", - "types": "./dist/customer/*.d.ts" + "./customer/providers": { + "import": "./dist/customer/providers/index.js", + "types": "./dist/customer/providers/index.d.ts" }, "./dashboard": { "import": "./dist/dashboard/index.js", "types": "./dist/dashboard/index.d.ts" }, - "./dashboard/*": { - "import": "./dist/dashboard/*.js", - "types": "./dist/dashboard/*.d.ts" - }, "./mappings": { "import": "./dist/mappings/index.js", "types": "./dist/mappings/index.d.ts" }, - "./mappings/*": { - "import": "./dist/mappings/*.js", - "types": "./dist/mappings/*.d.ts" - }, "./opportunity": { "import": "./dist/opportunity/index.js", "types": "./dist/opportunity/index.d.ts" }, - "./opportunity/*": { - "import": "./dist/opportunity/*.js", - "types": "./dist/opportunity/*.d.ts" - }, "./orders": { "import": "./dist/orders/index.js", "types": "./dist/orders/index.d.ts" }, - "./orders/*": { - "import": "./dist/orders/*.js", - "types": "./dist/orders/*.d.ts" + "./orders/providers": { + "import": "./dist/orders/providers/index.js", + "types": "./dist/orders/providers/index.d.ts" }, "./payments": { "import": "./dist/payments/index.js", "types": "./dist/payments/index.d.ts" }, - "./payments/*": { - "import": "./dist/payments/*.js", - "types": "./dist/payments/*.d.ts" + "./payments/providers": { + "import": "./dist/payments/providers/index.js", + "types": "./dist/payments/providers/index.d.ts" }, "./realtime": { "import": "./dist/realtime/index.js", "types": "./dist/realtime/index.d.ts" }, - "./realtime/*": { - "import": "./dist/realtime/*.js", - "types": "./dist/realtime/*.d.ts" - }, "./sim": { "import": "./dist/sim/index.js", "types": "./dist/sim/index.d.ts" }, - "./sim/*": { - "import": "./dist/sim/*.js", - "types": "./dist/sim/*.d.ts" - }, - "./sim/providers/freebit": { - "import": "./dist/sim/providers/freebit/index.js", - "types": "./dist/sim/providers/freebit/index.d.ts" + "./sim/providers": { + "import": "./dist/sim/providers/index.js", + "types": "./dist/sim/providers/index.d.ts" }, "./subscriptions": { "import": "./dist/subscriptions/index.js", "types": "./dist/subscriptions/index.d.ts" }, - "./subscriptions/*": { - "import": "./dist/subscriptions/*.js", - "types": "./dist/subscriptions/*.d.ts" + "./subscriptions/providers": { + "import": "./dist/subscriptions/providers/index.js", + "types": "./dist/subscriptions/providers/index.d.ts" }, "./support": { "import": "./dist/support/index.js", "types": "./dist/support/index.d.ts" }, - "./support/*": { - "import": "./dist/support/*.js", - "types": "./dist/support/*.d.ts" + "./support/providers": { + "import": "./dist/support/providers/index.js", + "types": "./dist/support/providers/index.d.ts" }, "./toolkit": { "import": "./dist/toolkit/index.js", "types": "./dist/toolkit/index.d.ts" }, - "./toolkit/*": { - "import": "./dist/toolkit/*.js", - "types": "./dist/toolkit/*.d.ts" - }, "./notifications": { "import": "./dist/notifications/index.js", "types": "./dist/notifications/index.d.ts" }, - "./notifications/*": { - "import": "./dist/notifications/*.js", - "types": "./dist/notifications/*.d.ts" - }, "./salesforce": { "import": "./dist/salesforce/index.js", "types": "./dist/salesforce/index.d.ts" - }, - "./salesforce/*": { - "import": "./dist/salesforce/*.js", - "types": "./dist/salesforce/*.d.ts" } }, "scripts": { diff --git a/packages/domain/payments/index.ts b/packages/domain/payments/index.ts index 18ab39a9..66343226 100644 --- a/packages/domain/payments/index.ts +++ b/packages/domain/payments/index.ts @@ -21,17 +21,3 @@ export type { PaymentGateway, PaymentGatewayList, } from "./schema.js"; - -// Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider raw types (request and response) -export type { - // Request params - WhmcsGetPayMethodsParams, - // Response types - WhmcsPaymentMethod, - WhmcsPaymentMethodListResponse, - WhmcsPaymentGateway, - WhmcsPaymentGatewayListResponse, -} from "./providers/whmcs/raw.types.js"; diff --git a/packages/domain/services/index.ts b/packages/domain/services/index.ts index ca621e13..3408bc17 100644 --- a/packages/domain/services/index.ts +++ b/packages/domain/services/index.ts @@ -6,13 +6,7 @@ * Types are derived from Zod schemas (Schema-First Approach) */ -// Provider-specific types -export { - type SalesforceProductFieldMap, - type PricingTier, - type CatalogFilter, - type CatalogPriceInfo, -} from "./contract.js"; +export { type PricingTier, type CatalogPriceInfo } from "./contract.js"; // Schemas (includes derived types) export * from "./schema.js"; @@ -40,21 +34,5 @@ export type { CatalogProduct, } from "./schema.js"; -// Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider raw types for convenience -export * from "./providers/salesforce/raw.types.js"; - -// Re-export WHMCS provider types -export type { - WhmcsCatalogProduct, - WhmcsCatalogProductListResponse, -} from "./providers/whmcs/raw.types.js"; -export type { - WhmcsCatalogProductNormalized, - WhmcsCatalogPricing, -} from "./providers/whmcs/mapper.js"; - // Utilities export * from "./utils.js"; diff --git a/packages/domain/sim/index.ts b/packages/domain/sim/index.ts index 4189f6e0..8606fa61 100644 --- a/packages/domain/sim/index.ts +++ b/packages/domain/sim/index.ts @@ -84,6 +84,3 @@ export type { } from "./schema.js"; export type { SimPlanCode } from "./contract.js"; export type { SimPlanOption, SimFeatureToggleSnapshot } from "./helpers.js"; - -// Provider adapters -export * as Providers from "./providers/index.js"; diff --git a/packages/domain/subscriptions/index.ts b/packages/domain/subscriptions/index.ts index cc675903..f2a4205a 100644 --- a/packages/domain/subscriptions/index.ts +++ b/packages/domain/subscriptions/index.ts @@ -37,16 +37,3 @@ export { internetCancellationPreviewSchema, internetCancelRequestSchema, } from "./schema.js"; - -// Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider raw types (request and response) -export type { - // Request params - WhmcsGetClientsProductsParams, - // Response types - WhmcsProductListResponse, -} from "./providers/whmcs/raw.types.js"; - -export { whmcsProductListResponseSchema } from "./providers/whmcs/raw.types.js"; diff --git a/packages/domain/support/index.ts b/packages/domain/support/index.ts index cbe8be05..6a86dba4 100644 --- a/packages/domain/support/index.ts +++ b/packages/domain/support/index.ts @@ -16,10 +16,3 @@ export { // Schemas (includes derived types) export * from "./schema.js"; - -// Provider adapters -export * as Providers from "./providers/index.js"; - -// Re-export provider types for convenience -export * from "./providers/salesforce/raw.types.js"; -export * from "./providers/salesforce/mapper.js"; diff --git a/packages/domain/toolkit/README.md b/packages/domain/toolkit/README.md index 54f97118..bcb2c935 100644 --- a/packages/domain/toolkit/README.md +++ b/packages/domain/toolkit/README.md @@ -71,18 +71,18 @@ getHostname(url); // Extracts hostname ### Formatting ```typescript -import { formatCurrency, formatDate } from "@customer-portal/domain/toolkit/formatting"; +import { Formatting } from "@customer-portal/domain/toolkit"; -const price = formatCurrency(1000, "JPY"); // "¥1,000" -const date = formatDate(new Date()); // "2025-10-08" +const price = Formatting.formatCurrency(1000, "JPY"); // "¥1,000" +const date = Formatting.formatDate(new Date()); // "2025-10-08" ``` ### Type Guards ```typescript -import { isString, isNumber } from "@customer-portal/domain/toolkit/typing"; +import { Typing } from "@customer-portal/domain/toolkit"; -if (isString(value)) { +if (Typing.isString(value)) { // TypeScript knows value is string here console.log(value.toUpperCase()); } @@ -91,31 +91,31 @@ if (isString(value)) { ### URL Utilities ```typescript -import { ensureProtocol, getHostname } from "@customer-portal/domain/toolkit/validation/url"; +import { Validation } from "@customer-portal/domain/toolkit"; -const fullUrl = ensureProtocol("example.com"); // "https://example.com" -const host = getHostname("https://example.com/path"); // "example.com" +const fullUrl = Validation.ensureProtocol("example.com"); // "https://example.com" +const host = Validation.getHostname("https://example.com/path"); // "example.com" ``` ### Email Utilities ```typescript -import { getEmailDomain, normalizeEmail } from "@customer-portal/domain/toolkit/validation/email"; +import { Validation } from "@customer-portal/domain/toolkit"; -const domain = getEmailDomain("user@example.com"); // "example.com" -const normalized = normalizeEmail(" User@Example.COM "); // "user@example.com" +const domain = Validation.getEmailDomain("user@example.com"); // "example.com" +const normalized = Validation.normalizeEmail(" User@Example.COM "); // "user@example.com" ``` ## When to Use What -| Task | Use | Example | -| --------------------- | -------------------------------- | ------------------------------- | -| Validate email format | `common/validation.ts` | `isValidEmail(email)` | -| Extract email domain | `toolkit/validation/email.ts` | `getEmailDomain(email)` | -| Validate URL format | `common/validation.ts` | `isValidUrl(url)` | -| Add protocol to URL | `toolkit/validation/url.ts` | `ensureProtocol(url)` | -| Format currency | `toolkit/formatting/currency.ts` | `formatCurrency(amount, "JPY")` | -| Format date | `toolkit/formatting/date.ts` | `formatDate(date)` | +| Task | Use | Example | +| --------------------- | ---------------------- | ------------------------------------------ | +| Validate email format | `common/validation.ts` | `isValidEmail(email)` | +| Extract email domain | `toolkit` | `Validation.getEmailDomain(email)` | +| Validate URL format | `common/validation.ts` | `isValidUrl(url)` | +| Add protocol to URL | `toolkit` | `Validation.ensureProtocol(url)` | +| Format currency | `toolkit` | `Formatting.formatCurrency(amount, "JPY")` | +| Format date | `toolkit` | `Formatting.formatDate(date)` | ## Best Practices @@ -134,8 +134,8 @@ const normalized = normalizeEmail(" User@Example.COM "); // "user@example.com" ```typescript // ✅ Good - import { normalizeEmail } from "@customer-portal/domain/toolkit/validation/email"; - const clean = normalizeEmail(email); + import { Validation } from "@customer-portal/domain/toolkit"; + const clean = Validation.normalizeEmail(email); // ❌ Bad - don't duplicate utility logic const clean = email.trim().toLowerCase(); @@ -152,11 +152,11 @@ const normalized = normalizeEmail(" User@Example.COM "); // "user@example.com" // ✅ Good - separate concerns import { isValidEmail } from "@customer-portal/domain/common/validation"; - import { normalizeEmail } from "@customer-portal/domain/toolkit/validation/email"; + import { Validation } from "@customer-portal/domain/toolkit"; function processEmail(email: string) { if (!isValidEmail(email)) return null; - return normalizeEmail(email); + return Validation.normalizeEmail(email); } ``` diff --git a/packages/domain/toolkit/index.ts b/packages/domain/toolkit/index.ts index f9c804bc..d33b93be 100644 --- a/packages/domain/toolkit/index.ts +++ b/packages/domain/toolkit/index.ts @@ -7,20 +7,3 @@ export * as Formatting from "./formatting/index.js"; export * as Validation from "./validation/index.js"; export * as Typing from "./typing/index.js"; - -// Re-export commonly used utilities for convenience -export { formatCurrency } from "./formatting/currency.js"; -export type { SupportedCurrency } from "./formatting/currency.js"; - -// Re-export AsyncState types and helpers -export type { AsyncState } from "./typing/helpers.js"; -export { - createIdleState, - createLoadingState, - createSuccessState, - createErrorState, - isIdle, - isLoading, - isSuccess, - isError, -} from "./typing/helpers.js"; diff --git a/scripts/domain/check-dist.sh b/scripts/domain/check-dist.sh index 1fb88160..e7aa5811 100644 --- a/scripts/domain/check-dist.sh +++ b/scripts/domain/check-dist.sh @@ -10,6 +10,12 @@ cd "$ROOT_DIR" echo "[domain] Building @customer-portal/domain…" pnpm --filter @customer-portal/domain build >/dev/null +echo "[domain] Checking exports contract…" +node ./scripts/domain/check-exports.mjs + +echo "[domain] Checking import contract…" +bash ./scripts/domain/check-import-contract.sh + if command -v git >/dev/null 2>&1; then if git diff --quiet -- packages/domain/dist; then echo "[domain] OK: packages/domain/dist is up to date." diff --git a/scripts/domain/check-exports.mjs b/scripts/domain/check-exports.mjs new file mode 100644 index 00000000..98dfadd1 --- /dev/null +++ b/scripts/domain/check-exports.mjs @@ -0,0 +1,26 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +const ROOT = process.cwd(); +const pkgPath = path.join(ROOT, "packages", "domain", "package.json"); + +const pkg = JSON.parse(await fs.readFile(pkgPath, "utf8")); + +const exportsField = pkg.exports; +if (!exportsField || typeof exportsField !== "object") { + console.error("[domain] ERROR: package.json exports field is missing or invalid."); + process.exit(1); +} + +const keys = Object.keys(exportsField); +const wildcardKeys = keys.filter(k => k.includes("*")); + +if (wildcardKeys.length > 0) { + console.error("[domain] ERROR: wildcard subpath exports are not allowed:"); + for (const k of wildcardKeys) console.error(`- ${k}`); + process.exit(1); +} + +console.log("[domain] OK: package.json exports contains no wildcard keys."); + + diff --git a/scripts/domain/check-import-contract.sh b/scripts/domain/check-import-contract.sh new file mode 100644 index 00000000..21552f33 --- /dev/null +++ b/scripts/domain/check-import-contract.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Guardrail: enforce the domain import contract. +# +# Allowed: +# - @customer-portal/domain/ +# - @customer-portal/domain/toolkit +# - BFF-only: @customer-portal/domain//providers +# +# Never: +# - @customer-portal/domain// +# - @customer-portal/domain//providers/ +# - Portal importing any .../providers + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +fail() { + echo "[domain] ERROR: $1" >&2 + exit 1 +} + +echo "[domain] Checking for illegal deep imports in apps/…" + +# Root import is forbidden (hard error). Only match actual import statements. +if grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.next \ + --include='*.ts' --include='*.tsx' \ + "(from[[:space:]]+[\"']@customer-portal/domain[\"'])|(import[[:space:]]+[\"']@customer-portal/domain[\"'])" \ + apps >/dev/null; then + echo "[domain] Found forbidden root imports (@customer-portal/domain):" + grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.next \ + --include='*.ts' --include='*.tsx' \ + "(from[[:space:]]+[\"']@customer-portal/domain[\"'])|(import[[:space:]]+[\"']@customer-portal/domain[\"'])" \ + apps | head -200 + fail "Root import is forbidden. Use @customer-portal/domain/." +fi + +# Any 3+ segment import like @customer-portal/domain/a/b/c is illegal anywhere. +if grep -RInE --include='*.ts' --include='*.tsx' \ + "@customer-portal/domain/[^\"'[:space:]]+/[^\"'[:space:]]+/[^\"'[:space:]]+" \ + apps >/dev/null; then + echo "[domain] Found illegal deep imports (3+ segments):" + grep -RInE --include='*.ts' --include='*.tsx' \ + "@customer-portal/domain/[^\"'[:space:]]+/[^\"'[:space:]]+/[^\"'[:space:]]+" \ + apps | head -200 + fail "Deep imports detected. Use @customer-portal/domain/ or ...//providers." +fi + +echo "[domain] Checking Portal boundary (no providers imports)…" + +if grep -RInE --include='*.ts' --include='*.tsx' \ + "@customer-portal/domain/[^\"'[:space:]]+/providers" \ + apps/portal/src >/dev/null; then + echo "[domain] Found provider imports in Portal:" + grep -RInE --include='*.ts' --include='*.tsx' \ + "@customer-portal/domain/[^\"'[:space:]]+/providers" \ + apps/portal/src | head -200 + fail "Portal must not import provider adapters/types." +fi + +echo "[domain] OK: import contract checks passed." + + diff --git a/scripts/domain/codemod-domain-imports.mjs b/scripts/domain/codemod-domain-imports.mjs new file mode 100644 index 00000000..2f96fdd1 --- /dev/null +++ b/scripts/domain/codemod-domain-imports.mjs @@ -0,0 +1,129 @@ +import fs from "node:fs/promises"; +import path from "node:path"; + +const ROOT = process.cwd(); +const TARGET_DIRS = [ + path.join(ROOT, "apps", "bff", "src"), + path.join(ROOT, "apps", "portal", "src"), +]; + +const FILE_EXTS = new Set([".ts", ".tsx"]); + +async function* walk(dir) { + const entries = await fs.readdir(dir, { withFileTypes: true }); + for (const e of entries) { + const p = path.join(dir, e.name); + if (e.isDirectory()) { + if (e.name === "node_modules" || e.name === "dist" || e.name.startsWith(".")) continue; + yield* walk(p); + } else if (e.isFile()) { + if (FILE_EXTS.has(path.extname(e.name))) yield p; + } + } +} + +function replaceProvidersDeepImports(code) { + // "@customer-portal/domain//providers/" -> "...//providers" + return code + .replaceAll( + /from\s+"@customer-portal\/domain\/([a-z-]+)\/providers\/[^"]+"/g, + 'from "@customer-portal/domain/$1/providers"' + ) + .replaceAll( + /from\s+'@customer-portal\/domain\/([a-z-]+)\/providers\/[^']+'/g, + "from '@customer-portal/domain/$1/providers'" + ); +} + +function replaceCommonProviderTypes(code) { + // Move provider response wrapper types out of common root -> common/providers + // Only touches *type-only* imports to avoid moving runtime exports accidentally. + return code + .replaceAll( + /import\s+type\s+\{([^}]*)\}\s+from\s+"@customer-portal\/domain\/common";/g, + (m, spec) => { + const s = String(spec); + const needsMove = + s.includes("WhmcsResponse") || s.includes("WhmcsErrorResponse") || s.includes("SalesforceResponse"); + if (!needsMove) return m; + return `import type {${spec}} from "@customer-portal/domain/common/providers";`; + } + ) + .replaceAll( + /import\s+type\s+\{([^}]*)\}\s+from\s+'@customer-portal\/domain\/common';/g, + (m, spec) => { + const s = String(spec); + const needsMove = + s.includes("WhmcsResponse") || s.includes("WhmcsErrorResponse") || s.includes("SalesforceResponse"); + if (!needsMove) return m; + return `import type {${spec}} from '@customer-portal/domain/common/providers';`; + } + ); +} + +function replaceProvidersNamespaceImports(code) { + // import { Providers } from "@customer-portal/domain/"; + // import { Providers as Foo } from "@customer-portal/domain/"; + return code + .replaceAll( + /import\s+\{\s*Providers\s*\}\s+from\s+"@customer-portal\/domain\/([a-z-]+)";/g, + 'import * as Providers from "@customer-portal/domain/$1/providers";' + ) + .replaceAll( + /import\s+\{\s*Providers\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\s+from\s+"@customer-portal\/domain\/([a-z-]+)";/g, + 'import * as $1 from "@customer-portal/domain/$2/providers";' + ) + .replaceAll( + /import\s+\{\s*Providers\s*\}\s+from\s+'@customer-portal\/domain\/([a-z-]+)';/g, + "import * as Providers from '@customer-portal/domain/$1/providers';" + ) + .replaceAll( + /import\s+\{\s*Providers\s+as\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\s+from\s+'@customer-portal\/domain\/([a-z-]+)';/g, + "import * as $1 from '@customer-portal/domain/$2/providers';" + ); +} + +function replaceToolkitPaginationHelper(code) { + if (!code.includes("@customer-portal/domain/toolkit/validation/helpers")) return code; + let next = code; + + next = next + .replaceAll( + /import\s+\{\s*createPaginationSchema\s*\}\s+from\s+"@customer-portal\/domain\/toolkit\/validation\/helpers";/g, + 'import { Validation } from "@customer-portal/domain/toolkit";' + ) + .replaceAll( + /import\s+\{\s*createPaginationSchema\s*\}\s+from\s+'@customer-portal\/domain\/toolkit\/validation\/helpers';/g, + "import { Validation } from '@customer-portal/domain/toolkit';" + ); + + // Update call sites + next = next.replaceAll(/\bcreatePaginationSchema\b/g, "Validation.createPaginationSchema"); + + return next; +} + +function transform(code) { + let next = code; + next = replaceProvidersDeepImports(next); + next = replaceCommonProviderTypes(next); + next = replaceProvidersNamespaceImports(next); + next = replaceToolkitPaginationHelper(next); + return next; +} + +let changedFiles = 0; +for (const dir of TARGET_DIRS) { + for await (const file of walk(dir)) { + const before = await fs.readFile(file, "utf8"); + const after = transform(before); + if (after !== before) { + await fs.writeFile(file, after, "utf8"); + changedFiles += 1; + } + } +} + +console.log(`codemod-domain-imports: updated ${changedFiles} file(s)`); + + diff --git a/scripts/migrate-imports.sh b/scripts/migrate-imports.sh index 9b032bd8..2205b2dd 100755 --- a/scripts/migrate-imports.sh +++ b/scripts/migrate-imports.sh @@ -48,7 +48,7 @@ migrate_directory() { -e 's|@customer-portal/contracts/sim|@customer-portal/domain/sim|g' \ -e 's|@customer-portal/schemas/business/sim|@customer-portal/domain/sim|g' \ -e 's|@customer-portal/integrations-freebit/mappers/sim|@customer-portal/domain/sim|g' \ - -e 's|@customer-portal/schemas/integrations/freebit/requests|@customer-portal/domain/sim/providers/freebit|g' \ + -e 's|@customer-portal/schemas/integrations/freebit/requests|@customer-portal/domain/sim/providers|g' \ {} + # Orders domain