feat(admin): users and permissions

Sprint 25.

New features/admin/users/ module, net-new /:lang/backoffice/users route +
Dashboard Quick Action.

- users: name, Telegram username, scope (marketplace vs office admin),
  role (inline change), status (active/invited/suspended), last login
- 4 built-in roles (owner/admin/editor/viewer) with flat permission lists
- invitations: email + role + scope form, pending list + revoke (no email
  actually sends - local record only)
- passwordless login confirmed already real (AdminAuthService Telegram QR,
  docs/BACKEND.md item 1) - linked, not reimplemented
- per-user mock session list (device/IP/last-active, revoke) - flagged as
  mock since the real AdminAuthService only ever tracks the current
  browser's session
- per-user audit log dialog (role/status changes), same pattern as
  Sprint 24's per-transaction audit, intentionally separate from the
  system-wide log planned for Sprint 26

docs/ADMIN.md + docs/BACKEND.md (new item 14) updated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-07-15 11:05:21 +04:00
parent 7d65913245
commit 17adc9e9fd
12 changed files with 455 additions and 3 deletions

View File

@@ -0,0 +1,47 @@
export type AdminUserScope = 'marketplace' | 'office';
export type AdminUserStatus = 'active' | 'invited' | 'suspended';
export type AdminInvitationStatus = 'pending' | 'accepted' | 'expired' | 'revoked';
export interface AdminRole {
id: string;
name: string;
permissions: string[];
builtIn: boolean;
}
export interface AdminUser {
id: string;
name: string;
telegramUsername: string;
email: string;
scope: AdminUserScope;
roleId: string;
status: AdminUserStatus;
lastLoginAt: string | null;
createdAt: string;
}
export interface AdminInvitation {
id: string;
email: string;
roleId: string;
scope: AdminUserScope;
status: AdminInvitationStatus;
invitedAt: string;
expiresAt: string;
}
export interface AdminSession {
id: string;
userId: string;
device: string;
ip: string;
lastActiveAt: string;
current: boolean;
}
export interface AdminUserAuditEntry {
action: string;
actor: string;
timestamp: string;
}