Merge branch 'ver2' into codex/extend-api-client-for-auth-header-os9pej
This commit is contained in:
commit
a9f7aa5403
@ -37,6 +37,13 @@ A modern customer portal where users can self-register, log in, browse & buy sub
|
||||
- **BullMQ** for async jobs with ioredis
|
||||
- **OpenAPI/Swagger** for documentation
|
||||
|
||||
### Temporarily Disabled Modules
|
||||
|
||||
- `CasesModule` and `JobsModule` are intentionally excluded from the running
|
||||
NestJS application until their APIs and job processors are fully
|
||||
implemented. See `docs/TEMPORARY-DISABLED-MODULES.md` for re-enablement
|
||||
details and placeholder behaviour.
|
||||
|
||||
### Logging
|
||||
|
||||
- Centralized structured logging via Pino using `nestjs-pino` in the BFF
|
||||
|
||||
@ -21,7 +21,6 @@ import { EmailModule } from "@bff/infra/email/email.module";
|
||||
// External Integration Modules
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module";
|
||||
import { SalesforceEventsModule } from "@bff/integrations/salesforce/events/events.module";
|
||||
import { JobsModule } from "@bff/modules/jobs/jobs.module";
|
||||
|
||||
// Feature Modules
|
||||
import { AuthModule } from "@bff/modules/auth/auth.module";
|
||||
@ -31,7 +30,6 @@ import { CatalogModule } from "@bff/modules/catalog/catalog.module";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module";
|
||||
import { InvoicesModule } from "@bff/modules/invoices/invoices.module";
|
||||
import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module";
|
||||
import { CasesModule } from "@bff/modules/cases/cases.module";
|
||||
|
||||
// System Modules
|
||||
import { HealthModule } from "@bff/modules/health/health.module";
|
||||
@ -73,7 +71,6 @@ import { SuccessResponseInterceptor } from "@bff/core/http/success-response.inte
|
||||
// === EXTERNAL INTEGRATIONS ===
|
||||
IntegrationsModule,
|
||||
SalesforceEventsModule,
|
||||
JobsModule,
|
||||
|
||||
// === FEATURE MODULES ===
|
||||
AuthModule,
|
||||
@ -83,7 +80,6 @@ import { SuccessResponseInterceptor } from "@bff/core/http/success-response.inte
|
||||
OrdersModule,
|
||||
InvoicesModule,
|
||||
SubscriptionsModule,
|
||||
CasesModule,
|
||||
|
||||
// === SYSTEM MODULES ===
|
||||
HealthModule,
|
||||
|
||||
152
apps/bff/src/app/bootstrap.ts
Normal file
152
apps/bff/src/app/bootstrap.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { type INestApplication, ValidationPipe } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import helmet from "helmet";
|
||||
import cookieParser from "cookie-parser";
|
||||
import * as express from "express";
|
||||
|
||||
import { GlobalExceptionFilter } from "@bff/core/http/http-exception.filter";
|
||||
|
||||
import { AppModule } from "../app.module";
|
||||
|
||||
export async function bootstrap(): Promise<INestApplication> {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
bufferLogs: true,
|
||||
// bodyParser is enabled by default in NestJS
|
||||
rawBody: true, // Enable raw body access for debugging
|
||||
});
|
||||
|
||||
// Set Pino as the logger
|
||||
app.useLogger(app.get(Logger));
|
||||
|
||||
const configService = app.get(ConfigService);
|
||||
const logger = app.get(Logger);
|
||||
|
||||
// Enhanced Security Headers
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
crossOriginResourcePolicy: { policy: "cross-origin" },
|
||||
})
|
||||
);
|
||||
|
||||
// Disable x-powered-by header
|
||||
const expressInstance = app.getHttpAdapter().getInstance() as {
|
||||
disable?: (name: string) => void;
|
||||
};
|
||||
if (typeof expressInstance?.disable === "function") {
|
||||
expressInstance.disable("x-powered-by");
|
||||
}
|
||||
|
||||
// Configure JSON body parser with proper limits
|
||||
app.use(express.json({ limit: "10mb" }));
|
||||
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
||||
|
||||
// Enhanced cookie parser with security options
|
||||
app.use(cookieParser());
|
||||
|
||||
// Trust proxy configuration for reverse proxies
|
||||
if (configService.get("TRUST_PROXY", "false") === "true") {
|
||||
const httpAdapter = app.getHttpAdapter();
|
||||
const instance = httpAdapter.getInstance() as {
|
||||
set?: (key: string, value: unknown) => void;
|
||||
};
|
||||
if (typeof instance?.set === "function") {
|
||||
instance.set("trust proxy", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced CORS configuration
|
||||
const corsOrigin = configService.get<string | undefined>("CORS_ORIGIN");
|
||||
app.enableCors({
|
||||
origin: corsOrigin ? [corsOrigin] : false,
|
||||
credentials: true,
|
||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
allowedHeaders: [
|
||||
"Origin",
|
||||
"X-Requested-With",
|
||||
"Content-Type",
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"X-API-Key",
|
||||
],
|
||||
exposedHeaders: ["X-Total-Count", "X-Page-Count"],
|
||||
maxAge: 86400, // 24 hours
|
||||
});
|
||||
|
||||
// Global validation pipe with enhanced security
|
||||
const exposeValidation = configService.get("EXPOSE_VALIDATION_ERRORS", "false") === "true";
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
forbidUnknownValues: true,
|
||||
disableErrorMessages: !exposeValidation && configService.get("NODE_ENV") === "production",
|
||||
validationError: {
|
||||
target: false,
|
||||
value: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Global exception filter
|
||||
app.useGlobalFilters(new GlobalExceptionFilter(app.get(Logger)));
|
||||
|
||||
// Global authentication guard will be registered via APP_GUARD provider in AuthModule
|
||||
|
||||
// Rely on Nest's built-in shutdown hooks. External orchestrator will send signals.
|
||||
app.enableShutdownHooks();
|
||||
|
||||
// Swagger documentation (only in non-production) - SETUP BEFORE GLOBAL PREFIX
|
||||
if (configService.get("NODE_ENV") !== "production") {
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle("Customer Portal API")
|
||||
.setDescription("Backend for Frontend API for customer portal")
|
||||
.setVersion("1.0")
|
||||
.addBearerAuth()
|
||||
.addCookieAuth("auth-cookie")
|
||||
.addServer("http://localhost:4000", "Development server")
|
||||
.addServer("https://api.yourdomain.com", "Production server")
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup("docs", app, document);
|
||||
}
|
||||
|
||||
// API routing prefix is applied via RouterModule in AppModule for clarity and modern routing.
|
||||
|
||||
const port = Number(configService.get("BFF_PORT", 4000));
|
||||
|
||||
await app.listen(port, "0.0.0.0");
|
||||
|
||||
// Enhanced startup information
|
||||
logger.log(`🚀 BFF API running on: http://localhost:${port}/api`);
|
||||
logger.log(`🌐 Frontend Portal: http://localhost:${configService.get("NEXT_PORT", 3000)}`);
|
||||
logger.log(
|
||||
`🗄️ Database: ${configService.get("DATABASE_URL", "postgresql://dev:dev@localhost:5432/portal_dev")}`
|
||||
);
|
||||
logger.log(`🔗 Prisma Studio: http://localhost:5555`);
|
||||
logger.log(`🔴 Redis: ${configService.get("REDIS_URL", "redis://localhost:6379")}`);
|
||||
|
||||
if (configService.get("NODE_ENV") !== "production") {
|
||||
logger.log(`📚 API Documentation: http://localhost:${port}/docs`);
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
@ -6,7 +6,6 @@ import { CatalogModule } from "@bff/modules/catalog/catalog.module";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module";
|
||||
import { InvoicesModule } from "@bff/modules/invoices/invoices.module";
|
||||
import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module";
|
||||
import { CasesModule } from "@bff/modules/cases/cases.module";
|
||||
|
||||
export const apiRoutes: Routes = [
|
||||
{
|
||||
@ -19,7 +18,6 @@ export const apiRoutes: Routes = [
|
||||
{ path: "", module: OrdersModule },
|
||||
{ path: "", module: InvoicesModule },
|
||||
{ path: "", module: SubscriptionsModule },
|
||||
{ path: "", module: CasesModule },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,151 +1,45 @@
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { ValidationPipe } from "@nestjs/common";
|
||||
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import helmet from "helmet";
|
||||
import cookieParser from "cookie-parser";
|
||||
import * as express from "express";
|
||||
import { Logger, type INestApplication } from "@nestjs/common";
|
||||
|
||||
import { AppModule } from "./app.module";
|
||||
import { GlobalExceptionFilter } from "@bff/core/http/http-exception.filter";
|
||||
import { bootstrap } from "./app/bootstrap";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
bufferLogs: true,
|
||||
// bodyParser is enabled by default in NestJS
|
||||
rawBody: true, // Enable raw body access for debugging
|
||||
const logger = new Logger("Main");
|
||||
let app: INestApplication | null = null;
|
||||
|
||||
const signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
|
||||
for (const signal of signals) {
|
||||
process.once(signal, async () => {
|
||||
logger.log(`Received ${signal}. Closing Nest application...`);
|
||||
|
||||
if (!app) {
|
||||
logger.warn("Nest application not initialized. Exiting immediately.");
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await app.close();
|
||||
logger.log("Nest application closed gracefully.");
|
||||
} catch (error) {
|
||||
const resolvedError = error as Error;
|
||||
logger.error(
|
||||
`Error during Nest application shutdown: ${resolvedError.message}`,
|
||||
resolvedError.stack
|
||||
);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
// Set Pino as the logger
|
||||
app.useLogger(app.get(Logger));
|
||||
|
||||
const configService = app.get(ConfigService);
|
||||
const logger = app.get(Logger);
|
||||
|
||||
// Enhanced Security Headers
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||
scriptSrc: ["'self'"],
|
||||
imgSrc: ["'self'", "data:", "https:"],
|
||||
connectSrc: ["'self'"],
|
||||
fontSrc: ["'self'"],
|
||||
objectSrc: ["'none'"],
|
||||
mediaSrc: ["'self'"],
|
||||
frameSrc: ["'none'"],
|
||||
},
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
crossOriginResourcePolicy: { policy: "cross-origin" },
|
||||
})
|
||||
);
|
||||
|
||||
// Disable x-powered-by header
|
||||
const expressInstance = app.getHttpAdapter().getInstance() as {
|
||||
disable?: (name: string) => void;
|
||||
};
|
||||
if (typeof expressInstance?.disable === "function") {
|
||||
expressInstance.disable("x-powered-by");
|
||||
}
|
||||
|
||||
// Configure JSON body parser with proper limits
|
||||
app.use(express.json({ limit: "10mb" }));
|
||||
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
||||
|
||||
// Enhanced cookie parser with security options
|
||||
app.use(cookieParser());
|
||||
|
||||
// Trust proxy configuration for reverse proxies
|
||||
if (configService.get("TRUST_PROXY", "false") === "true") {
|
||||
const httpAdapter = app.getHttpAdapter();
|
||||
const instance = httpAdapter.getInstance() as {
|
||||
set?: (key: string, value: unknown) => void;
|
||||
};
|
||||
if (typeof instance?.set === "function") {
|
||||
instance.set("trust proxy", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced CORS configuration
|
||||
const corsOrigin = configService.get<string | undefined>("CORS_ORIGIN");
|
||||
app.enableCors({
|
||||
origin: corsOrigin ? [corsOrigin] : false,
|
||||
credentials: true,
|
||||
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||
allowedHeaders: [
|
||||
"Origin",
|
||||
"X-Requested-With",
|
||||
"Content-Type",
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"X-API-Key",
|
||||
],
|
||||
exposedHeaders: ["X-Total-Count", "X-Page-Count"],
|
||||
maxAge: 86400, // 24 hours
|
||||
});
|
||||
|
||||
// Global validation pipe with enhanced security
|
||||
const exposeValidation = configService.get("EXPOSE_VALIDATION_ERRORS", "false") === "true";
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
forbidUnknownValues: true,
|
||||
disableErrorMessages: !exposeValidation && configService.get("NODE_ENV") === "production",
|
||||
validationError: {
|
||||
target: false,
|
||||
value: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Global exception filter
|
||||
app.useGlobalFilters(new GlobalExceptionFilter(app.get(Logger)));
|
||||
|
||||
// Global authentication guard will be registered via APP_GUARD provider in AuthModule
|
||||
|
||||
// Rely on Nest's built-in shutdown hooks. External orchestrator will send signals.
|
||||
app.enableShutdownHooks();
|
||||
|
||||
// Swagger documentation (only in non-production) - SETUP BEFORE GLOBAL PREFIX
|
||||
if (configService.get("NODE_ENV") !== "production") {
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle("Customer Portal API")
|
||||
.setDescription("Backend for Frontend API for customer portal")
|
||||
.setVersion("1.0")
|
||||
.addBearerAuth()
|
||||
.addCookieAuth("auth-cookie")
|
||||
.addServer("http://localhost:4000", "Development server")
|
||||
.addServer("https://api.yourdomain.com", "Production server")
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup("docs", app, document);
|
||||
}
|
||||
|
||||
// API routing prefix is applied via RouterModule in AppModule for clarity and modern routing.
|
||||
|
||||
const port = Number(configService.get("BFF_PORT", 4000));
|
||||
|
||||
await app.listen(port, "0.0.0.0");
|
||||
|
||||
// Enhanced startup information
|
||||
logger.log(`🚀 BFF API running on: http://localhost:${port}/api`);
|
||||
logger.log(`🌐 Frontend Portal: http://localhost:${configService.get("NEXT_PORT", 3000)}`);
|
||||
logger.log(
|
||||
`🗄️ Database: ${configService.get("DATABASE_URL", "postgresql://dev:dev@localhost:5432/portal_dev")}`
|
||||
);
|
||||
logger.log(`🔗 Prisma Studio: http://localhost:5555`);
|
||||
logger.log(`🔴 Redis: ${configService.get("REDIS_URL", "redis://localhost:6379")}`);
|
||||
|
||||
if (configService.get("NODE_ENV") !== "production") {
|
||||
logger.log(`📚 API Documentation: http://localhost:${port}/docs`);
|
||||
}
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
void bootstrap()
|
||||
.then((startedApp) => {
|
||||
app = startedApp;
|
||||
})
|
||||
.catch((error) => {
|
||||
const resolvedError = error as Error;
|
||||
logger.error(
|
||||
`Failed to bootstrap the Nest application: ${resolvedError.message}`,
|
||||
resolvedError.stack
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@ -1,11 +1,22 @@
|
||||
import { Processor, WorkerHost } from "@nestjs/bullmq";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { Job } from "bullmq";
|
||||
import { QUEUE_NAMES } from "@bff/infra/queue/queue.constants";
|
||||
|
||||
@Processor(QUEUE_NAMES.RECONCILE)
|
||||
export class ReconcileProcessor extends WorkerHost {
|
||||
async process(_job: Job) {
|
||||
// TODO: Implement reconciliation logic
|
||||
// Note: In production, this should use proper logging
|
||||
private readonly logger = new Logger(ReconcileProcessor.name);
|
||||
|
||||
async process(job: Job) {
|
||||
this.logger.warn(
|
||||
`Skipping reconciliation job while JobsModule is temporarily disabled`,
|
||||
{
|
||||
jobId: job.id,
|
||||
name: job.name,
|
||||
attemptsMade: job.attemptsMade,
|
||||
},
|
||||
);
|
||||
|
||||
return { status: "skipped", reason: "jobs_module_disabled" };
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
/**
|
||||
|
||||
* Core API client configuration
|
||||
* Wraps the shared generated client to inject portal-specific behavior like auth headers.
|
||||
*/
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export { apiClient, configureApiClientAuth } from "./client";
|
||||
export { queryKeys } from "./query-keys";
|
||||
|
||||
export type { ApiClient } from "./client";
|
||||
export type { AuthHeaderResolver } from "@customer-portal/api-client";
|
||||
|
||||
@ -352,6 +352,7 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
error: null,
|
||||
hasCheckedAuth: true,
|
||||
});
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
// Token is invalid, clear auth state
|
||||
|
||||
@ -22,9 +22,11 @@ export function useSubscriptions(options: UseSubscriptionsOptions = {}) {
|
||||
return useQuery<SubscriptionList | Subscription[]>({
|
||||
queryKey: ["subscriptions", status],
|
||||
queryFn: async () => {
|
||||
if (!hasValidToken) {
|
||||
throw new Error("Authentication required");
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
...(status && { status }),
|
||||
});
|
||||
|
||||
|
||||
const response = await apiClient.GET(
|
||||
"/subscriptions",
|
||||
@ -56,6 +58,7 @@ export function useActiveSubscriptions() {
|
||||
throw new Error("Authentication required");
|
||||
}
|
||||
|
||||
|
||||
const response = await apiClient.GET("/subscriptions/active");
|
||||
return (response.data ?? []) as Subscription[];
|
||||
},
|
||||
@ -84,6 +87,7 @@ export function useSubscriptionStats() {
|
||||
throw new Error("Authentication required");
|
||||
}
|
||||
|
||||
|
||||
const response = await apiClient.GET("/subscriptions/stats");
|
||||
return (response.data ?? {
|
||||
total: 0,
|
||||
@ -92,12 +96,16 @@ export function useSubscriptionStats() {
|
||||
cancelled: 0,
|
||||
pending: 0,
|
||||
}) as {
|
||||
|
||||
|
||||
total: number;
|
||||
active: number;
|
||||
suspended: number;
|
||||
cancelled: number;
|
||||
pending: number;
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcTime: 10 * 60 * 1000, // 10 minutes
|
||||
@ -118,6 +126,7 @@ export function useSubscription(subscriptionId: number) {
|
||||
throw new Error("Authentication required");
|
||||
}
|
||||
|
||||
|
||||
const response = await apiClient.GET("/subscriptions/{id}", {
|
||||
params: { path: { id: subscriptionId } },
|
||||
});
|
||||
@ -127,6 +136,7 @@ export function useSubscription(subscriptionId: number) {
|
||||
}
|
||||
|
||||
return response.data as Subscription;
|
||||
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
gcTime: 10 * 60 * 1000, // 10 minutes
|
||||
@ -168,6 +178,7 @@ export function useSubscriptionInvoices(
|
||||
},
|
||||
}
|
||||
) as InvoiceList;
|
||||
|
||||
},
|
||||
staleTime: 60 * 1000, // 1 minute
|
||||
gcTime: 5 * 60 * 1000, // 5 minutes
|
||||
@ -187,6 +198,7 @@ export function useSubscriptionAction() {
|
||||
params: { path: { id } },
|
||||
body: { action },
|
||||
});
|
||||
|
||||
},
|
||||
onSuccess: (_, { id }) => {
|
||||
// Invalidate relevant queries after successful action
|
||||
|
||||
28
docs/TEMPORARY-DISABLED-MODULES.md
Normal file
28
docs/TEMPORARY-DISABLED-MODULES.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Temporarily Disabled Modules
|
||||
|
||||
The backend currently omits two partially implemented modules from the runtime
|
||||
NestJS configuration so that the public API surface only exposes completed
|
||||
features.
|
||||
|
||||
## Cases Module
|
||||
|
||||
- Removed from `AppModule` and `apiRoutes` to ensure the unfinished `/cases`
|
||||
endpoints are not routable.
|
||||
- All existing code remains in `apps/bff/src/modules/cases/` for future
|
||||
development; re-enable by importing the module in
|
||||
`apps/bff/src/app.module.ts` and adding it back to the router configuration in
|
||||
`apps/bff/src/core/config/router.config.ts` once the endpoints are ready.
|
||||
|
||||
## Jobs Module
|
||||
|
||||
- Temporarily excluded from `AppModule` while the reconciliation workflows are
|
||||
fleshed out.
|
||||
- The BullMQ processor now logs an explicit warning and acknowledges each job so
|
||||
queue workers do not hang when the module is re-registered.
|
||||
- When background processing is ready, restore the `JobsModule` import in
|
||||
`apps/bff/src/app.module.ts` and replace the placeholder logic in
|
||||
`ReconcileProcessor.process` with the real reconciliation implementation.
|
||||
|
||||
> **Note**: If additional queues or HTTP routes reference these modules, make
|
||||
> sure they fail fast with a `501 Not Implemented` response or similar logging so
|
||||
> that downstream systems have clear telemetry while the modules are disabled.
|
||||
@ -18,6 +18,7 @@ export function createClient(
|
||||
throwOnError: true,
|
||||
});
|
||||
|
||||
|
||||
if (typeof client.use === "function" && options.getAuthHeader) {
|
||||
const resolveAuthHeader = options.getAuthHeader;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user