mirror of https://github.com/OpenVidu/openvidu.git
openvidu-dashboard: the backend container is built in di/container.ts
buildContainer() leaves lifecycle/startup.ts, which keeps the startup sequence; it takes the edition, version and docs switch instead of the startup options.pull/924/head
parent
e34946d44d
commit
7e7e71bfbc
|
|
@ -6,9 +6,11 @@ Behaviour is specified in `openspec/specs/*-api`, `api-conventions`, `auth`, `st
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- `src/lifecycle/startup.ts`: `startDashboard(options)` reads the configuration, connects to MongoDB, applies the
|
- `src/lifecycle/startup.ts`: `startDashboard(options)` reads the configuration, connects to MongoDB, applies the
|
||||||
writer schema gate, builds the container, calls `configure` and the startup hooks, then listens. `TOKENS`
|
writer schema gate, builds the container, calls `configure` and the startup hooks, then listens. Exit codes go
|
||||||
(`src/di/tokens.ts`) are bound with `toResolvedValue` factories, without decorators, so esbuild and Vitest need no
|
through `Shutdown.run(code)`.
|
||||||
metadata. Exit codes go through `Shutdown.run(code)`.
|
- `src/di/container.ts`: `buildContainer()` binds the CE services and extension tokens and registers the CE routes.
|
||||||
|
`TOKENS` (`src/di/tokens.ts`) are bound with `toResolvedValue` factories, without decorators, so esbuild and Vitest
|
||||||
|
need no metadata.
|
||||||
- Routes are declared once in the `RouteRegistry` (`src/api/route-registry.ts`): method, path under `/api/v1`, edition,
|
- Routes are declared once in the `RouteRegistry` (`src/api/route-registry.ts`): method, path under `/api/v1`, edition,
|
||||||
`feature` of a PRO route, security (`public`, `bearer`, `optional-bearer`), Zod schemas of params, query, body and
|
`feature` of a PRO route, security (`public`, `bearer`, `optional-bearer`), Zod schemas of params, query, body and
|
||||||
responses, and the handler. It drives validation, authentication and the OpenAPI document (only when `NODE_ENV` is
|
responses, and the handler. It drives validation, authentication and the OpenAPI document (only when `NODE_ENV` is
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
import { Container } from 'inversify';
|
||||||
|
import { type Db, type MongoClient } from 'mongodb';
|
||||||
|
|
||||||
|
import { type Edition, type FeatureKey, type InfoLicense } from '@openvidu-dashboard/typings';
|
||||||
|
|
||||||
|
import { AnalyticsCache } from '../analytics/analytics-cache.js';
|
||||||
|
import { type AnalyticsCatalogue, CE_ANALYTICS_CATALOGUE } from '../analytics/analytics-catalogue.js';
|
||||||
|
import { AnalyticsFeatureGate } from '../analytics/analytics-gate.js';
|
||||||
|
import { registerAnalyticsRoutes } from '../analytics/analytics-routes.js';
|
||||||
|
import { AnalyticsService } from '../analytics/analytics-service.js';
|
||||||
|
import { registerProbes } from '../api/probes.js';
|
||||||
|
import { rankedSortLimits } from '../api/ranked-limits.js';
|
||||||
|
import { RouteRegistry } from '../api/route-registry.js';
|
||||||
|
import { registerAuthRoutes } from '../auth/auth-routes.js';
|
||||||
|
import { AuthService } from '../auth/auth-service.js';
|
||||||
|
import { SessionRepository } from '../auth/session-repository.js';
|
||||||
|
import { type DashboardConfig } from '../config/variables.js';
|
||||||
|
import { registerEgressRoutes } from '../egress/egress-routes.js';
|
||||||
|
import { EgressService } from '../egress/egress-service.js';
|
||||||
|
import { registerInfoRoute } from '../info/info-routes.js';
|
||||||
|
import { InfoService } from '../info/info-service.js';
|
||||||
|
import { registerIngressRoutes } from '../ingress/ingress-routes.js';
|
||||||
|
import { IngressService } from '../ingress/ingress-service.js';
|
||||||
|
import { type Shutdown } from '../lifecycle/shutdown.js';
|
||||||
|
import { type Logger } from '../logging/logger.js';
|
||||||
|
import { registerOverviewRoutes } from '../overview/overview-routes.js';
|
||||||
|
import { type OverviewExtension, OverviewService } from '../overview/overview-service.js';
|
||||||
|
import { registerParticipantsRoutes } from '../participants/participants-routes.js';
|
||||||
|
import { ParticipantsService } from '../participants/participants-service.js';
|
||||||
|
import { registerRoomsRoutes } from '../rooms/rooms-routes.js';
|
||||||
|
import { RoomsService } from '../rooms/rooms-service.js';
|
||||||
|
import { StatsModeReader } from '../stats/stats-mode.js';
|
||||||
|
import { WriterFreshness } from '../status/freshness.js';
|
||||||
|
import { registerStatusRoutes } from '../status/status-routes.js';
|
||||||
|
import { StatusService } from '../status/status-service.js';
|
||||||
|
import { registerTracksRoutes } from '../tracks/tracks-routes.js';
|
||||||
|
import { TOKENS } from './tokens.js';
|
||||||
|
|
||||||
|
export interface ContainerInputs {
|
||||||
|
edition: Edition;
|
||||||
|
version: string;
|
||||||
|
/** Whether the OpenAPI document and its page are served. */
|
||||||
|
docsEnabled: boolean;
|
||||||
|
config: DashboardConfig;
|
||||||
|
logger: Logger;
|
||||||
|
client: MongoClient;
|
||||||
|
db: Db;
|
||||||
|
shutdown: Shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the CE services and extension tokens and registers the CE routes. An extending edition rebinds them in its
|
||||||
|
* `configure`, before the startup hooks.
|
||||||
|
*/
|
||||||
|
export function buildContainer(inputs: ContainerInputs): Container {
|
||||||
|
const { edition, version, config, logger, client, db, shutdown } = inputs;
|
||||||
|
const container = new Container();
|
||||||
|
|
||||||
|
container.bind(TOKENS.Config).toConstantValue(config);
|
||||||
|
container.bind(TOKENS.Logger).toConstantValue(logger);
|
||||||
|
container.bind(TOKENS.MongoClient).toConstantValue(client);
|
||||||
|
container.bind(TOKENS.Database).toConstantValue(db);
|
||||||
|
container.bind(TOKENS.Shutdown).toConstantValue(shutdown);
|
||||||
|
container.bind<() => Date>(TOKENS.Clock).toConstantValue(() => new Date());
|
||||||
|
container.bind<FeatureKey[]>(TOKENS.Features).toConstantValue([]);
|
||||||
|
container.bind<() => InfoLicense | undefined>(TOKENS.LicenseInfo).toConstantValue(() => undefined);
|
||||||
|
container.bind(TOKENS.RouteRegistry).toConstantValue(new RouteRegistry(edition));
|
||||||
|
container.bind<AnalyticsCatalogue>(TOKENS.AnalyticsCatalogue).toConstantValue(CE_ANALYTICS_CATALOGUE);
|
||||||
|
container
|
||||||
|
.bind(TOKENS.SessionRepository)
|
||||||
|
.toResolvedValue((database: Db) => new SessionRepository(database), [TOKENS.Database])
|
||||||
|
.inSingletonScope();
|
||||||
|
container
|
||||||
|
.bind(TOKENS.StatsModeReader)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date) => new StatsModeReader(database, clock),
|
||||||
|
[TOKENS.Database, TOKENS.Clock]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
container
|
||||||
|
.bind(TOKENS.InfoService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, statsMode: StatsModeReader) =>
|
||||||
|
new InfoService(database, {
|
||||||
|
edition: edition,
|
||||||
|
version: version,
|
||||||
|
features: () => container.get<FeatureKey[]>(TOKENS.Features),
|
||||||
|
limits: rankedSortLimits(config.lists.totalMaxRangeMs),
|
||||||
|
license: () => container.get<() => InfoLicense | undefined>(TOKENS.LicenseInfo)(),
|
||||||
|
openapiDocsEnabled: inputs.docsEnabled,
|
||||||
|
statsMode
|
||||||
|
}),
|
||||||
|
[TOKENS.Database, TOKENS.StatsModeReader]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
container
|
||||||
|
.bind(TOKENS.AuthService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(key: Uint8Array, sessions: SessionRepository, clock: () => Date) =>
|
||||||
|
new AuthService({
|
||||||
|
username: config.admin.username,
|
||||||
|
password: config.admin.password,
|
||||||
|
edition: edition,
|
||||||
|
key,
|
||||||
|
refreshTokenTtlSeconds: config.session.refreshTokenTtlSeconds,
|
||||||
|
sessions,
|
||||||
|
logger,
|
||||||
|
now: clock
|
||||||
|
}),
|
||||||
|
[TOKENS.SigningKey, TOKENS.SessionRepository, TOKENS.Clock]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.WriterFreshness)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date) => new WriterFreshness(database, clock),
|
||||||
|
[TOKENS.Database, TOKENS.Clock]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.RoomsService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date, freshness: WriterFreshness) =>
|
||||||
|
new RoomsService(database, {
|
||||||
|
totalMaxRangeMs: config.lists.totalMaxRangeMs,
|
||||||
|
freshness,
|
||||||
|
now: clock
|
||||||
|
}),
|
||||||
|
[TOKENS.Database, TOKENS.Clock, TOKENS.WriterFreshness]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.ParticipantsService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date, freshness: WriterFreshness) =>
|
||||||
|
new ParticipantsService(database, {
|
||||||
|
totalMaxRangeMs: config.lists.totalMaxRangeMs,
|
||||||
|
freshness,
|
||||||
|
now: clock
|
||||||
|
}),
|
||||||
|
[TOKENS.Database, TOKENS.Clock, TOKENS.WriterFreshness]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.EgressService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date) =>
|
||||||
|
new EgressService(database, { totalMaxRangeMs: config.lists.totalMaxRangeMs, now: clock }),
|
||||||
|
[TOKENS.Database, TOKENS.Clock]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.IngressService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date) =>
|
||||||
|
new IngressService(database, { totalMaxRangeMs: config.lists.totalMaxRangeMs, now: clock }),
|
||||||
|
[TOKENS.Database, TOKENS.Clock]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.AnalyticsFeatureGate)
|
||||||
|
.toResolvedValue(() => new AnalyticsFeatureGate(() => container.get<FeatureKey[]>(TOKENS.Features)))
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind(TOKENS.AnalyticsService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date, gate: AnalyticsFeatureGate, catalogue: AnalyticsCatalogue) =>
|
||||||
|
new AnalyticsService(database, {
|
||||||
|
now: clock,
|
||||||
|
gate,
|
||||||
|
catalogue,
|
||||||
|
cache: new AnalyticsCache(config.analytics.cacheTtlSeconds * 1000, clock)
|
||||||
|
}),
|
||||||
|
[TOKENS.Database, TOKENS.Clock, TOKENS.AnalyticsFeatureGate, TOKENS.AnalyticsCatalogue]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
container.bind<OverviewExtension>(TOKENS.OverviewExtension).toConstantValue(() => ({}));
|
||||||
|
container
|
||||||
|
.bind(TOKENS.OverviewService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date, extension: OverviewExtension) =>
|
||||||
|
new OverviewService(database, { now: clock, extension }),
|
||||||
|
[TOKENS.Database, TOKENS.Clock, TOKENS.OverviewExtension]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
container
|
||||||
|
.bind(TOKENS.StatusService)
|
||||||
|
.toResolvedValue(
|
||||||
|
(database: Db, clock: () => Date, info: InfoService) => new StatusService(database, { info, now: clock }),
|
||||||
|
[TOKENS.Database, TOKENS.Clock, TOKENS.InfoService]
|
||||||
|
)
|
||||||
|
.inSingletonScope();
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
config.analytics.cacheTtlSeconds > 0
|
||||||
|
? `Analytics cache: completed buckets kept for ${String(config.analytics.cacheTtlSeconds)} s`
|
||||||
|
: 'Analytics cache disabled (DASHBOARD_ANALYTICS_CACHE_TTL=0)'
|
||||||
|
);
|
||||||
|
|
||||||
|
const registry = container.get<RouteRegistry>(TOKENS.RouteRegistry);
|
||||||
|
const now = () => container.get<() => Date>(TOKENS.Clock)();
|
||||||
|
|
||||||
|
registerProbes(registry, version, db);
|
||||||
|
registerAuthRoutes(
|
||||||
|
registry,
|
||||||
|
() => container.get<AuthService>(TOKENS.AuthService),
|
||||||
|
() => ({
|
||||||
|
basePath: container.get<string>(TOKENS.BasePath),
|
||||||
|
ttlSeconds: config.session.refreshTokenTtlSeconds,
|
||||||
|
secure: config.session.cookieSecure
|
||||||
|
})
|
||||||
|
);
|
||||||
|
registerInfoRoute(registry, () => container.get<InfoService>(TOKENS.InfoService));
|
||||||
|
registerRoomsRoutes(registry, () => container.get<RoomsService>(TOKENS.RoomsService), now);
|
||||||
|
registerParticipantsRoutes(registry, () => container.get<ParticipantsService>(TOKENS.ParticipantsService), now);
|
||||||
|
registerTracksRoutes(registry);
|
||||||
|
registerEgressRoutes(registry, () => container.get<EgressService>(TOKENS.EgressService), now);
|
||||||
|
registerIngressRoutes(registry, () => container.get<IngressService>(TOKENS.IngressService), now);
|
||||||
|
registerAnalyticsRoutes(registry, () => container.get<AnalyticsService>(TOKENS.AnalyticsService), now);
|
||||||
|
registerOverviewRoutes(registry, () => container.get<OverviewService>(TOKENS.OverviewService));
|
||||||
|
registerStatusRoutes(registry, () => container.get<StatusService>(TOKENS.StatusService));
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
@ -1,55 +1,25 @@
|
||||||
import type { Server } from 'node:http';
|
import type { Server } from 'node:http';
|
||||||
|
|
||||||
import { Container } from 'inversify';
|
import { type Container } from 'inversify';
|
||||||
import { type Db, MongoClient } from 'mongodb';
|
import { type Db, MongoClient } from 'mongodb';
|
||||||
|
|
||||||
import {
|
import { type Edition, type InfoSchema, WriterSchemaStatus } from '@openvidu-dashboard/typings';
|
||||||
type Edition,
|
|
||||||
type FeatureKey,
|
|
||||||
type InfoLicense,
|
|
||||||
type InfoSchema,
|
|
||||||
WriterSchemaStatus
|
|
||||||
} from '@openvidu-dashboard/typings';
|
|
||||||
|
|
||||||
|
import { docsRouter, openApiDocument } from '../api/openapi.js';
|
||||||
|
import { type RouteRegistry } from '../api/route-registry.js';
|
||||||
|
import { type AuthService } from '../auth/auth-service.js';
|
||||||
|
import { bearerAuthenticator } from '../auth/bearer.js';
|
||||||
|
import { type SessionRepository } from '../auth/session-repository.js';
|
||||||
|
import { provisionSigningKey } from '../auth/signing-key.js';
|
||||||
import { loadConfigFile } from '../config/config-file.js';
|
import { loadConfigFile } from '../config/config-file.js';
|
||||||
import { type DashboardConfig, parseConfig } from '../config/variables.js';
|
import { type DashboardConfig, parseConfig } from '../config/variables.js';
|
||||||
|
import { buildContainer } from '../di/container.js';
|
||||||
import { TOKENS } from '../di/tokens.js';
|
import { TOKENS } from '../di/tokens.js';
|
||||||
import { AnalyticsCache } from '../analytics/analytics-cache.js';
|
|
||||||
import { type AnalyticsCatalogue, CE_ANALYTICS_CATALOGUE } from '../analytics/analytics-catalogue.js';
|
|
||||||
import { AnalyticsFeatureGate } from '../analytics/analytics-gate.js';
|
|
||||||
import { registerAnalyticsRoutes } from '../analytics/analytics-routes.js';
|
|
||||||
import { AnalyticsService } from '../analytics/analytics-service.js';
|
|
||||||
import { docsRouter, openApiDocument } from '../api/openapi.js';
|
|
||||||
import { registerProbes } from '../api/probes.js';
|
|
||||||
import { rankedSortLimits } from '../api/ranked-limits.js';
|
|
||||||
import { RouteRegistry } from '../api/route-registry.js';
|
|
||||||
import { registerAuthRoutes } from '../auth/auth-routes.js';
|
|
||||||
import { AuthService } from '../auth/auth-service.js';
|
|
||||||
import { bearerAuthenticator } from '../auth/bearer.js';
|
|
||||||
import { SessionRepository } from '../auth/session-repository.js';
|
|
||||||
import { provisionSigningKey } from '../auth/signing-key.js';
|
|
||||||
import { registerEgressRoutes } from '../egress/egress-routes.js';
|
|
||||||
import { EgressService } from '../egress/egress-service.js';
|
|
||||||
import { createHttpApp } from '../http/app.js';
|
import { createHttpApp } from '../http/app.js';
|
||||||
import { resolveBasePath } from '../http/base-path.js';
|
import { resolveBasePath } from '../http/base-path.js';
|
||||||
import { InfoService } from '../info/info-service.js';
|
|
||||||
import { registerIngressRoutes } from '../ingress/ingress-routes.js';
|
|
||||||
import { IngressService } from '../ingress/ingress-service.js';
|
|
||||||
import { registerInfoRoute } from '../info/info-routes.js';
|
|
||||||
import { createLogger, type Logger } from '../logging/logger.js';
|
import { createLogger, type Logger } from '../logging/logger.js';
|
||||||
import { mongoClientOptions, resolveMongoUrl } from '../mongo/mongo-client.js';
|
import { mongoClientOptions, resolveMongoUrl } from '../mongo/mongo-client.js';
|
||||||
import { registerOverviewRoutes } from '../overview/overview-routes.js';
|
|
||||||
import { type OverviewExtension, OverviewService } from '../overview/overview-service.js';
|
|
||||||
import { registerParticipantsRoutes } from '../participants/participants-routes.js';
|
|
||||||
import { ParticipantsService } from '../participants/participants-service.js';
|
|
||||||
import { registerRoomsRoutes } from '../rooms/rooms-routes.js';
|
|
||||||
import { RoomsService } from '../rooms/rooms-service.js';
|
|
||||||
import { olderSchemaMessage, readSchemaStatus } from '../schema/schema-status.js';
|
import { olderSchemaMessage, readSchemaStatus } from '../schema/schema-status.js';
|
||||||
import { StatsModeReader } from '../stats/stats-mode.js';
|
|
||||||
import { registerStatusRoutes } from '../status/status-routes.js';
|
|
||||||
import { WriterFreshness } from '../status/freshness.js';
|
|
||||||
import { StatusService } from '../status/status-service.js';
|
|
||||||
import { registerTracksRoutes } from '../tracks/tracks-routes.js';
|
|
||||||
import { type Exit, Shutdown } from './shutdown.js';
|
import { type Exit, Shutdown } from './shutdown.js';
|
||||||
|
|
||||||
/** Process exit codes of the dashboard; an extending edition's startup hooks may exit with codes of their own. */
|
/** Process exit codes of the dashboard; an extending edition's startup hooks may exit with codes of their own. */
|
||||||
|
|
@ -203,7 +173,16 @@ export async function startDashboard(options: DashboardOptions): Promise<Running
|
||||||
|
|
||||||
logSchema(logger, schema);
|
logSchema(logger, schema);
|
||||||
|
|
||||||
const container = buildContainer(options, config, logger, client, db, shutdown);
|
const container = buildContainer({
|
||||||
|
edition: options.edition,
|
||||||
|
version: options.version,
|
||||||
|
docsEnabled: docsEnabled(env),
|
||||||
|
config,
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
db,
|
||||||
|
shutdown
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
options.configure?.(container);
|
options.configure?.(container);
|
||||||
|
|
@ -296,189 +275,3 @@ function logSchema(logger: Logger, schema: InfoSchema): void {
|
||||||
logger.info('The OpenVidu server has not initialized the analytics database yet');
|
logger.info('The OpenVidu server has not initialized the analytics database yet');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildContainer(
|
|
||||||
options: DashboardOptions,
|
|
||||||
config: DashboardConfig,
|
|
||||||
logger: Logger,
|
|
||||||
client: MongoClient,
|
|
||||||
db: Db,
|
|
||||||
shutdown: Shutdown
|
|
||||||
): Container {
|
|
||||||
const env = options.env ?? process.env;
|
|
||||||
const container = new Container();
|
|
||||||
|
|
||||||
container.bind(TOKENS.Config).toConstantValue(config);
|
|
||||||
container.bind(TOKENS.Logger).toConstantValue(logger);
|
|
||||||
container.bind(TOKENS.MongoClient).toConstantValue(client);
|
|
||||||
container.bind(TOKENS.Database).toConstantValue(db);
|
|
||||||
container.bind(TOKENS.Shutdown).toConstantValue(shutdown);
|
|
||||||
container.bind<() => Date>(TOKENS.Clock).toConstantValue(() => new Date());
|
|
||||||
container.bind<FeatureKey[]>(TOKENS.Features).toConstantValue([]);
|
|
||||||
container.bind<() => InfoLicense | undefined>(TOKENS.LicenseInfo).toConstantValue(() => undefined);
|
|
||||||
container.bind(TOKENS.RouteRegistry).toConstantValue(new RouteRegistry(options.edition));
|
|
||||||
container.bind<AnalyticsCatalogue>(TOKENS.AnalyticsCatalogue).toConstantValue(CE_ANALYTICS_CATALOGUE);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.SessionRepository)
|
|
||||||
.toResolvedValue((database: Db) => new SessionRepository(database), [TOKENS.Database])
|
|
||||||
.inSingletonScope();
|
|
||||||
container
|
|
||||||
.bind(TOKENS.StatsModeReader)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date) => new StatsModeReader(database, clock),
|
|
||||||
[TOKENS.Database, TOKENS.Clock]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
container
|
|
||||||
.bind(TOKENS.InfoService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, statsMode: StatsModeReader) =>
|
|
||||||
new InfoService(database, {
|
|
||||||
edition: options.edition,
|
|
||||||
version: options.version,
|
|
||||||
features: () => container.get<FeatureKey[]>(TOKENS.Features),
|
|
||||||
limits: rankedSortLimits(config.lists.totalMaxRangeMs),
|
|
||||||
license: () => container.get<() => InfoLicense | undefined>(TOKENS.LicenseInfo)(),
|
|
||||||
openapiDocsEnabled: docsEnabled(env),
|
|
||||||
statsMode
|
|
||||||
}),
|
|
||||||
[TOKENS.Database, TOKENS.StatsModeReader]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
container
|
|
||||||
.bind(TOKENS.AuthService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(key: Uint8Array, sessions: SessionRepository, clock: () => Date) =>
|
|
||||||
new AuthService({
|
|
||||||
username: config.admin.username,
|
|
||||||
password: config.admin.password,
|
|
||||||
edition: options.edition,
|
|
||||||
key,
|
|
||||||
refreshTokenTtlSeconds: config.session.refreshTokenTtlSeconds,
|
|
||||||
sessions,
|
|
||||||
logger,
|
|
||||||
now: clock
|
|
||||||
}),
|
|
||||||
[TOKENS.SigningKey, TOKENS.SessionRepository, TOKENS.Clock]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.WriterFreshness)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date) => new WriterFreshness(database, clock),
|
|
||||||
[TOKENS.Database, TOKENS.Clock]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.RoomsService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date, freshness: WriterFreshness) =>
|
|
||||||
new RoomsService(database, {
|
|
||||||
totalMaxRangeMs: config.lists.totalMaxRangeMs,
|
|
||||||
freshness,
|
|
||||||
now: clock
|
|
||||||
}),
|
|
||||||
[TOKENS.Database, TOKENS.Clock, TOKENS.WriterFreshness]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.ParticipantsService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date, freshness: WriterFreshness) =>
|
|
||||||
new ParticipantsService(database, {
|
|
||||||
totalMaxRangeMs: config.lists.totalMaxRangeMs,
|
|
||||||
freshness,
|
|
||||||
now: clock
|
|
||||||
}),
|
|
||||||
[TOKENS.Database, TOKENS.Clock, TOKENS.WriterFreshness]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.EgressService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date) =>
|
|
||||||
new EgressService(database, { totalMaxRangeMs: config.lists.totalMaxRangeMs, now: clock }),
|
|
||||||
[TOKENS.Database, TOKENS.Clock]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.IngressService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date) =>
|
|
||||||
new IngressService(database, { totalMaxRangeMs: config.lists.totalMaxRangeMs, now: clock }),
|
|
||||||
[TOKENS.Database, TOKENS.Clock]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.AnalyticsFeatureGate)
|
|
||||||
.toResolvedValue(() => new AnalyticsFeatureGate(() => container.get<FeatureKey[]>(TOKENS.Features)))
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container
|
|
||||||
.bind(TOKENS.AnalyticsService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date, gate: AnalyticsFeatureGate, catalogue: AnalyticsCatalogue) =>
|
|
||||||
new AnalyticsService(database, {
|
|
||||||
now: clock,
|
|
||||||
gate,
|
|
||||||
catalogue,
|
|
||||||
cache: new AnalyticsCache(config.analytics.cacheTtlSeconds * 1000, clock)
|
|
||||||
}),
|
|
||||||
[TOKENS.Database, TOKENS.Clock, TOKENS.AnalyticsFeatureGate, TOKENS.AnalyticsCatalogue]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
container.bind<OverviewExtension>(TOKENS.OverviewExtension).toConstantValue(() => ({}));
|
|
||||||
container
|
|
||||||
.bind(TOKENS.OverviewService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date, extension: OverviewExtension) =>
|
|
||||||
new OverviewService(database, { now: clock, extension }),
|
|
||||||
[TOKENS.Database, TOKENS.Clock, TOKENS.OverviewExtension]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
container
|
|
||||||
.bind(TOKENS.StatusService)
|
|
||||||
.toResolvedValue(
|
|
||||||
(database: Db, clock: () => Date, info: InfoService) => new StatusService(database, { info, now: clock }),
|
|
||||||
[TOKENS.Database, TOKENS.Clock, TOKENS.InfoService]
|
|
||||||
)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
config.analytics.cacheTtlSeconds > 0
|
|
||||||
? `Analytics cache: completed buckets kept for ${String(config.analytics.cacheTtlSeconds)} s`
|
|
||||||
: 'Analytics cache disabled (DASHBOARD_ANALYTICS_CACHE_TTL=0)'
|
|
||||||
);
|
|
||||||
|
|
||||||
const registry = container.get<RouteRegistry>(TOKENS.RouteRegistry);
|
|
||||||
const now = () => container.get<() => Date>(TOKENS.Clock)();
|
|
||||||
|
|
||||||
registerProbes(registry, options.version, db);
|
|
||||||
registerAuthRoutes(
|
|
||||||
registry,
|
|
||||||
() => container.get<AuthService>(TOKENS.AuthService),
|
|
||||||
() => ({
|
|
||||||
basePath: container.get<string>(TOKENS.BasePath),
|
|
||||||
ttlSeconds: config.session.refreshTokenTtlSeconds,
|
|
||||||
secure: config.session.cookieSecure
|
|
||||||
})
|
|
||||||
);
|
|
||||||
registerInfoRoute(registry, () => container.get<InfoService>(TOKENS.InfoService));
|
|
||||||
registerRoomsRoutes(registry, () => container.get<RoomsService>(TOKENS.RoomsService), now);
|
|
||||||
registerParticipantsRoutes(registry, () => container.get<ParticipantsService>(TOKENS.ParticipantsService), now);
|
|
||||||
registerTracksRoutes(registry);
|
|
||||||
registerEgressRoutes(registry, () => container.get<EgressService>(TOKENS.EgressService), now);
|
|
||||||
registerIngressRoutes(registry, () => container.get<IngressService>(TOKENS.IngressService), now);
|
|
||||||
registerAnalyticsRoutes(registry, () => container.get<AnalyticsService>(TOKENS.AnalyticsService), now);
|
|
||||||
registerOverviewRoutes(registry, () => container.get<OverviewService>(TOKENS.OverviewService));
|
|
||||||
registerStatusRoutes(registry, () => container.get<StatusService>(TOKENS.StatusService));
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue