feat(career): add factual evidence bank
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using System.Text.Json;
|
||||
using JobTrackerApi.Data;
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace JobTrackerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/career/evidence")]
|
||||
[Authorize(AuthenticationSchemes = "local")]
|
||||
public sealed class CareerEvidenceController(JobTrackerContext db, UserManager<ApplicationUser> users) : ControllerBase
|
||||
{
|
||||
public sealed record EvidenceRequest(string? Category, string? Title, string? Statement, IReadOnlyList<string>? Tags, string? SourceType, string? SourceReference, bool? IsVerified);
|
||||
public sealed record EvidenceDto(int Id, string Category, string Title, string Statement, IReadOnlyList<string> Tags, string SourceType, string? SourceReference, bool IsVerified, DateTimeOffset UpdatedAtUtc);
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IReadOnlyList<EvidenceDto>>> List(CancellationToken ct)
|
||||
{
|
||||
var owner = await OwnerAsync();
|
||||
if (owner is null) return Unauthorized();
|
||||
var rows = await db.Set<CareerEvidence>().AsNoTracking().Where(x => x.OwnerUserId == owner).OrderByDescending(x => x.UpdatedAtUtc).ToListAsync(ct);
|
||||
return Ok(rows.Select(ToDto));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<EvidenceDto>> Create(EvidenceRequest request, CancellationToken ct)
|
||||
{
|
||||
var owner = await OwnerAsync();
|
||||
if (owner is null) return Unauthorized();
|
||||
var error = Validate(request);
|
||||
if (error is not null) return BadRequest(error);
|
||||
var row = new CareerEvidence { OwnerUserId = owner };
|
||||
Apply(row, request);
|
||||
db.Add(row);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Ok(ToDto(row));
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
public async Task<ActionResult<EvidenceDto>> Update(int id, EvidenceRequest request, CancellationToken ct)
|
||||
{
|
||||
var owner = await OwnerAsync();
|
||||
if (owner is null) return Unauthorized();
|
||||
var row = await db.Set<CareerEvidence>().FirstOrDefaultAsync(x => x.Id == id && x.OwnerUserId == owner, ct);
|
||||
if (row is null) return NotFound();
|
||||
var error = Validate(request);
|
||||
if (error is not null) return BadRequest(error);
|
||||
Apply(row, request);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return Ok(ToDto(row));
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<IActionResult> Delete(int id, CancellationToken ct)
|
||||
{
|
||||
var owner = await OwnerAsync();
|
||||
if (owner is null) return Unauthorized();
|
||||
var row = await db.Set<CareerEvidence>().FirstOrDefaultAsync(x => x.Id == id && x.OwnerUserId == owner, ct);
|
||||
if (row is null) return NotFound();
|
||||
db.Remove(row);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<string?> OwnerAsync() => (await users.GetUserAsync(User))?.Id;
|
||||
private static string? Validate(EvidenceRequest value) =>
|
||||
string.IsNullOrWhiteSpace(value.Title) ? "Title is required." : string.IsNullOrWhiteSpace(value.Statement) ? "Evidence is required." : null;
|
||||
private static void Apply(CareerEvidence row, EvidenceRequest value)
|
||||
{
|
||||
row.Category = Clean(value.Category, "achievement", 32);
|
||||
row.Title = Clean(value.Title, string.Empty, 255);
|
||||
row.Statement = value.Statement!.Trim();
|
||||
row.TagsJson = JsonSerializer.Serialize((value.Tags ?? []).Select(x => x.Trim()).Where(x => x.Length > 0).Distinct(StringComparer.OrdinalIgnoreCase).Take(30));
|
||||
row.SourceType = Clean(value.SourceType, "manual", 32);
|
||||
row.SourceReference = string.IsNullOrWhiteSpace(value.SourceReference) ? null : value.SourceReference.Trim()[..Math.Min(value.SourceReference.Trim().Length, 500)];
|
||||
row.IsVerified = value.IsVerified ?? true;
|
||||
row.UpdatedAtUtc = DateTimeOffset.UtcNow;
|
||||
}
|
||||
private static string Clean(string? value, string fallback, int max) { var text = string.IsNullOrWhiteSpace(value) ? fallback : value.Trim(); return text[..Math.Min(text.Length, max)]; }
|
||||
private static EvidenceDto ToDto(CareerEvidence row) => new(row.Id, row.Category, row.Title, row.Statement, JsonSerializer.Deserialize<List<string>>(row.TagsJson) ?? [], row.SourceType, row.SourceReference, row.IsVerified, row.UpdatedAtUtc);
|
||||
}
|
||||
Reference in New Issue
Block a user