import { registerAs } from "@nestjs/config"; import { TypeOrmModuleOptions } from "@nestjs/typeorm"; import { DataSourceOptions } from "typeorm"; import { join, dirname } from "path"; import { DefaultPosition, DefaultUnit, EmployeePosition, Employee, OrganizationConfiguration, GlobalOrganizationConfiguration, OrganizationType, Organization, PositionConfiguration, PositionPermission, PositionTypeConfiguration, PositionTypePermission, PositionType, Position, Project, GlobalUnitConfiguration, Unit, EmployeeSignature, EmployeeStamp, RecordFooter, RecordHeader, Seal, AccountConfiguration, Application, DocumentaryRequirement, Permission, RolePermission, Role, Session, UserCredential, UserDocument, UserRole, UserVerification, User, Notification, NotificationEvent, NotificationPlaceholder, NotificationReceiverField, NotificationReceiver, NotificationTemplate, } from "@tria-plc/iamapi-common"; import { OrganizationSetting } from "@tria-plc/iamapi-common/entities/iam/organization-structure/organization-setting.entity"; import { UnitSetting } from "@tria-plc/iamapi-common/entities/iam/organization-structure/unit-setting.entity"; import { UnitDetail } from "@tria-plc/iamapi-common/entities/iam/organization-structure/unit-detail.entity"; import { OrganizationDetail } from "@tria-plc/iamapi-common/entities/iam/organization-structure/organization-detail.entity"; import { UnitCluster } from "@tria-plc/iamapi-common/entities/iam/organization-structure/unit-cluster.entity"; import { Location } from "@tria-plc/iamapi-common/entities/iam/organization-structure/location.entity"; import { LocationType } from "@tria-plc/iamapi-common/entities/iam/organization-structure/location-type.entity"; import { DelegationTerminationReason } from "@tria-plc/iamapi-common/entities/iam/organization-structure/delegation-termination-reason.entity"; import { EmployeePositionActivePeriod } from "@tria-plc/iamapi-common/entities/iam/organization-structure/employee-position-active-period.entity"; import { UnitConfiguration } from "@tria-plc/iamapi-common/entities/iam/organization-structure/unit-configuration.entity"; import { Site } from "@tria-plc/iamapi-common/entities/iam/site/site.entity"; import { SiteSetting } from "@tria-plc/iamapi-common/entities/iam/site/site-setting.entity"; import { AuditLog, AuditLogCommand } from "@tria-plc/auditlog"; // @tria-plc/auditlog's entities live in node_modules, same as the iam ones — // the glob below only matches this app's own src/**/*.entity.ts. const auditEntities = [AuditLog, AuditLogCommand]; const iamEntities = [ UnitSetting, UnitDetail, OrganizationDetail, UnitCluster, Location, LocationType, DelegationTerminationReason, EmployeePositionActivePeriod, UnitConfiguration, Site, SiteSetting, DefaultPosition, DefaultUnit, EmployeePosition, Employee, OrganizationConfiguration, GlobalOrganizationConfiguration, OrganizationSetting, OrganizationType, Organization, PositionConfiguration, PositionPermission, PositionTypeConfiguration, PositionTypePermission, PositionType, Position, Project, GlobalUnitConfiguration, Unit, EmployeeSignature, EmployeeStamp, RecordFooter, RecordHeader, Seal, AccountConfiguration, Application, DocumentaryRequirement, Permission, RolePermission, Role, Session, UserCredential, UserDocument, UserRole, UserVerification, User, Notification, NotificationEvent, NotificationPlaceholder, NotificationReceiverField, NotificationReceiver, NotificationTemplate, ]; const iamMigrationsGlob = join( dirname(require.resolve("@tria-plc/iamapi-common/package.json")), "dist/db/migrations/*.js", ); const freightMigrationsGlob = join(__dirname, "../migrations/*.js"); /** * Migration history is split per owner instead of sharing one `public.migrations` * table: * * - IAM migrations ship with `@tria-plc/iamapi-common`, target the `iam` schema * and are recorded in `iam.typeorm_migrations` — the same table the package's * own CLI (`pnpm iam:migration:run|show|revert`) uses, so both paths agree on * what has been applied. * - Freight migrations are recorded in `freight.migrations`. * * `public.migrations` is the pre-split table; `src/scripts/migrate.ts` adopts its * rows into the two tables above on first run and then leaves it untouched. */ export const IAM_MIGRATIONS = { schema: "iam", table: "typeorm_migrations", } as const; export const FREIGHT_MIGRATIONS = { schema: "freight", table: "migrations", } as const; export const LEGACY_MIGRATIONS = { schema: "public", table: "migrations", } as const; function buildConnectionOptions() { return { type: "postgres" as const, host: process.env.DB_HOST ?? "localhost", port: parseInt(process.env.DB_PORT ?? "5433", 10), username: process.env.DB_USER ?? "postgres", password: process.env.DB_PASSWORD ?? "", database: process.env.DB_NAME ?? "edr_freight", // NOTE: do NOT pass `extra.options: '-c search_path=...'`. That sends the // Postgres startup `options` parameter, which connection poolers (PgBouncer / // proxies fronting the remote edr_dev DB) reject with // `08P01 unsupported startup parameter in options: search_path`. // The search_path is instead applied per-connection via a pool `connect` // handler (app.module.ts `setPoolSearchPath`, migrate.ts `applySearchPath`). synchronize: false, logging: process.env.TYPEORM_LOGGING === "true" ? true : (["error", "warn"] as DataSourceOptions["logging"]), }; } /** * Runtime options for the API (and the seed scripts using `AppDataSource`). * Carries no migrations: migrations run only through `src/scripts/migrate.ts`, * which uses the two dedicated DataSources below. */ export function buildDataSourceOptions(): DataSourceOptions { return { ...buildConnectionOptions(), schema: "public", entities: [ __dirname + "/../**/*.entity.{ts,js}", ...iamEntities, ...auditEntities, ], migrations: [], }; } /** IAM migrations only, recorded in `iam.typeorm_migrations`. */ export function buildIamMigrationDataSourceOptions(): DataSourceOptions { return { ...buildConnectionOptions(), schema: IAM_MIGRATIONS.schema, entities: [], migrations: [iamMigrationsGlob], migrationsTableName: IAM_MIGRATIONS.table, migrationsTransactionMode: "each", }; } /** Freight migrations only, recorded in `freight.migrations`. */ export function buildFreightMigrationDataSourceOptions(): DataSourceOptions { return { ...buildConnectionOptions(), schema: FREIGHT_MIGRATIONS.schema, entities: [], migrations: [freightMigrationsGlob], migrationsTableName: FREIGHT_MIGRATIONS.table, migrationsTransactionMode: "each", }; } export default registerAs("database", (): TypeOrmModuleOptions => ({ ...buildDataSourceOptions(), autoLoadEntities: true, migrationsRun: false, }));