Files
jobtrackingapp/Models/CvVariantSettings.cs
T
cesnimda e3b255f226
CI and Deploy / test (push) Failing after 1m55s
CI and Deploy / deploy (push) Has been skipped
feat(career): theme polish, rich-text bullets, entry ordering, outline API
Phase 4.5 backend enablers.
- Themes (priority 3): AtsFriendly flag on single-column themes (surfaced in
  GET /api/cv/themes), print-quality page-break rules (entries never split
  across a page; headings stay with content; widow/orphan control), darkened
  the creative sidebar for AA contrast.
- Rich text (priority 1): bullets/summary support **bold**, *italic*,
  __underline__, [text](url) via a safe inline pass — everything is HTML-escaped
  first, so no user tag can survive; only the whitelist emits markup.
- Entry ordering (priority 1): CvSectionSetting.ItemOrder reorders entries
  within a section by ItemKey, never touching the master profile.
- Outline API: GET /api/cv/outline returns the master profile as sections+entries
  with ItemKeys, so the Content tab can render editable per-item rows.
- 3 new tests (22 total in the builder suite).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:38:35 +02:00

92 lines
3.7 KiB
C#

using System.Text.Json;
using System.Text.Json.Serialization;
namespace JobTrackerApi.Models;
// The "lens" a CvVariant stores. Deliberately all-optional + defaulted so an empty settings object
// renders a sensible default CV straight from the master profile. Overrides are keyed by the master
// profile's stable ItemKeys so career data is referenced, never copied.
public sealed class CvVariantSettings
{
public string ThemeId { get; set; } = "modern";
// Theme overrides (null => use the theme's own value).
public string? AccentColor { get; set; }
public string? HeadingFont { get; set; }
public string? BodyFont { get; set; }
public string? Density { get; set; } // compact | balanced | roomy
public string? PageSize { get; set; } // a4 | letter
public string? DateFormat { get; set; } // display hint for the frontend; renderer uses source strings
public string? Language { get; set; }
public string? Headline { get; set; } // override the contact headline for this variant
public bool ShowPhoto { get; set; }
public bool ShowPageNumbers { get; set; }
public bool ShowIcons { get; set; } = true;
// Section order + visibility + optional renamed heading. Empty => default order, all visible.
public List<CvSectionSetting> Sections { get; set; } = new();
// Per-item overrides keyed by ItemKey (job/education/project/certification stable id).
public Dictionary<string, CvItemOverride> Overrides { get; set; } = new();
// Extra sections unique to this variant (not in the master profile).
public List<CvCustomSectionSetting> CustomSections { get; set; } = new();
}
public sealed class CvSectionSetting
{
public string Key { get; set; } = string.Empty; // summary|skills|experience|education|projects|certifications|languages|interests|links|custom:<k>
public bool Hidden { get; set; }
public string? Title { get; set; } // renamed heading
// Explicit entry order for this section, as a list of item ItemKeys. Entries not listed keep their
// master order and come last. Null => master profile order. Lets a variant reorder entries without
// touching the master profile.
public List<string>? ItemOrder { get; set; }
}
public sealed class CvItemOverride
{
public bool Hidden { get; set; }
public string? Title { get; set; }
public string? Subtitle { get; set; }
public List<string>? Bullets { get; set; } // null => use master bullets; non-null => replace for this variant only
}
public sealed class CvCustomSectionSetting
{
public string Key { get; set; } = string.Empty;
public string? Title { get; set; }
public List<string> Items { get; set; } = new();
public bool Hidden { get; set; }
}
public static class CvVariantSettingsJson
{
private static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
public static string Serialize(CvVariantSettings? settings) =>
JsonSerializer.Serialize(Normalize(settings), Options);
public static CvVariantSettings Deserialize(string? json)
{
if (string.IsNullOrWhiteSpace(json)) return new CvVariantSettings();
try { return Normalize(JsonSerializer.Deserialize<CvVariantSettings>(json, Options)); }
catch { return new CvVariantSettings(); }
}
public static CvVariantSettings Normalize(CvVariantSettings? s)
{
s ??= new CvVariantSettings();
s.ThemeId = string.IsNullOrWhiteSpace(s.ThemeId) ? "modern" : s.ThemeId.Trim().ToLowerInvariant();
s.Sections ??= new();
s.Overrides ??= new();
s.CustomSections ??= new();
return s;
}
}