backend phase 14 & frontend phase 7
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Reviews.Queries.GetReviewModerationQueue;
|
||||
using Baya.Application.Models.Common;
|
||||
using Baya.Application.Models.Reviews;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>Admin review console: the moderation queue (defaults to <c>pending_moderation</c>), with any linked
|
||||
/// low-rating alert id. Support alerts themselves stay internal — only their id is surfaced here for triage.</summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/admin/reviews")]
|
||||
[Authorize(ConstantPolicies.DynamicPermission)]
|
||||
[Display(Description = "Admin review moderation queue")]
|
||||
public sealed class AdminReviewsController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpGet("moderation_queue")]
|
||||
[ProducesOkApiResponseType<PagedResult<ModerationQueueItemDto>>]
|
||||
public async Task<IActionResult> ModerationQueue([FromQuery] GetReviewModerationQueueQuery query, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(query, cancellationToken));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Reviews.Commands.SubmitReview;
|
||||
using Baya.Application.Models.Reviews;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// The customer submits the one allowed review for a completed booking. Ownership, the completed/closed
|
||||
/// eligibility gate, and the 1:1 rule are enforced in the handler; the booking id comes from the route.
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/bookings")]
|
||||
[Authorize]
|
||||
[Display(Description = "Submit the one review for a completed booking (customer)")]
|
||||
public sealed class BookingReviewsController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("{bookingId}/review")]
|
||||
[ProducesOkApiResponseType<SubmitReviewResult>]
|
||||
public async Task<IActionResult> Review(long bookingId, SubmitReviewBody body, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(
|
||||
new SubmitReviewCommand(bookingId, body.Rating, body.Body, body.TagCodes), cancellationToken));
|
||||
|
||||
/// <summary>The review body (the booking id comes from the route).</summary>
|
||||
public record SubmitReviewBody(int Rating, string? Body, IReadOnlyList<string>? TagCodes);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Reviews.Queries.GetTagAggregates;
|
||||
using Baya.Application.Features.Reviews.Queries.ListReviewsForNurse;
|
||||
using Baya.Application.Features.Verification.Queries.GetTrustBadge;
|
||||
using Baya.Application.Models.Reviews;
|
||||
using Baya.Application.Models.Verification;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
@@ -14,7 +17,7 @@ namespace Baya.Web.Api.Controllers.V1;
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[AllowAnonymous]
|
||||
[Display(Description = "Public nurse read surface (the verified trust badge)")]
|
||||
[Display(Description = "Public nurse read surface (the verified trust badge + published reviews)")]
|
||||
public sealed class NursesController(ISender sender) : BaseController
|
||||
{
|
||||
// Public: the verified badge exposes credential *types* held, never the encrypted numbers.
|
||||
@@ -22,4 +25,16 @@ public sealed class NursesController(ISender sender) : BaseController
|
||||
[ProducesOkApiResponseType<TrustBadgeDto>]
|
||||
public async Task<IActionResult> TrustBadge(long nurseId, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new GetVerifiedTrustBadgeQuery(nurseId), cancellationToken));
|
||||
|
||||
// Public: published reviews only (the publish gate is enforced in the query) + the cached rating aggregate.
|
||||
[HttpGet("{nurseProfileId}/reviews")]
|
||||
[ProducesOkApiResponseType<NurseReviewsResult>]
|
||||
public async Task<IActionResult> Reviews(long nurseProfileId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default)
|
||||
=> OperationResult(await sender.Send(new ListReviewsForNurseQuery(nurseProfileId, page, pageSize), cancellationToken));
|
||||
|
||||
// Public: the per-nurse tag rollup ("% punctual", …) over published reviews.
|
||||
[HttpGet("{nurseProfileId}/review_tags")]
|
||||
[ProducesOkApiResponseType<NurseTagAggregatesResult>]
|
||||
public async Task<IActionResult> ReviewTags(long nurseProfileId, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new GetTagAggregatesQuery(nurseProfileId), cancellationToken));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.PatientCareRecords.Commands.WritePatientCareRecord;
|
||||
using Baya.Application.Features.PatientCareRecords.Queries.GetPatientHistory;
|
||||
using Baya.Application.Models.Common;
|
||||
using Baya.Application.Models.Reviews;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// Patient-scoped clinical care records. Writing is nurse-only (with a confirmed booking for that patient);
|
||||
/// reading is restricted to the owning customer, a nurse with a confirmed booking, or admin — the strict
|
||||
/// clinical access rule is enforced in the handler (not just this route policy). Clinical bodies are encrypted
|
||||
/// at rest and decrypted only after the access check passes.
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/patients")]
|
||||
[Authorize]
|
||||
[Display(Description = "Patient-scoped, encrypted clinical care records (write: nurse; read: owner/nurse/admin)")]
|
||||
public sealed class PatientCareRecordsController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("{patientId}/care_records")]
|
||||
[ProducesOkApiResponseType<WriteCareRecordResult>]
|
||||
public async Task<IActionResult> Write(long patientId, WriteCareRecordBody body, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(
|
||||
new WritePatientCareRecordCommand(patientId, body.BookingId, body.Body), cancellationToken));
|
||||
|
||||
[HttpGet("{patientId}/care_records")]
|
||||
[ProducesOkApiResponseType<PagedResult<CareRecordDto>>]
|
||||
public async Task<IActionResult> History(long patientId, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, CancellationToken cancellationToken = default)
|
||||
=> OperationResult(await sender.Send(new GetPatientHistoryQuery(patientId, page, pageSize), cancellationToken));
|
||||
|
||||
/// <summary>The care-record body (the patient id comes from the route).</summary>
|
||||
public record WriteCareRecordBody(long? BookingId, string Body);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Asp.Versioning;
|
||||
using Baya.Application.Features.Reviews.Commands.AttachReviewTags;
|
||||
using Baya.Application.Features.Reviews.Commands.ModerateReview;
|
||||
using Baya.Application.Models.Reviews;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Baya.WebFramework.Attributes;
|
||||
using Baya.WebFramework.BaseController;
|
||||
using Mediator;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Baya.Web.Api.Controllers.V1;
|
||||
|
||||
/// <summary>
|
||||
/// Review write surface. Tagging a review is for its author (or a moderator, enforced in the handler); the
|
||||
/// moderation transition is admin/moderator-only (the narrower <see cref="ConstantPolicies.DynamicPermission"/>
|
||||
/// policy overrides the controller-level authorize).
|
||||
/// </summary>
|
||||
[ApiVersion("1")]
|
||||
[ApiController]
|
||||
[Route("api/v{version:apiVersion}/reviews")]
|
||||
[Authorize]
|
||||
[Display(Description = "Review tagging (owner/moderator) and moderation transitions (admin)")]
|
||||
public sealed class ReviewsController(ISender sender) : BaseController
|
||||
{
|
||||
[HttpPost("{reviewId}/tags")]
|
||||
[ProducesOkApiResponseType<ReviewTagsResult>]
|
||||
public async Task<IActionResult> Tags(long reviewId, AttachReviewTagsBody body, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new AttachReviewTagsCommand(reviewId, body.TagCodes), cancellationToken));
|
||||
|
||||
[HttpPatch("{reviewId}/status")]
|
||||
[Authorize(ConstantPolicies.DynamicPermission)]
|
||||
[ProducesOkApiResponseType<ModerateReviewResult>]
|
||||
public async Task<IActionResult> Status(long reviewId, ModerateReviewBody body, CancellationToken cancellationToken)
|
||||
=> OperationResult(await sender.Send(new ModerateReviewCommand(reviewId, body.Action, body.Reason), cancellationToken));
|
||||
|
||||
/// <summary>Tag-attach body (the review id comes from the route).</summary>
|
||||
public record AttachReviewTagsBody(IReadOnlyList<string> TagCodes);
|
||||
|
||||
/// <summary>Moderation body (the review id comes from the route): <c>publish</c>|<c>hide</c>|<c>reject</c>|<c>unpublish</c>.</summary>
|
||||
public record ModerateReviewBody(string Action, string? Reason);
|
||||
}
|
||||
Reference in New Issue
Block a user