-
Notifications
You must be signed in to change notification settings - Fork 5
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)
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:
Routes call
internal static string[] AuthenticationSchemes = [ BasicAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.LocalAuthenticationScheme, JwtBearerDefaults.ExternalAuthenticationScheme ];
.RequireAuthorization(AuthenticationSchemes)to opt every scheme in. - A shared
AuthorizeAsyncthat resolves the calling user viaICredentialsServiceand throwsAuthorizationExceptionwhen the request can't be served. -
IsServerReadyAsync— returns503 Service UnavailableuntilICometHasStartedServicehas flipped totrue(set byCometStartUpService, which runs the migrations on boot). -
CreateAndRegisterCometTask— registers the request withICometTaskServiceso a long-running POST can be polled at/tasks/{taskId}. - Hooks into
IBackgroundThingsMessageProducerto broadcast change messages once the operation has committed.
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.
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:
-
EnumerableOfGuidroute constraint (Modules/Constraints/EnumerableOfGuidRouteConstraint.cs, registered inStartup.cs) lets a client fetch many engineering model headers withEngineeringModel/{guid1,guid2,…}in a single round-trip. -
Two extra query parameters are accepted:
classKindandcategoryfor catalog-style filtering, andcherryPickto ask for a tailored slice of an iteration (handled byICherryPickService). - 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, andIContainmentServicein addition to the SiteDirectory dependency set.
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.
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 /loginvalidates a username/password againstIAuthenticationPersonAuthenticator(which in turn delegates to whicheverCDP4Authenticationplugin is enabled — see Authentication and Authorization) and returns a JWT bearer + refresh token fromIJwtTokenService. -
POST /refreshexchanges a refresh token for a new bearer + refresh pair. -
GET /auth/schemesreturns the schemes enabled inappsettings.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 theAuthorityandClientIdadvertised byappsettings.jsonso an OIDC client can configure itself. -
POST /logoutis 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.
| 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).
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.
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.
- 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/. - If it touches Annex C model data, derive from
ApiBaseand reuseAuthorizeAsync,IsServerReadyAsyncandCreateAndRegisterCometTask. If it is purely operational (health, tasks, auth), derive fromCarterModuledirectly. - Register routes in
AddRouteswithapp.MapGet/Post/...and chain.RequireAuthorization(AuthenticationSchemes)whenever the endpoint requires an authenticated user. - Keep the handler thin: open a transaction with
ICdp4TransactionManager, dispatch intoServices(and through them into the auto-generated DAOs and the SideEffects pipeline), then format the response with the existingHttpResponseExtensions. - The module is picked up automatically because
Startup.csscans the assembly for everyCarterModulesubtype.
For the layer the modules call into, continue with CometServer Services.
copyright @ Starion Group S.A.