Persist structured CV section data
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user