fix(app): harden account and workflow state
This commit is contained in:
@@ -473,6 +473,7 @@ Canonical profile:
|
||||
[FromQuery] int? companyId = null,
|
||||
[FromQuery] string? location = null,
|
||||
[FromQuery] bool needsFollowUp = false,
|
||||
[FromQuery] string? readiness = null,
|
||||
[FromQuery] bool includeDeleted = false,
|
||||
[FromQuery] bool deletedOnly = false,
|
||||
[FromQuery] string? sortBy = null,
|
||||
@@ -482,6 +483,9 @@ Canonical profile:
|
||||
{
|
||||
if (page < 1) page = 1;
|
||||
if (pageSize is not (15 or 20 or 25)) pageSize = 15;
|
||||
var readinessFilter = (readiness ?? string.Empty).Trim().ToLowerInvariant();
|
||||
if (readinessFilter is not ("" or "needs-work" or "interview"))
|
||||
return BadRequest("Readiness must be 'needs-work' or 'interview'.");
|
||||
|
||||
var query = _db.JobApplications
|
||||
.AsNoTracking()
|
||||
@@ -542,16 +546,25 @@ Canonical profile:
|
||||
var dirDesc = string.Equals(sortDir, "desc", StringComparison.OrdinalIgnoreCase);
|
||||
var key = (sortBy ?? "dateApplied").Trim();
|
||||
|
||||
if (needsFollowUp)
|
||||
if (needsFollowUp || readinessFilter.Length > 0)
|
||||
{
|
||||
// NeedsFollowUp depends on rules + last correspondence date; evaluate in memory so filtering is correct.
|
||||
// Both filters depend on rule evaluation and workflow signals that include normalized
|
||||
// note content, so evaluate before pagination to keep totals/pages correct.
|
||||
var pre = await query.ToListAsync(cancellationToken);
|
||||
var filtered = new List<JobApplication>();
|
||||
foreach (var j in pre)
|
||||
{
|
||||
lastMsg.TryGetValue(j.Id, out var lm);
|
||||
var d = RulesEngine.Evaluate(settings, j, now, lm);
|
||||
if (d.NeedsFollowUp) filtered.Add(j);
|
||||
var signal = BuildWorkflowSignal(j, d);
|
||||
var matchesFollowUp = !needsFollowUp || d.NeedsFollowUp;
|
||||
var matchesReadiness = readinessFilter switch
|
||||
{
|
||||
"interview" => signal.NeedsInterviewPrep,
|
||||
"needs-work" => signal.HasPackageGap || signal.NeedsInterviewPrep,
|
||||
_ => true,
|
||||
};
|
||||
if (matchesFollowUp && matchesReadiness) filtered.Add(j);
|
||||
}
|
||||
|
||||
filtered = key switch
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using JobTrackerApi.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace JobTrackerApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/notification-settings")]
|
||||
[Authorize(AuthenticationSchemes = "local")]
|
||||
public sealed class NotificationSettingsController(UserManager<ApplicationUser> users) : ControllerBase
|
||||
{
|
||||
public sealed record NotificationSettingsDto(bool EmailFollowUpRemindersEnabled);
|
||||
public sealed record SaveNotificationSettingsRequest(bool EmailFollowUpRemindersEnabled);
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<NotificationSettingsDto>> Get()
|
||||
{
|
||||
var user = await users.GetUserAsync(User);
|
||||
return user is null ? Unauthorized() : Ok(new NotificationSettingsDto(user.EmailFollowUpRemindersEnabled));
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<ActionResult<NotificationSettingsDto>> Put([FromBody] SaveNotificationSettingsRequest request)
|
||||
{
|
||||
var user = await users.GetUserAsync(User);
|
||||
if (user is null) return Unauthorized();
|
||||
user.EmailFollowUpRemindersEnabled = request.EmailFollowUpRemindersEnabled;
|
||||
var result = await users.UpdateAsync(user);
|
||||
if (!result.Succeeded)
|
||||
return Problem("Notification settings could not be saved.", statusCode: StatusCodes.Status500InternalServerError);
|
||||
return Ok(new NotificationSettingsDto(user.EmailFollowUpRemindersEnabled));
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,13 @@ public sealed class UsersController : ControllerBase
|
||||
if (!removeRoles.Succeeded) return IdentityFailure(removeRoles);
|
||||
}
|
||||
|
||||
// Role claims are embedded in issued JWTs. Without revoking the target user's live
|
||||
// sessions, a removed Admin role would remain effective until those tokens expired.
|
||||
// The application DI path always supplies the context; the null branch only supports
|
||||
// isolated controller construction in older tests.
|
||||
if (_db is not null && (toAdd.Count > 0 || toRemove.Count > 0))
|
||||
await SessionRevocation.RevokeAllAsync(_db, u.Id, trustedDeviceHashToKeep: null, cancellationToken);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user