Persist structured CV section data

This commit is contained in:
cesnimda
2026-03-23 23:48:39 +01:00
parent 8f04637cff
commit c33640986e
5 changed files with 32 additions and 2 deletions
+7 -1
View File
@@ -1,3 +1,4 @@
using System.Text.Json;
using System.Security.Claims;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
@@ -57,10 +58,11 @@ public sealed class AuthController : ControllerBase
string? LastName,
string? DisplayName,
string? ProfileCvText,
string? ProfileCvStructureJson,
string? AvatarImageDataUrl,
IList<string> Roles,
GoogleLinkDto? GoogleLink);
public sealed record UpdateProfileRequest(string? Email, string? UserName, string? FirstName, string? LastName, string? DisplayName, string? ProfileCvText);
public sealed record UpdateProfileRequest(string? Email, string? UserName, string? FirstName, string? LastName, string? DisplayName, string? ProfileCvText, string? ProfileCvStructureJson);
public sealed record GoogleTokenRequest(string Token);
[HttpPost("login")]
@@ -173,6 +175,7 @@ public sealed class AuthController : ControllerBase
LastName: User.FindFirstValue("family_name"),
DisplayName: User.FindFirstValue("name"),
ProfileCvText: null,
ProfileCvStructureJson: null,
AvatarImageDataUrl: null,
Roles: Array.Empty<string>(),
GoogleLink: provider == "google" ? new GoogleLinkDto(false, email, null) : null));
@@ -194,6 +197,7 @@ public sealed class AuthController : ControllerBase
var lastName = TrimOrNull(request.LastName);
var displayName = TrimOrNull(request.DisplayName);
var profileCvText = TrimOrNull(request.ProfileCvText);
var profileCvStructureJson = TrimOrNull(request.ProfileCvStructureJson);
if (email is not null) user.Email = email;
if (userName is not null) user.UserName = userName;
@@ -201,6 +205,7 @@ public sealed class AuthController : ControllerBase
user.LastName = lastName;
user.DisplayName = displayName;
user.ProfileCvText = profileCvText;
user.ProfileCvStructureJson = profileCvStructureJson;
var res = await _users.UpdateAsync(user);
if (!res.Succeeded)
@@ -440,6 +445,7 @@ public sealed class AuthController : ControllerBase
LastName: user.LastName,
DisplayName: user.DisplayName,
ProfileCvText: user.ProfileCvText,
ProfileCvStructureJson: user.ProfileCvStructureJson,
AvatarImageDataUrl: user.AvatarImageDataUrl,
Roles: roles,
GoogleLink: new GoogleLinkDto(
@@ -1,4 +1,5 @@
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using JobTrackerApi.Services;
using JobTrackerApi.Models;
@@ -86,6 +87,8 @@ public sealed class ProfileCvController : ControllerBase
}
user.ProfileCvText = text;
user.ProfileCvStructureJson = JsonSerializer.Serialize(
ParseSections(text).Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content))).ToList());
var result = await _users.UpdateAsync(user);
if (!result.Succeeded)
{
@@ -114,6 +117,8 @@ public sealed class ProfileCvController : ControllerBase
}
user.ProfileCvText = rebuilt.Trim();
user.ProfileCvStructureJson = JsonSerializer.Serialize(
ParseSections(user.ProfileCvText).Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content))).ToList());
var result = await _users.UpdateAsync(user);
if (!result.Succeeded)
{
@@ -161,6 +166,13 @@ public sealed class ProfileCvController : ControllerBase
.Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content)))
.ToList();
user.ProfileCvStructureJson = JsonSerializer.Serialize(sections);
var update = await _users.UpdateAsync(user);
if (!update.Succeeded)
{
return BadRequest(string.Join("; ", update.Errors.Select(e => e.Description)));
}
return Ok(new { sections, totalWords = CountWords(source) });
}
@@ -183,6 +195,8 @@ public sealed class ProfileCvController : ControllerBase
}
user.ProfileCvText = improved.Trim();
user.ProfileCvStructureJson = JsonSerializer.Serialize(
ParseSections(user.ProfileCvText).Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content))).ToList());
var result = await _users.UpdateAsync(user);
if (!result.Succeeded)
{
+2
View File
@@ -576,6 +576,7 @@ CREATE TABLE IF NOT EXISTS "AspNetUserTokens" (
EnsureColumn(conn, "AspNetUsers", "LastName", "ALTER TABLE AspNetUsers ADD COLUMN LastName TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "DisplayName", "ALTER TABLE AspNetUsers ADD COLUMN DisplayName TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "ProfileCvText", "ALTER TABLE AspNetUsers ADD COLUMN ProfileCvText TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "ProfileCvStructureJson", "ALTER TABLE AspNetUsers ADD COLUMN ProfileCvStructureJson TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "AvatarImageDataUrl", "ALTER TABLE AspNetUsers ADD COLUMN AvatarImageDataUrl TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "GoogleSubject", "ALTER TABLE AspNetUsers ADD COLUMN GoogleSubject TEXT NULL;");
EnsureColumn(conn, "AspNetUsers", "GoogleEmail", "ALTER TABLE AspNetUsers ADD COLUMN GoogleEmail TEXT NULL;");
@@ -734,6 +735,7 @@ CREATE TABLE IF NOT EXISTS "GmailConnections" (
EnsureMySqlColumn(conn, "JobApplications", "LastReminderEmailSentAt", "ALTER TABLE `JobApplications` ADD COLUMN `LastReminderEmailSentAt` datetime NULL;");
EnsureMySqlColumn(conn, "Attachments", "Purpose", "ALTER TABLE `Attachments` ADD COLUMN `Purpose` varchar(100) NULL;");
EnsureMySqlColumn(conn, "Attachments", "UseForAi", "ALTER TABLE `Attachments` ADD COLUMN `UseForAi` tinyint(1) NOT NULL DEFAULT 1;");
EnsureMySqlColumn(conn, "AspNetUsers", "ProfileCvStructureJson", "ALTER TABLE `AspNetUsers` ADD COLUMN `ProfileCvStructureJson` longtext NULL;");
if (!MySqlIndexExists(conn, "Companies", "IX_Companies_OwnerUserId"))
{
+1
View File
@@ -8,6 +8,7 @@ public sealed class ApplicationUser : IdentityUser
public string? LastName { get; set; }
public string? DisplayName { get; set; }
public string? ProfileCvText { get; set; }
public string? ProfileCvStructureJson { get; set; }
public string? AvatarImageDataUrl { get; set; }
public string? GoogleSubject { get; set; }
public string? GoogleEmail { get; set; }
+8 -1
View File
@@ -29,6 +29,7 @@ type MeResponse = {
lastName?: string;
displayName?: string;
profileCvText?: string;
profileCvStructureJson?: string;
avatarImageDataUrl?: string;
roles?: string[];
googleLink?: {
@@ -115,6 +116,12 @@ export default function ProfilePage() {
setLastName(r.data?.lastName ?? "");
setDisplayName(r.data?.displayName ?? "");
setProfileCvText(r.data?.profileCvText ?? "");
try {
const parsed = r.data?.profileCvStructureJson ? JSON.parse(r.data.profileCvStructureJson) : [];
setParsedCvSections(Array.isArray(parsed) ? parsed : []);
} catch {
setParsedCvSections([]);
}
setHeadline(window.localStorage.getItem("profileHeadline") ?? "");
} catch {
setMe(null);
@@ -467,7 +474,7 @@ export default function ProfilePage() {
onClick={async () => {
setLoading(true);
try {
await api.put("/auth/profile", { email, userName, firstName, lastName, displayName, profileCvText });
await api.put("/auth/profile", { email, userName, firstName, lastName, displayName, profileCvText, profileCvStructureJson: JSON.stringify(parsedCvSections) });
window.localStorage.setItem("profileHeadline", headline.trim());
await loadProfile();
toast(t("profileUpdated"), "success");