mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 09:19:04 +00:00
Adds a managed pricing library so admins can seed/curate reference priced items
directly, instead of the corpus being populated only by ingesting Sent proposals.
v1 PR5.
API:
- PricingLibraryItem entity + migration; /api/pricing-library CRUD (admin), with
GET {id} reachable by internal Lambda callers (admins role via internal key).
- Create/update publish an ADDITIVE library-ingest SQS job {pricingLibraryItemId},
wrapped so a publish failure never rolls back the save.
Lambda (library-ingest):
- Additive event-shape branch: pricingLibraryItemId -> fetch item, format markdown,
upload to pricing-library/{category}/{id}.md, trigger KB sync. The existing
proposalId path is byte-for-byte unchanged. Explicit error when neither id present;
warns when both present.
Web:
- Pricing Library management page (/admin/pricing-library): list / create / edit / delete.
GPT-4.1 cross-review on the event-shape change: no BLOCK (neither/both-id handling
applied). Verified: api 159 tests; web tsc + 26 tests; lambdas ruff + 37 pytest.
96 lines
3.3 KiB
C#
96 lines
3.3 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using ProposalSystem.Application.DTOs;
|
|
using ProposalSystem.Application.Interfaces;
|
|
|
|
namespace ProposalSystem.Api.Controllers;
|
|
|
|
/// <summary>
|
|
/// PR5: Pricing Library CRUD endpoints. Admins seed/manage reusable priced
|
|
/// reference items that feed the Bedrock RAG corpus.
|
|
///
|
|
/// GET {id} is reachable by the internal API key middleware (which assigns
|
|
/// the "admins" role) so the library-ingest Lambda can fetch item details.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/pricing-library")]
|
|
[Authorize]
|
|
public class PricingLibraryController : ControllerBase
|
|
{
|
|
private readonly IPricingLibraryService _pricingLibraryService;
|
|
|
|
public PricingLibraryController(IPricingLibraryService pricingLibraryService)
|
|
{
|
|
_pricingLibraryService = pricingLibraryService;
|
|
}
|
|
|
|
[HttpGet("list")]
|
|
[Authorize(Roles = "admins,sysadmins")]
|
|
[ProducesResponseType(typeof(PagedResponse<PricingLibraryItemResponse>), 200)]
|
|
public async Task<ActionResult<PagedResponse<PricingLibraryItemResponse>>> List(
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 25,
|
|
CancellationToken ct = default)
|
|
{
|
|
var result = await _pricingLibraryService.ListAsync(page, pageSize, ct);
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a single pricing library item. Accessible by admins/sysadmins role,
|
|
/// which includes internal Lambda callers (the internal API key middleware
|
|
/// assigns the "admins" role).
|
|
/// </summary>
|
|
[HttpGet("{id:guid}")]
|
|
[Authorize(Roles = "admins,sysadmins")]
|
|
[ProducesResponseType(typeof(PricingLibraryItemResponse), 200)]
|
|
[ProducesResponseType(404)]
|
|
public async Task<ActionResult<PricingLibraryItemResponse>> GetById(
|
|
Guid id,
|
|
CancellationToken ct)
|
|
{
|
|
var result = await _pricingLibraryService.GetByIdAsync(id, ct);
|
|
if (result == null) return NotFound();
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpPost]
|
|
[Authorize(Roles = "admins,sysadmins")]
|
|
[ProducesResponseType(typeof(PricingLibraryItemResponse), 201)]
|
|
[ProducesResponseType(400)]
|
|
public async Task<ActionResult<PricingLibraryItemResponse>> Create(
|
|
[FromBody] CreatePricingLibraryItemRequest request,
|
|
CancellationToken ct)
|
|
{
|
|
var result = await _pricingLibraryService.CreateAsync(request, ct);
|
|
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
}
|
|
|
|
[HttpPut("{id:guid}")]
|
|
[Authorize(Roles = "admins,sysadmins")]
|
|
[ProducesResponseType(typeof(PricingLibraryItemResponse), 200)]
|
|
[ProducesResponseType(400)]
|
|
[ProducesResponseType(404)]
|
|
public async Task<ActionResult<PricingLibraryItemResponse>> Update(
|
|
Guid id,
|
|
[FromBody] UpdatePricingLibraryItemRequest request,
|
|
CancellationToken ct)
|
|
{
|
|
var result = await _pricingLibraryService.UpdateAsync(id, request, ct);
|
|
if (result == null) return NotFound();
|
|
return Ok(result);
|
|
}
|
|
|
|
[HttpDelete("{id:guid}")]
|
|
[Authorize(Roles = "admins,sysadmins")]
|
|
[ProducesResponseType(204)]
|
|
[ProducesResponseType(404)]
|
|
public async Task<IActionResult> Delete(
|
|
Guid id,
|
|
CancellationToken ct)
|
|
{
|
|
var deleted = await _pricingLibraryService.DeleteAsync(id, ct);
|
|
if (!deleted) return NotFound();
|
|
return NoContent();
|
|
}
|
|
}
|