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.
This commit is contained in:
parent
fcc9bc247e
commit
a3dbd07183
@ -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.
|
||||
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
|
||||
@ -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<typeof CustomerProviders.Portal.mapPrismaUserToUserAuth>[0];
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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]`] || ""),
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 };
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
defaultSalesforceOrderFieldMap,
|
||||
type PartialSalesforceOrderFieldMap,
|
||||
type SalesforceOrderFieldMap,
|
||||
} from "@customer-portal/domain/orders";
|
||||
} from "@customer-portal/domain/orders/providers";
|
||||
|
||||
const unique = <T>(values: T[]): T[] => Array.from(new Set(values));
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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<WhmcsCurrency>({
|
||||
const { data, isLoading, isError, error } = useQuery<Currency>({
|
||||
queryKey: queryKeys.currency.default(),
|
||||
queryFn: () => currencyService.getDefaultCurrency(),
|
||||
retry: 2,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<WhmcsCurrency> {
|
||||
const response = await apiClient.GET<WhmcsCurrency>("/api/currency/default");
|
||||
async getDefaultCurrency(): Promise<Currency> {
|
||||
const response = await apiClient.GET<Currency>("/api/currency/default");
|
||||
return getDataOrThrow(response, "Failed to get default currency");
|
||||
},
|
||||
|
||||
async getAllCurrencies(): Promise<WhmcsCurrency[]> {
|
||||
const response = await apiClient.GET<WhmcsCurrency[]>("/api/currency/all");
|
||||
async getAllCurrencies(): Promise<Currency[]> {
|
||||
const response = await apiClient.GET<Currency[]>("/api/currency/all");
|
||||
return getDataOrThrow(response, "Failed to get currencies");
|
||||
},
|
||||
};
|
||||
|
||||
@ -102,9 +102,8 @@ 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 |
|
||||
|
||||
|
||||
@ -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**
|
||||
|
||||
53
docs/development/domain/import-hygiene.md
Normal file
53
docs/development/domain/import-hygiene.md
Normal file
@ -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/<module>`
|
||||
- `@customer-portal/domain/toolkit`
|
||||
- **BFF-only (integration/infrastructure)**:
|
||||
- `@customer-portal/domain/<module>/providers`
|
||||
|
||||
### Never
|
||||
|
||||
- `@customer-portal/domain/<module>/**` (anything deeper than the module entrypoint)
|
||||
- `@customer-portal/domain/<module>/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/<module>`, don’t import a deeper file.
|
||||
- If it’s provider-specific, use `@customer-portal/domain/<module>/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/<module>`): normalized domain types/models, schemas, provider-agnostic helpers.
|
||||
- **Providers entrypoint** (`.../<module>/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 `.../<module>/providers` in BFF).
|
||||
- Portal has **zero** `.../providers` imports.
|
||||
- No wildcard subpath exports added to `packages/domain/package.json#exports`.
|
||||
@ -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);
|
||||
```
|
||||
|
||||
@ -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/<module> instead.",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
{
|
||||
group: ["@customer-portal/domain/**/src/**"],
|
||||
message: "Import from @customer-portal/domain/<module> instead of internals.",
|
||||
},
|
||||
{
|
||||
group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"],
|
||||
message:
|
||||
"No deep @customer-portal/domain imports. Use @customer-portal/domain/<module> (or BFF-only: .../<module>/providers).",
|
||||
},
|
||||
{
|
||||
group: ["@customer-portal/domain/*/providers/*"],
|
||||
message:
|
||||
"Do not deep-import provider internals. Import from @customer-portal/domain/<module>/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/<module> instead.",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
{
|
||||
group: ["@customer-portal/domain/**/src/**"],
|
||||
message: "Import from @customer-portal/domain/<module> instead of internals.",
|
||||
},
|
||||
{
|
||||
group: ["@customer-portal/domain/*/providers"],
|
||||
message:
|
||||
"Portal must not import provider adapters/types. Import normalized domain models from @customer-portal/domain/<module> instead.",
|
||||
},
|
||||
{
|
||||
group: ["@customer-portal/domain/*/*", "!@customer-portal/domain/*/providers"],
|
||||
message: "No deep @customer-portal/domain imports. Use @customer-portal/domain/<module> only.",
|
||||
},
|
||||
{
|
||||
group: ["@customer-portal/domain/*/providers/*"],
|
||||
message:
|
||||
"Do not deep-import provider internals. Import from @customer-portal/domain/<module>/providers only (BFF-only).",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["apps/portal/src/app/(authenticated)/layout.tsx"],
|
||||
rules: { "no-restricted-imports": "off" },
|
||||
},
|
||||
|
||||
// =============================================================================
|
||||
// BFF: stricter type safety (type-aware)
|
||||
// =============================================================================
|
||||
|
||||
@ -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: "¥",
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<typeof currencySchema>;
|
||||
|
||||
// Invoice Status Schema
|
||||
export const invoiceStatusSchema = z.enum([
|
||||
"Draft",
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
14
packages/domain/orders/providers/types.ts
Normal file
14
packages/domain/orders/providers/types.ts
Normal file
@ -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;
|
||||
}
|
||||
@ -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": {
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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)` |
|
||||
| Extract email domain | `toolkit` | `Validation.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)` |
|
||||
| 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);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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."
|
||||
|
||||
26
scripts/domain/check-exports.mjs
Normal file
26
scripts/domain/check-exports.mjs
Normal file
@ -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.");
|
||||
|
||||
|
||||
64
scripts/domain/check-import-contract.sh
Normal file
64
scripts/domain/check-import-contract.sh
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Guardrail: enforce the domain import contract.
|
||||
#
|
||||
# Allowed:
|
||||
# - @customer-portal/domain/<module>
|
||||
# - @customer-portal/domain/toolkit
|
||||
# - BFF-only: @customer-portal/domain/<module>/providers
|
||||
#
|
||||
# Never:
|
||||
# - @customer-portal/domain/<module>/<anything-else>
|
||||
# - @customer-portal/domain/<module>/providers/<anything-else>
|
||||
# - 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/<module>."
|
||||
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/<module> or .../<module>/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."
|
||||
|
||||
|
||||
129
scripts/domain/codemod-domain-imports.mjs
Normal file
129
scripts/domain/codemod-domain-imports.mjs
Normal file
@ -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/<module>/providers/<anything>" -> ".../<module>/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/<module>";
|
||||
// import { Providers as Foo } from "@customer-portal/domain/<module>";
|
||||
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)`);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user