using EntitlementBLL.Onboarding; using EntitlementDAL.DTOs; using FastEndpoints; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using ErrorResponse = GB5Shared.Resource.Response.ErrorResponse; using static GB5Shared.GB5Constant.Constant; using GB5Shared.Authorization; namespace EntitlementSL.Endpoints.Onboarding; // Internal-staff-only — end-to-end client onboarding: identity creation + // SqlWorkbench-governed database provisioning. Gated via [MenuRights], matching the // Thread 0 §20 / CreateClient.cs precedent (GOODBOOKS_ADMIN has no backing JWT scheme). A // menu-seed migration registering "entonboardclient" as a real MMENU row is a small, separate // follow-up, same as CreateClient's own (§22.2) — not bundled into this pass. Returns once the // database ChangeRequest is submitted — it does NOT wait for the database to actually exist; // poll GetProvisioningJob (existing endpoint, unchanged) for JobStatus/CurrentStep. // MenuCode shortened to fit MMENU.MENUCODE's real NVARCHAR(10) limit (confirmed live 2026-08-06, // tracker §31.13) — "entonboardclient" (17 chars) could never be seeded/matched against the real // schema. This same overflow affects most other [MenuRights] codes registered across this module // in an earlier thread (Thread 0 §20) — flagged as a separate, larger follow-up, not fixed here. [MenuRights("entonbrd", RightOperation.Insert)] public class OnboardClient : BaseEndpoint> { private readonly IClientOnboardingOrchestratorBLL _Orchestrator; private readonly IMenuRightsBLL _MenuRightsBLL; public OnboardClient(IClientOnboardingOrchestratorBLL orchestrator, IMenuRightsBLL menuRightsBLL) { _Orchestrator = orchestrator; _MenuRightsBLL = menuRightsBLL; } public override void Configure() { Post("/Onboarding/OnboardClient"); AllowAnonymous(); } public record Params( [property: FromHeader] string Login, [property: FromBody] OnboardClientRequestDTO OnboardClientRequestDTO ); /// NOT_REQUIRED — a one-time creation action, never cached. protected override string? GetCacheKey(Params req, LoginDTO loginDTO) => null; protected override async Task> ExecuteAsync(Params req, LoginDTO loginDTO, CancellationToken ct) { // Tracker §52 — BYOC/On-Prem purchase gating: this endpoint's own class-level // [MenuRights("entonbrd", ...)] grant lets a staff member create an ordinary SaaS client, // but committing to dedicated/BYOC infrastructure for one is a materially bigger, // rarer action — requires a SEPARATE "entdeploy" grant, checked imperatively here (not // every entonbrd-holder should also hold entdeploy). Demo/Trial can never reach this // check at all — their own internal OnboardClientRequestDTO construction never sets // DeploymentType, so it stays 0/SaaS for them regardless. if (req.OnboardClientRequestDTO.DeploymentType != 0) { var allowed = await _MenuRightsBLL.IsAllowedAsync("entdeploy", RightOperation.Insert, loginDTO, ct); if (!allowed) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, loginDTO, statusCode: 403); } var result = await _Orchestrator.StartOnboardingAsync(req.OnboardClientRequestDTO, ct); return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse(result, CacheKeyLevel.NOT_REQUIRED, loginDTO); } }