Files
baya-monorepo/server/src/API/Baya.Web.Api/Controllers/V1/NurseVerificationController.cs
T
hamid 1c266523bc backend phase 6: nurse verification & credentials (mocked vendors)
The trust engine. New `verif` schema (5 tables) + a data-driven verification
pipeline: steps are rows (6 seeded step-types), not a code enum.

- nurse_verifications.status is the single source of verification truth;
  nurse_profiles.is_verified is flipped ONLY inside the finalize transaction
  (VerificationAggregator: tracked verification + tracked profile -> one commit)
  and reversed on suspension/expiry — no in-between state.
- is_automated snapshotted onto each step at submit; steps seeded from active
  required step-types; automated runs (identity-KYC, Shahkar, IBAN ownership)
  find their step by code.
- users.national_id populated only on identity-KYC pass; Shahkar + IBAN owner
  compare against it (money-mule guard); shared-SIM -> shared_sim support alert.
- Documents are metadata-only behind signed URLs; credential_number encrypted
  and never serialized; public trust badge exposes credential TYPES, not numbers;
  holder-name cross-checked against the verified identity before recording.
- Admin-triggered credential-expiry scan reverts lapsed steps, re-gates
  bookability, raises a verification_expired alert + verification_expiry_prompt
  notification (scheduled cron deferred; config key
  verification_expiry_scan_cadence_hours).

Three new mock vendor seams (IShahkarVerifier / IIdentityKycProvider /
ICredentialVerifier) behind DI; reuses b3 IBankAccountOwnershipVerifier and
b0 IObjectStorage/IFieldEncryptor. 15 endpoints across 4 controllers.

Two migrations (tables + step-type seed). 154 tests pass, zero new warnings.
Contract dev/contracts/domains/verification.md + swagger snapshot refreshed;
handoff/report/mocks-registry updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 14:39:32 +03:30

61 lines
3.1 KiB
C#

using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Verification.Commands.ConfirmDocumentUpload;
using Baya.Application.Features.Verification.Commands.RequestDocumentUploadUrl;
using Baya.Application.Features.Verification.Commands.RunBankAccountVerification;
using Baya.Application.Features.Verification.Commands.RunIdentityKyc;
using Baya.Application.Features.Verification.Commands.RunShahkarMatch;
using Baya.Application.Features.Verification.Commands.SubmitVerification;
using Baya.Application.Features.Verification.Queries.GetStatus;
using Baya.Application.Models.Verification;
using Baya.WebFramework.Attributes;
using Baya.WebFramework.BaseController;
using Mediator;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Baya.Web.Api.Controllers.V1;
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[Authorize]
[Display(Description = "The signed-in nurse's verification pipeline (checklist, uploads, automated runs)")]
public sealed class NurseVerificationController(ISender sender) : BaseController
{
[HttpPost("[action]")]
[ProducesOkApiResponseType<VerificationStatusDto>]
public async Task<IActionResult> Submit(CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new SubmitNurseVerificationCommand(), cancellationToken));
[HttpGet]
[ProducesOkApiResponseType<VerificationStatusDto>]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetNurseVerificationStatusQuery(), cancellationToken));
[HttpPost("steps/{stepId}/[action]")]
[ProducesOkApiResponseType<UploadUrlResult>]
public async Task<IActionResult> UploadUrl(long stepId, RequestDocumentUploadUrlCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { StepId = stepId }, cancellationToken));
[HttpPost("steps/{stepId}/documents")]
[ProducesOkApiResponseType<DocumentConfirmedResult>]
public async Task<IActionResult> ConfirmDocument(long stepId, ConfirmDocumentUploadCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { StepId = stepId }, cancellationToken));
[HttpPost("steps/identity_kyc/run")]
[ProducesOkApiResponseType<RunStepResult>]
public async Task<IActionResult> RunIdentityKyc(RunIdentityKycCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command, cancellationToken));
[HttpPost("steps/shahkar_match/run")]
[ProducesOkApiResponseType<RunStepResult>]
public async Task<IActionResult> RunShahkarMatch(CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new RunShahkarMatchCommand(), cancellationToken));
[HttpPost("steps/bank_account_verification/run")]
[ProducesOkApiResponseType<RunStepResult>]
public async Task<IActionResult> RunBankAccountVerification(CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new RunBankAccountVerificationCommand(), cancellationToken));
}