backend phase 15 & frontend phase 8

This commit is contained in:
hamid
2026-07-10 03:22:29 +03:30
parent 93cc5ecb98
commit cd6c2591a6
154 changed files with 15335 additions and 37 deletions
@@ -0,0 +1,60 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.PartnerCenters.Commands.CreatePartnerCenter;
using Baya.Application.Features.PartnerCenters.Commands.SponsorNurse;
using Baya.Application.Features.PartnerCenters.Commands.UpdatePartnerCenter;
using Baya.Application.Features.PartnerCenters.Commands.VerifyPartnerCenter;
using Baya.Application.Features.PartnerCenters.Queries.GetPartnerCenterById;
using Baya.Application.Features.PartnerCenters.Queries.ListPartnerCenters;
using Baya.Application.Models.Common;
using Baya.Application.Models.PartnerCenters;
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 partner-center console (admin/super_admin). Creates/updates/verifies licensed centers and sponsors
/// nurses. The settlement IBAN is encrypted at rest and returned <b>masked</b> (last 4). Internal-only, audited.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/admin/partner-centers")]
[Authorize(ConstantPolicies.DynamicPermission)]
[Display(Description = "Admin partner-center management (licensed sponsor / merchant-of-record)")]
public sealed class AdminPartnerCentersController(ISender sender) : BaseController
{
[HttpPost]
[ProducesOkApiResponseType<PartnerCenterDetailDto>]
public async Task<IActionResult> Create(CreatePartnerCenterCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command, cancellationToken));
[HttpPatch("{id}")]
[ProducesOkApiResponseType<PartnerCenterDetailDto>]
public async Task<IActionResult> Update(long id, UpdatePartnerCenterCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { Id = id }, cancellationToken));
[HttpPost("{id}/verify")]
[ProducesOkApiResponseType<PartnerCenterDetailDto>]
public async Task<IActionResult> Verify(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new VerifyPartnerCenterCommand(id), cancellationToken));
[HttpPost("{id}/sponsor-nurse")]
[ProducesOkApiResponseType]
public async Task<IActionResult> SponsorNurse(long id, SponsorNurseCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { CenterId = id }, cancellationToken));
[HttpGet]
[ProducesOkApiResponseType<PagedResult<PartnerCenterListItemDto>>]
public async Task<IActionResult> List([FromQuery] ListPartnerCentersQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
[HttpGet("{id}")]
[ProducesOkApiResponseType<PartnerCenterDetailDto>]
public async Task<IActionResult> GetById(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetPartnerCenterByIdQuery(id), cancellationToken));
}
@@ -0,0 +1,35 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Messaging.Queries.GetTicketThread;
using Baya.Application.Features.Messaging.Queries.ListTicketsForAdmin;
using Baya.Application.Models.Common;
using Baya.Application.Models.Messaging;
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 ticket console (support/admin): the global queue and the <b>admin</b> thread view (internal
/// notes included). Internal-only, RBAC-gated, audited.</summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/admin/tickets")]
[Authorize(ConstantPolicies.DynamicPermission)]
[Display(Description = "Admin ticket queue + full (internal-inclusive) thread view")]
public sealed class AdminTicketsController(ISender sender) : BaseController
{
[HttpGet]
[ProducesOkApiResponseType<PagedResult<TicketSummaryDto>>]
public async Task<IActionResult> List([FromQuery] ListTicketsForAdminQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
// Admin view — internal notes ARE returned.
[HttpGet("{id}")]
[ProducesOkApiResponseType<TicketThreadDto>]
public async Task<IActionResult> Thread(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetTicketThreadQuery(id, AsAdmin: true), cancellationToken));
}
@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.PartnerCenters.Queries.GetCenterDashboard;
using Baya.Application.Models.PartnerCenters;
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 partner-center portal — scoped to the center's own dashboard account (or staff). Surfaces the
/// sponsored nurses + booking/invoice counts + the masked settlement summary.</summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/centers")]
[Authorize]
[Display(Description = "Partner-center dashboard (center account scoped)")]
public sealed class CentersController(ISender sender) : BaseController
{
[HttpGet("{id}/dashboard")]
[ProducesOkApiResponseType<CenterDashboardDto>]
public async Task<IActionResult> Dashboard(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetCenterDashboardQuery(id), cancellationToken));
}
@@ -0,0 +1,27 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.PartnerCenters.Queries.GetCenterForBooking;
using Baya.Application.Models.PartnerCenters;
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>Internal/admin resolver: which center legally covers a booking (invoice issuer + settlement target).
/// The single merchant-of-record decision — invoices and settlement follow <c>partner_centers</c>.</summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/internal/bookings")]
[Authorize(ConstantPolicies.DynamicPermission)]
[Display(Description = "Internal: resolve the invoice issuer / settlement center for a booking")]
public sealed class InternalCentersController(ISender sender) : BaseController
{
[HttpGet("{bookingId}/center")]
[ProducesOkApiResponseType<CenterForBookingDto>]
public async Task<IActionResult> CenterForBooking(long bookingId, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetCenterForBookingQuery(bookingId), cancellationToken));
}
@@ -0,0 +1,78 @@
using System.ComponentModel.DataAnnotations;
using Asp.Versioning;
using Baya.Application.Features.Messaging.Commands.AddParticipant;
using Baya.Application.Features.Messaging.Commands.CloseTicket;
using Baya.Application.Features.Messaging.Commands.LogEmergencyTicket;
using Baya.Application.Features.Messaging.Commands.OpenTicket;
using Baya.Application.Features.Messaging.Commands.PostMessage;
using Baya.Application.Features.Messaging.Commands.RemoveParticipant;
using Baya.Application.Features.Messaging.Commands.ReopenTicket;
using Baya.Application.Features.Messaging.Queries.GetTicketThread;
using Baya.Application.Features.Messaging.Queries.ListMyTickets;
using Baya.Application.Models.Common;
using Baya.Application.Models.Messaging;
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 ticket system — the only sanctioned post-booking communication channel. All reads/writes are
/// participation-gated (staff may attach to any ticket). The user thread view never contains an internal note.
/// </summary>
[ApiVersion("1")]
[ApiController]
[Route("api/v{version:apiVersion}/tickets")]
[Authorize]
[Display(Description = "Post-booking ticket communication (participant-scoped)")]
public sealed class TicketsController(ISender sender) : BaseController
{
[HttpPost]
[ProducesOkApiResponseType<OpenTicketResult>]
public async Task<IActionResult> Open(OpenTicketCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command, cancellationToken));
[HttpPost("emergency")]
[ProducesOkApiResponseType<OpenTicketResult>]
public async Task<IActionResult> Emergency(LogEmergencyTicketCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command, cancellationToken));
[HttpPost("{id}/messages")]
[ProducesOkApiResponseType<PostMessageResult>]
public async Task<IActionResult> PostMessage(long id, PostMessageCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { TicketId = id }, cancellationToken));
[HttpPost("{id}/participants")]
[ProducesOkApiResponseType]
public async Task<IActionResult> AddParticipant(long id, AddParticipantCommand command, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(command with { TicketId = id }, cancellationToken));
[HttpDelete("{id}/participants/{userId}")]
[ProducesOkApiResponseType]
public async Task<IActionResult> RemoveParticipant(long id, int userId, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new RemoveParticipantCommand(id, userId), cancellationToken));
[HttpPost("{id}/close")]
[ProducesOkApiResponseType]
public async Task<IActionResult> Close(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new CloseTicketCommand(id), cancellationToken));
[HttpPost("{id}/reopen")]
[ProducesOkApiResponseType]
public async Task<IActionResult> Reopen(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new ReopenTicketCommand(id), cancellationToken));
[HttpGet]
[ProducesOkApiResponseType<PagedResult<TicketSummaryDto>>]
public async Task<IActionResult> ListMine([FromQuery] ListMyTicketsQuery query, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(query, cancellationToken));
// User view — internal notes are stripped in the query projection.
[HttpGet("{id}")]
[ProducesOkApiResponseType<TicketThreadDto>]
public async Task<IActionResult> Thread(long id, CancellationToken cancellationToken)
=> OperationResult(await sender.Send(new GetTicketThreadQuery(id, AsAdmin: false), cancellationToken));
}