using JobTrackerApi.Models; using JobTrackerApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace JobTrackerApi.Controllers; // Anonymous read-only surface for public CVs: /cv/{slug} in the SPA fetches this. Only variants the // owner has explicitly toggled public are served; noindex by default. docs/architecture/cv-builder.md. [ApiController] [Route("api/public-cv")] [AllowAnonymous] public sealed class PublicCvController : ControllerBase { private readonly UserManager _users; private readonly ICvVariantService _variants; public PublicCvController(UserManager users, ICvVariantService variants) { _users = users; _variants = variants; } [HttpGet("{slug}")] public async Task Get(string slug, CancellationToken ct) { var ownerId = await _variants.GetPublicOwnerAsync(slug, ct); if (ownerId is null) return NotFound(); var owner = await _users.FindByIdAsync(ownerId); var person = Person(owner); var result = await _variants.RenderPublicAsync(slug, person, ct); if (result is null) return NotFound(); Response.Headers["X-Robots-Tag"] = "noindex, nofollow"; return Ok(new { html = result.Value.render.Html, name = person.FallbackName }); } private static CvRenderPerson Person(ApplicationUser? user) { if (user is null) return new CvRenderPerson("Candidate", null); var name = string.Join(" ", new[] { user.FirstName?.Trim(), user.LastName?.Trim() }.Where(x => !string.IsNullOrWhiteSpace(x))); if (string.IsNullOrWhiteSpace(name)) name = user.DisplayName?.Trim(); if (string.IsNullOrWhiteSpace(name)) name = user.UserName?.Trim(); if (string.IsNullOrWhiteSpace(name)) name = "Candidate"; return new CvRenderPerson(name!, AvatarStorage.Resolve(user.AvatarImageDataUrl)); } }