Files
jobtrackingapp/JobTrackerApi/Controllers/CareerEvidenceController.cs
T
2026-08-31 17:03:09 +02:00

85 lines
4.2 KiB
C#

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);
}