Skip to content

CometServer Modules

samatstarion edited this page May 4, 2026 · 1 revision

CometServer Modules

Sub-page of CometServer. Read that first for the overall picture.

Routing in CometServer is done with Carter, not MVC controllers. Each route group lives in a class that derives from Carter.CarterModule and overrides AddRoutes(IEndpointRouteBuilder app) to register its endpoints. Carter modules are auto-discovered and registered with Autofac in Startup.cs, then mounted in the pipeline by app.UseEndpoints(b => b.MapCarter()) in Startup.cs.

The folder layout under CometServer/Modules mirrors the responsibility of each module:

Modules/
├── 10-25/             // ECSS-E-TM-10-25 Annex C endpoints (the API documented in [[REST API - ICD|ICD]])
├── Authentication/    // /login, /logout, /refresh, /username, /auth/schemes
├── Health/            // /health/startup, /healthz, /ready, /metrics
├── Tasks/             // /tasks, /tasks/{taskId}
├── Root/              // GET / landing page (HTML)
├── CarterExtensions/  // shared HttpResponse extensions used by every module
└── Constraints/       // custom route constraints (e.g. EnumerableOfGuid)

ApiBase

All Annex C endpoints derive from CometServer.Modules.ApiBase (CometServer/Modules/10-25/ApiBase.cs). It is itself an abstract CarterModule and provides:

  • The list of accepted authentication schemes:
    internal static string[] AuthenticationSchemes =
    [
        BasicAuthenticationDefaults.AuthenticationScheme,
        JwtBearerDefaults.LocalAuthenticationScheme,
        JwtBearerDefaults.ExternalAuthenticationScheme
    ];
    Routes call .RequireAuthorization(AuthenticationSchemes) to opt every scheme in.
  • A shared AuthorizeAsync that resolves the calling user via ICredentialsService and throws AuthorizationException when the request can't be served.
  • IsServerReadyAsync — returns 503 Service Unavailable until ICometHasStartedService has flipped to true (set by CometStartUpService, which runs the migrations on boot).
  • CreateAndRegisterCometTask — registers the request with ICometTaskService so a long-running POST can be polled at /tasks/{taskId}.
  • Hooks into IBackgroundThingsMessageProducer to broadcast change messages once the operation has committed.

Annex C modules (Modules/10-25)

SiteDirectoryApi

Verbatim from Modules/10-25/SiteDirectoryApi.cs:

public override void AddRoutes(IEndpointRouteBuilder app)
{
    app.MapGet ("SiteDirectory",                async (...) => { ... }).RequireAuthorization(AuthenticationSchemes);
    app.MapGet ("SiteDirectory/{*path}",        async (...) => { ... }).RequireAuthorization(AuthenticationSchemes);
    app.MapPost("SiteDirectory/{iid:guid}",     async (...) => { ... }).RequireAuthorization(AuthenticationSchemes);
}

The {*path} catch-all is what implements deep paths such as SiteDirectory/{iid}/model/{iid}/iterationSetup/{iid}IRequestUtils parses the segment list against the metadata of the Thing types. The supported query parameters are listed at the top of the file (SupportedGetQueryParameters): extent, includeReferenceData, includeAllContainers, includeFileData, revisionNumber, revisionFrom, revisionTo. POST is single-part only; multi-part requests with embedded files are rejected because SiteDirectory does not host file binaries.

EngineeringModelApi

Verbatim from Modules/10-25/EngineeringModelApi.cs:

public override void AddRoutes(IEndpointRouteBuilder app)
{
    app.MapGet ("EngineeringModel/{ids:EnumerableOfGuid}", this.GetEngineeringModelsShallowAsync).RequireAuthorization(AuthenticationSchemes);
    app.MapGet ("EngineeringModel/*",                      this.GetEngineeringModelsShallowAsync).RequireAuthorization(AuthenticationSchemes);
    app.MapGet ("EngineeringModel/{*path}",                async (...) => { ... }).RequireAuthorization(AuthenticationSchemes);
    app.MapPost("EngineeringModel/{engineeringModelIid:guid}/iteration/{iterationIid:guid}", async (...) => { ... }).RequireAuthorization(AuthenticationSchemes);
}

Differences with SiteDirectoryApi worth noting:

  • EnumerableOfGuid route constraint (Modules/Constraints/EnumerableOfGuidRouteConstraint.cs, registered in Startup.cs) lets a client fetch many engineering model headers with EngineeringModel/{guid1,guid2,…} in a single round-trip.
  • Two extra query parameters are accepted: classKind and category for catalog-style filtering, and cherryPick to ask for a tailored slice of an iteration (handled by ICherryPickService).
  • POSTs are scoped to an iteration, not the engineering model. Multi-part requests are accepted here so file binaries can be uploaded alongside the JSON operation.
  • The handler injects IFileBinaryService, IFileArchiveService, IObfuscationService, ICherryPickService, and IContainmentService in addition to the SiteDirectory dependency set.

ExchangeFileImportApi and ExchangeFileExportApi

Modules/10-25/ExchangeFileImportApi.cs registers three administrative POST endpoints:

public override void AddRoutes(IEndpointRouteBuilder app)
{
    app.MapPost("/Data/Exchange", async (...) => await this.SeedDataStoreAsync(...));
    app.MapPost("/Data/Import",   async (...) => await this.ImportDataStoreAsync(...));
    app.MapPost("/Data/Restore",  async (...) => await this.RestoreDatastoreAsync(...));
}
Endpoint Action
POST /Data/Exchange Seed an empty database from a .zip exchange file. Wipes and recreates schemas before reading the archive with IJsonExchangeFileReader.
POST /Data/Import Import an exchange file into a non-empty database, applying any model-level migrations through IMigrationService.
POST /Data/Restore Restore the cdp4serverrestore snapshot into the live cdp4server database. Delegated to IDataStoreController; the procedure is described in Restore default admin password.

ExchangeFileExportApi exposes the symmetrical POST /export (Modules/10-25/ExchangeFileExportApi.cs:111-113), which packages the requested set of engineering models into a zip via IJsonExchangeFileWriter + IZipArchiveWriter.

Authentication module (Modules/Authentication)

AuthenticationModule (Modules/Authentication/AuthenticationModule.cs):

public override void AddRoutes(IEndpointRouteBuilder app)
{
    app.MapPost("/login",   async (LoginUser loginUser, ..., IAuthenticationPersonAuthenticator authenticator, IJwtTokenService jwtTokenService, ...) => { ... });
    app.MapPost("/logout",  (HttpRequest req, HttpResponse res) => { throw new NotImplementedException(); }).RequireAuthorization(ApiBase.AuthenticationSchemes);
    app.MapPost("/refresh", RefreshToken);
    app.MapGet ("/auth/schemes", ProvideEnabledAuthenticationScheme);
}
  • POST /login validates a username/password against IAuthenticationPersonAuthenticator (which in turn delegates to whichever CDP4Authentication plugin is enabled — see Authentication and Authorization) and returns a JWT bearer + refresh token from IJwtTokenService.
  • POST /refresh exchanges a refresh token for a new bearer + refresh pair.
  • GET /auth/schemes returns the schemes enabled in appsettings.json (Basic, Local JWT, External JWT). External clients call this first to find out how to authenticate. When the external scheme is enabled, the response also includes the Authority and ClientId advertised by appsettings.json so an OIDC client can configure itself.
  • POST /logout is intentionally unimplemented — JWT tokens are stateless and the server does not maintain server-side session state.

UsernameModule.cs adds GET /username, a small helper that returns the resolved Person.ShortName from ICredentialsService.Credentials so a UI can display the active user without round-tripping the SiteDirectory.

Health, Root and Tasks

Module Routes Purpose
HealthModule (Modules/Health/HealthModule.cs) GET /health/startup, GET /healthz, GET /ready Container-orchestration probes. startup becomes 200 once CometStartUpService finishes migrations; healthz covers the running process; ready adds a database round-trip via IDataStoreConnectionChecker.
RootModule (Modules/Root/RootModule.cs) GET / Serves the embedded Resources/RootPage.html landing page (resolved through IResourceLoader).
CometTasksModule (Modules/Tasks/CometTasksModule.cs) GET /tasks, GET /tasks/{taskId}, POST /tasks/{taskId} Long-running operation bookkeeping. The GET endpoints filter by Person.Iid from ICredentialsService; POST cancels a running task. Tasks are registered by ApiBase.CreateAndRegisterCometTask whenever a POST takes long enough to stream a 202 instead of a 200.

Prometheus metrics are not added by a Carter module — they are wired directly in Startup.cs via app.UseMetricServer(); app.UseHttpMetrics();. The Hangfire dashboard is mounted at /hangfire (Startup.cs).

Custom route constraints

Modules/Constraints/EnumerableOfGuidRouteConstraint.cs lets a comma-separated list of GUIDs match a single route segment. It is registered in Startup.cs:156:

services.AddRouting(options =>
    options.ConstraintMap.Add("EnumerableOfGuid", typeof(EnumerableOfGuidRouteConstraint)));

Used by EngineeringModel/{ids:EnumerableOfGuid} to allow EngineeringModel/{guid1,guid2,guid3} in one request.

Module response helpers

Modules/CarterExtensions/HttpResponseExtensions.cs defines the small vocabulary of response shaping used everywhere — UpdateWithNotAutherizedSettings, UpdateWithNotAuthenticatedSettings, UpdateWithNotBearerAuthenticatedSettings. Every module uses the same helpers so behaviour stays consistent across endpoints.

Adding a new endpoint

  1. Decide whether the endpoint belongs in an existing Carter module or warrants a new one. New top-level concerns get their own folder under Modules/.
  2. If it touches Annex C model data, derive from ApiBase and reuse AuthorizeAsync, IsServerReadyAsync and CreateAndRegisterCometTask. If it is purely operational (health, tasks, auth), derive from CarterModule directly.
  3. Register routes in AddRoutes with app.MapGet/Post/... and chain .RequireAuthorization(AuthenticationSchemes) whenever the endpoint requires an authenticated user.
  4. Keep the handler thin: open a transaction with ICdp4TransactionManager, dispatch into Services (and through them into the auto-generated DAOs and the SideEffects pipeline), then format the response with the existing HttpResponseExtensions.
  5. The module is picked up automatically because Startup.cs scans the assembly for every CarterModule subtype.

For the layer the modules call into, continue with CometServer Services.

Clone this wiki locally