Persist structured CV section data
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Text.Json;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using JobTrackerApi.Models;
|
using JobTrackerApi.Models;
|
||||||
using JobTrackerApi.Services;
|
using JobTrackerApi.Services;
|
||||||
@@ -57,10 +58,11 @@ public sealed class AuthController : ControllerBase
|
|||||||
string? LastName,
|
string? LastName,
|
||||||
string? DisplayName,
|
string? DisplayName,
|
||||||
string? ProfileCvText,
|
string? ProfileCvText,
|
||||||
|
string? ProfileCvStructureJson,
|
||||||
string? AvatarImageDataUrl,
|
string? AvatarImageDataUrl,
|
||||||
IList<string> Roles,
|
IList<string> Roles,
|
||||||
GoogleLinkDto? GoogleLink);
|
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);
|
public sealed record GoogleTokenRequest(string Token);
|
||||||
|
|
||||||
[HttpPost("login")]
|
[HttpPost("login")]
|
||||||
@@ -173,6 +175,7 @@ public sealed class AuthController : ControllerBase
|
|||||||
LastName: User.FindFirstValue("family_name"),
|
LastName: User.FindFirstValue("family_name"),
|
||||||
DisplayName: User.FindFirstValue("name"),
|
DisplayName: User.FindFirstValue("name"),
|
||||||
ProfileCvText: null,
|
ProfileCvText: null,
|
||||||
|
ProfileCvStructureJson: null,
|
||||||
AvatarImageDataUrl: null,
|
AvatarImageDataUrl: null,
|
||||||
Roles: Array.Empty<string>(),
|
Roles: Array.Empty<string>(),
|
||||||
GoogleLink: provider == "google" ? new GoogleLinkDto(false, email, null) : null));
|
GoogleLink: provider == "google" ? new GoogleLinkDto(false, email, null) : null));
|
||||||
@@ -194,6 +197,7 @@ public sealed class AuthController : ControllerBase
|
|||||||
var lastName = TrimOrNull(request.LastName);
|
var lastName = TrimOrNull(request.LastName);
|
||||||
var displayName = TrimOrNull(request.DisplayName);
|
var displayName = TrimOrNull(request.DisplayName);
|
||||||
var profileCvText = TrimOrNull(request.ProfileCvText);
|
var profileCvText = TrimOrNull(request.ProfileCvText);
|
||||||
|
var profileCvStructureJson = TrimOrNull(request.ProfileCvStructureJson);
|
||||||
|
|
||||||
if (email is not null) user.Email = email;
|
if (email is not null) user.Email = email;
|
||||||
if (userName is not null) user.UserName = userName;
|
if (userName is not null) user.UserName = userName;
|
||||||
@@ -201,6 +205,7 @@ public sealed class AuthController : ControllerBase
|
|||||||
user.LastName = lastName;
|
user.LastName = lastName;
|
||||||
user.DisplayName = displayName;
|
user.DisplayName = displayName;
|
||||||
user.ProfileCvText = profileCvText;
|
user.ProfileCvText = profileCvText;
|
||||||
|
user.ProfileCvStructureJson = profileCvStructureJson;
|
||||||
|
|
||||||
var res = await _users.UpdateAsync(user);
|
var res = await _users.UpdateAsync(user);
|
||||||
if (!res.Succeeded)
|
if (!res.Succeeded)
|
||||||
@@ -440,6 +445,7 @@ public sealed class AuthController : ControllerBase
|
|||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
DisplayName: user.DisplayName,
|
DisplayName: user.DisplayName,
|
||||||
ProfileCvText: user.ProfileCvText,
|
ProfileCvText: user.ProfileCvText,
|
||||||
|
ProfileCvStructureJson: user.ProfileCvStructureJson,
|
||||||
AvatarImageDataUrl: user.AvatarImageDataUrl,
|
AvatarImageDataUrl: user.AvatarImageDataUrl,
|
||||||
Roles: roles,
|
Roles: roles,
|
||||||
GoogleLink: new GoogleLinkDto(
|
GoogleLink: new GoogleLinkDto(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using JobTrackerApi.Services;
|
using JobTrackerApi.Services;
|
||||||
using JobTrackerApi.Models;
|
using JobTrackerApi.Models;
|
||||||
@@ -86,6 +87,8 @@ public sealed class ProfileCvController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.ProfileCvText = text;
|
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);
|
var result = await _users.UpdateAsync(user);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
@@ -114,6 +117,8 @@ public sealed class ProfileCvController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.ProfileCvText = rebuilt.Trim();
|
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);
|
var result = await _users.UpdateAsync(user);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
@@ -161,6 +166,13 @@ public sealed class ProfileCvController : ControllerBase
|
|||||||
.Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content)))
|
.Select(section => new ParsedCvSectionDto(section.Name, section.Content, CountWords(section.Content)))
|
||||||
.ToList();
|
.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) });
|
return Ok(new { sections, totalWords = CountWords(source) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +195,8 @@ public sealed class ProfileCvController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.ProfileCvText = improved.Trim();
|
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);
|
var result = await _users.UpdateAsync(user);
|
||||||
if (!result.Succeeded)
|
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", "LastName", "ALTER TABLE AspNetUsers ADD COLUMN LastName TEXT NULL;");
|
||||||
EnsureColumn(conn, "AspNetUsers", "DisplayName", "ALTER TABLE AspNetUsers ADD COLUMN DisplayName 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", "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", "AvatarImageDataUrl", "ALTER TABLE AspNetUsers ADD COLUMN AvatarImageDataUrl TEXT NULL;");
|
||||||
EnsureColumn(conn, "AspNetUsers", "GoogleSubject", "ALTER TABLE AspNetUsers ADD COLUMN GoogleSubject 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;");
|
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, "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", "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, "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"))
|
if (!MySqlIndexExists(conn, "Companies", "IX_Companies_OwnerUserId"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public sealed class ApplicationUser : IdentityUser
|
|||||||
public string? LastName { get; set; }
|
public string? LastName { get; set; }
|
||||||
public string? DisplayName { get; set; }
|
public string? DisplayName { get; set; }
|
||||||
public string? ProfileCvText { get; set; }
|
public string? ProfileCvText { get; set; }
|
||||||
|
public string? ProfileCvStructureJson { get; set; }
|
||||||
public string? AvatarImageDataUrl { get; set; }
|
public string? AvatarImageDataUrl { get; set; }
|
||||||
public string? GoogleSubject { get; set; }
|
public string? GoogleSubject { get; set; }
|
||||||
public string? GoogleEmail { get; set; }
|
public string? GoogleEmail { get; set; }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type MeResponse = {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
profileCvText?: string;
|
profileCvText?: string;
|
||||||
|
profileCvStructureJson?: string;
|
||||||
avatarImageDataUrl?: string;
|
avatarImageDataUrl?: string;
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
googleLink?: {
|
googleLink?: {
|
||||||
@@ -115,6 +116,12 @@ export default function ProfilePage() {
|
|||||||
setLastName(r.data?.lastName ?? "");
|
setLastName(r.data?.lastName ?? "");
|
||||||
setDisplayName(r.data?.displayName ?? "");
|
setDisplayName(r.data?.displayName ?? "");
|
||||||
setProfileCvText(r.data?.profileCvText ?? "");
|
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") ?? "");
|
setHeadline(window.localStorage.getItem("profileHeadline") ?? "");
|
||||||
} catch {
|
} catch {
|
||||||
setMe(null);
|
setMe(null);
|
||||||
@@ -467,7 +474,7 @@ export default function ProfilePage() {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
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());
|
window.localStorage.setItem("profileHeadline", headline.trim());
|
||||||
await loadProfile();
|
await loadProfile();
|
||||||
toast(t("profileUpdated"), "success");
|
toast(t("profileUpdated"), "success");
|
||||||
|
|||||||
Reference in New Issue
Block a user