feat(phase-2): separate /profile (identity) from /career (master profile)
Phase 2 — Career/Profile separation. The master career profile is the source of
truth; identity and career data are now saved independently so neither wipes the
other. CV Builder deliberately not built yet.
Backend — PUT /auth/profile is now a partial update:
- null/omitted field -> unchanged; "" -> cleared; value -> set (trimmed).
- Email/UserName never cleared to empty (login identifiers).
This lets /profile save identity fields and /career save the master-profile
fields through the same endpoint without one nulling the other. 4 new tests
cover the data-integrity guarantees (identity save keeps the CV, career save
keeps identity, empty clears, null leaves).
Frontend:
- ProfilePage save payload is now scoped by careerOnly: /career sends only
{ profileCvText, profileCvStructureJson }, /profile sends only identity.
- CareerWorkspacePage: removed the inert "CV Builder" tab (careerView) — Phase 2
establishes the master profile only; the builder is Phase 4.
- Dropped the dead careerView prop.
- Updated the CV-save test to render career mode and assert identity is excluded.
Source-of-truth flip (CareerProfileService authoritative) stays deferred to F5
per the branch design; CareerProfileService keeps mirroring via its dual-write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -363,21 +363,28 @@ public sealed class AuthController : ControllerBase
|
||||
return StatusCode(501, "Profile updates are only supported for local username/password accounts.");
|
||||
}
|
||||
|
||||
var email = TrimOrNull(request.Email);
|
||||
var userName = TrimOrNull(request.UserName);
|
||||
var firstName = TrimOrNull(request.FirstName);
|
||||
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;
|
||||
user.FirstName = firstName;
|
||||
user.LastName = lastName;
|
||||
user.DisplayName = displayName;
|
||||
user.ProfileCvText = profileCvText;
|
||||
user.ProfileCvStructureJson = profileCvStructureJson;
|
||||
// Partial update. A field is only touched when the request actually carries it:
|
||||
// - null / omitted -> leave unchanged (the caller isn't editing this field)
|
||||
// - "" -> explicitly clear
|
||||
// - "value" -> set (trimmed)
|
||||
// This lets /profile save identity fields and /career save the master-profile fields
|
||||
// through the same endpoint without one wiping the other. Email and UserName are the
|
||||
// login identifiers and are never cleared to empty.
|
||||
if (request.Email is not null)
|
||||
{
|
||||
var v = request.Email.Trim();
|
||||
if (v.Length > 0) user.Email = v;
|
||||
}
|
||||
if (request.UserName is not null)
|
||||
{
|
||||
var v = request.UserName.Trim();
|
||||
if (v.Length > 0) user.UserName = v;
|
||||
}
|
||||
if (request.FirstName is not null) user.FirstName = TrimOrNull(request.FirstName);
|
||||
if (request.LastName is not null) user.LastName = TrimOrNull(request.LastName);
|
||||
if (request.DisplayName is not null) user.DisplayName = TrimOrNull(request.DisplayName);
|
||||
if (request.ProfileCvText is not null) user.ProfileCvText = TrimOrNull(request.ProfileCvText);
|
||||
if (request.ProfileCvStructureJson is not null) user.ProfileCvStructureJson = TrimOrNull(request.ProfileCvStructureJson);
|
||||
|
||||
var res = await _users.UpdateAsync(user);
|
||||
if (!res.Succeeded)
|
||||
|
||||
Reference in New Issue
Block a user