PUT /api/candidate/profile

行政区划、学校班级、特长类别校验
证件号码唯一性
自动创建资料审核工作流
资料和用户显示名称原子更新

POST /api/candidate/registrations
资料审核状态、报名时间和科目校验
重复报名防护
自动选择负载最低的审批管理员
报名、科目和审批流原子写入
This commit is contained in:
biss committed 2026-07-22 19:55:19 +08:00
1 parent 07efa6813b
commit aecb0a04e7
16 files changed
+995 -36

No files matched your search

@@ -4,7 +4,7 @@ namespace Eis.Application.Candidate;
public sealed record CandidateEndpointResult(int StatusCode, JsonObject Body);
public interface ICandidateQueryService
public interface ICandidateService
{
Task<CandidateEndpointResult> GetDashboardAsync(string sessionToken, CancellationToken cancellationToken);
@@ -15,4 +15,14 @@ public interface ICandidateQueryService
Task<CandidateEndpointResult> GetExamsAsync(string sessionToken, CancellationToken cancellationToken);
Task<CandidateEndpointResult> GetRegistrationsAsync(string sessionToken, CancellationToken cancellationToken);
Task<CandidateEndpointResult> UpdateProfileAsync(
string sessionToken,
JsonObject body,
CancellationToken cancellationToken);
Task<CandidateEndpointResult> CreateRegistrationAsync(
string sessionToken,
JsonObject body,
CancellationToken cancellationToken);
}
@@ -1,15 +1,15 @@
namespace Eis.Infrastructure.Candidate;
public sealed record CandidateMigrationOptions(bool NativeReadEnabled)
public sealed record CandidateMigrationOptions(bool NativeEnabled)
{
public static CandidateMigrationOptions FromEnvironment(
bool configuredNativeReadEnabled,
bool configuredNativeEnabled,
bool authenticationNativeEnabled,
bool sharesLegacySessions)
{
var enabled = ParseBoolean(
Environment.GetEnvironmentVariable("CANDIDATE_NATIVE_ENABLED"),
configuredNativeReadEnabled);
configuredNativeEnabled);
if (enabled && !authenticationNativeEnabled)
{
throw new InvalidOperationException(
@@ -141,9 +141,11 @@ internal sealed record CandidateUserSummary(
string? ClassId,
string DisplayName,
string? CandidateNumber,
bool Active,
bool MustChangePassword,
bool TotpEnabled,
string? ArchivedAt);
string? ArchivedAt,
string CreatedAt);
internal sealed class CandidateReadSnapshotLoader(IRelationalConnectionFactory connectionFactory)
{
@@ -262,7 +264,7 @@ internal sealed class CandidateReadSnapshotLoader(IRelationalConnectionFactory c
ReadWorkflowAction,
cancellationToken);
var users = await QueryAsync(connection,
"SELECT id, username, role, admin_level, school_id, class_id, display_name, candidate_number, must_change_password, totp_enabled, archived_at FROM users ORDER BY created_at, id",
"SELECT id, username, role, admin_level, school_id, class_id, display_name, candidate_number, active, must_change_password, totp_enabled, archived_at, created_at FROM users ORDER BY created_at, id",
ReadUserSummary,
cancellationToken);
@@ -435,18 +437,24 @@ internal sealed class CandidateReadSnapshotLoader(IRelationalConnectionFactory c
ReadOptionalString(reader, "to_assignee_id"),
ReadString(reader, "created_at"));
private static CandidateUserSummary ReadUserSummary(DbDataReader reader) => new(
ReadString(reader, "id"),
ReadString(reader, "username"),
ReadString(reader, "role"),
ReadOptionalString(reader, "admin_level"),
ReadOptionalString(reader, "school_id"),
ReadOptionalString(reader, "class_id"),
ReadString(reader, "display_name"),
ReadOptionalString(reader, "candidate_number"),
ReadBoolean(reader, "must_change_password"),
ReadBoolean(reader, "totp_enabled"),
ReadOptionalString(reader, "archived_at"));
private static CandidateUserSummary ReadUserSummary(DbDataReader reader)
{
var role = ReadString(reader, "role");
return new CandidateUserSummary(
ReadString(reader, "id"),
ReadString(reader, "username"),
role,
ReadOptionalString(reader, "admin_level") ?? (role == "admin" ? "super" : null),
ReadOptionalString(reader, "school_id"),
ReadOptionalString(reader, "class_id"),
ReadString(reader, "display_name"),
ReadOptionalString(reader, "candidate_number"),
ReadBoolean(reader, "active"),
ReadBoolean(reader, "must_change_password"),
ReadBoolean(reader, "totp_enabled"),
ReadOptionalString(reader, "archived_at"),
ReadString(reader, "created_at"));
}
private static string ReadString(DbDataReader reader, string name) =>
Convert.ToString(reader.GetValue(reader.GetOrdinal(name)), CultureInfo.InvariantCulture) ?? string.Empty;
Loaded 3 of 16 files, more files were not shown because too many files have changed in this diff. Show more