Files
Agent-System/web/src/api/authUserMap.ts
T
weichenandCursor c8dbefe8a1 fix(web): align settings CAS, PDF preview, and collaboration UX guards
Wire expected_revision, blob PDF framing, mustChangePassword session rewrite,
client_key project create, no-project send disable, and right-rail persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 07:05:06 +00:00

110 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Go UserAccount JSON 使用 snake_case(role_id / employee_id / must_change_password / created_at…)。
* 前端 UserAccount / UserProfile 使用 camelCase。在 API 边界统一映射,兼容偶发旧驼峰字段。
*/
import type { AccountStatus, LoginResult, MeResult, UserAccount } from '../types/auth';
function pickString(raw: Record<string, unknown>, snake: string, camel: string): string {
const a = raw[snake];
const b = raw[camel];
if (typeof a === 'string' && a.length > 0) return a;
if (typeof b === 'string') return b;
if (typeof a === 'string') return a;
return '';
}
function pickBool(raw: Record<string, unknown>, snake: string, camel: string): boolean {
if (typeof raw[snake] === 'boolean') return raw[snake] as boolean;
if (typeof raw[camel] === 'boolean') return raw[camel] as boolean;
return false;
}
function pickNumber(raw: Record<string, unknown>, key: string): number {
const v = raw[key];
if (typeof v === 'number' && Number.isFinite(v)) return v;
if (typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v))) return Number(v);
return 0;
}
/** 将服务端(或混合)用户对象规范为前端 camelCase UserAccount。 */
export function mapUserAccount(raw: unknown): UserAccount {
if (!raw || typeof raw !== 'object') {
throw new Error('无效的用户账户响应');
}
const o = raw as Record<string, unknown>;
const statusRaw = pickString(o, 'status', 'status') || 'active';
const status = statusRaw as AccountStatus;
return {
id: pickNumber(o, 'id'),
username: pickString(o, 'username', 'username'),
name: pickString(o, 'name', 'name'),
status,
mustChangePassword: pickBool(o, 'must_change_password', 'mustChangePassword'),
roleId: pickString(o, 'role_id', 'roleId'),
position: pickString(o, 'position', 'position'),
employeeId: pickString(o, 'employee_id', 'employeeId'),
department: pickString(o, 'department', 'department'),
email: pickString(o, 'email', 'email') || undefined,
avatar: pickString(o, 'avatar', 'avatar') || undefined,
createdAt: pickString(o, 'created_at', 'createdAt') || undefined,
updatedAt: pickString(o, 'updated_at', 'updatedAt') || undefined,
};
}
export function mapUserAccountList(raw: unknown): UserAccount[] {
if (!Array.isArray(raw)) return [];
return raw.map((item) => mapUserAccount(item));
}
/**
* Top-level must_change_password is the sole authority when present.
* Never OR-merge with nested user.mustChangePassword — that revived a cleared
* flag after FirstLoginPasswordModal only patched the top-level field.
*/
function resolveMustChangePassword(
o: Record<string, unknown>,
nestedUser: { mustChangePassword?: boolean },
): boolean {
if (typeof o.must_change_password === 'boolean') return o.must_change_password;
if (typeof o.mustChangePassword === 'boolean') return o.mustChangePassword as boolean;
return !!nestedUser.mustChangePassword;
}
/** 映射 /auth/login 响应(token + user + must_change_password + env)。 */
export function mapLoginResult(raw: unknown): LoginResult {
if (!raw || typeof raw !== 'object') {
throw new Error('无效的登录响应');
}
const o = raw as Record<string, unknown>;
const user = mapUserAccount(o.user);
const mustChangePassword = resolveMustChangePassword(o, user);
const capabilities = Array.isArray(o.capabilities)
? (o.capabilities as unknown[]).map(String)
: undefined;
return {
token: pickString(o, 'token', 'token'),
user: { ...user, mustChangePassword },
mustChangePassword,
env: pickString(o, 'env', 'env') || 'dev',
capabilities,
};
}
/** 映射 /auth/me 响应。 */
export function mapMeResult(raw: unknown): MeResult {
if (!raw || typeof raw !== 'object') {
throw new Error('无效的会话响应');
}
const o = raw as Record<string, unknown>;
const user = mapUserAccount(o.user);
const mustChangePassword = resolveMustChangePassword(o, user);
return {
user: { ...user, mustChangePassword },
mustChangePassword,
env: pickString(o, 'env', 'env') || 'dev',
capabilities: Array.isArray(o.capabilities) ? (o.capabilities as unknown[]).map(String) : [],
};
}