Files
edr-platform/apps/finance-api/src/config/database.config.ts
2026-08-25 00:11:39 +03:00

111 lines
4.1 KiB
TypeScript

import { dirname } from "path";
import { registerAs } from "@nestjs/config";
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { DataSourceOptions } from "typeorm";
/**
* Finance migrations are recorded in `finance.migrations`, alongside
* `iam.typeorm_migrations`, `freight.migrations` and `hr.migrations` in the same
* database. Each app owns exactly one history table; Finance never writes
* another app's.
*/
export const FINANCE_MIGRATIONS = {
schema: "finance",
table: "migrations",
} as const;
/**
* IAM entity registration.
*
* `IamModule` is embedded in this app (the platform pattern: every service takes
* the IAM package as a dependency and IAM gates each action by the caller's
* position), so its entities must be on this DataSource.
*
* Registered as GLOBS over the two package dists rather than as a hand-written
* class list — copied from `apps/edr-hr-api/src/config/database.config.ts`,
* which documents why: freight-api's hand-listed array goes stale on every
* package bump, and globs cannot.
*
* BOTH dists are required. Some IAM entities relate to notification entities
* that physically live in `@tria-plc/api-common` — the IAM barrel only
* re-exports them — so registering only the IAM dist throws
* `Entity metadata for User#sessions was not found` at boot.
*/
function resolvePackageDist(pkg: string): string {
// Node honours each package's `exports` map at runtime even though TypeScript's
// resolution does not, so require.resolve on the barrel lands in dist/.
return dirname(require.resolve(pkg)).replace(/\\/g, "/");
}
const IAM_ENTITY_GLOBS = [
`${resolvePackageDist("@tria-plc/iamapi-common")}/entities/**/*.entity.{ts,js}`,
`${resolvePackageDist("@tria-plc/api-common")}/entities/**/*.entity.{ts,js}`,
];
function buildConnectionOptions() {
return {
type: "postgres" as const,
host: process.env.DB_HOST ?? "localhost",
port: parseInt(process.env.DB_PORT ?? "5432", 10),
username: process.env.DB_USER ?? "edr",
password: process.env.DB_PASSWORD ?? "edr_secret",
database: process.env.DB_NAME ?? "edr_database",
// Do NOT pass `extra.options: '-c search_path=...'` — the connection pooler
// fronting the dev database rejects the Postgres `options` startup parameter
// with `08P01`. search_path is applied per physical connection in a pool
// `connect` handler instead (see app.module.ts).
synchronize: false,
logging:
process.env.TYPEORM_LOGGING === "true"
? true
: (["error", "warn"] as DataSourceOptions["logging"]),
};
}
/**
* Runtime options for the API. Carries no migrations — see `migration:run`.
*
* One connection serves both schemas: Finance entities are
* `@Entity({schema:"finance"})` and IAM's are `schema:"iam"`, so they cannot
* collide. `autoLoadEntities` is OFF because everything is listed explicitly
* here — leaving it on lets IamModule's `forFeature` registrations add a PARTIAL
* entity set, which is the exact configuration that failed at boot in HR.
*/
export function buildDataSourceOptions(): DataSourceOptions {
return {
...buildConnectionOptions(),
schema: FINANCE_MIGRATIONS.schema,
entities: [__dirname + "/../**/*.entity.{ts,js}", ...IAM_ENTITY_GLOBS],
migrations: [],
};
}
/**
* Finance migrations only, recorded in `finance.migrations`.
*
* Deliberately no entities: this DataSource exists to run Finance's own DDL.
* Finance never migrates `iam`, `freight`, `passenger`, `hr` or `edr_payment` —
* it only ever READS those, and each belongs to its own owner.
*/
export function buildFinanceMigrationDataSourceOptions(): DataSourceOptions {
return {
...buildConnectionOptions(),
schema: FINANCE_MIGRATIONS.schema,
entities: [],
migrations: [__dirname + "/../migrations/*.js"],
migrationsTableName: FINANCE_MIGRATIONS.table,
migrationsTransactionMode: "each",
};
}
export default registerAs(
"database",
(): TypeOrmModuleOptions => ({
...buildDataSourceOptions(),
autoLoadEntities: false,
// Migrations run as a separate one-shot step, never on API boot (house rule).
migrationsRun: false,
}),
);