import { mkdir, writeFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import prettier from 'prettier'; const root = resolve(import.meta.dirname, '..'); const files = new Map([ [ 'packages/api-client/src/public-api.ts', `export * from './models';\nexport * from './api-client.service';\n`, ], [ 'packages/api-client/src/models.ts', `// Generated from the backend OpenAPI contract. Do not edit manually. export type Permission = | 'items.read' | 'items.create' | 'items.update' | 'items.delete' | 'users.read' | 'users.manage' | 'roles.read' | 'roles.manage' | 'audit.read' | 'sessions.readOwn' | 'sessions.revokeOwn' | 'sessions.manage' | 'notifications.readOwn' | 'notifications.updateOwn' | 'notifications.manage' | 'projects.use'; export interface ApiErrorBody { status: number; code: string; message: string; requestId: string; validation?: { field: string; messages: string[] }[]; } export interface PageDto { items: T[]; total: number; page: number; pageSize: number; } export interface UserDto { id: string; name: string; email: string | null; emailVerified: boolean | null; active: boolean; lastLoginAt: string | null; roles: RoleDto[]; settings: { tablePageSize: number; sidebarExpanded: boolean }; } export interface AdminRoleSummaryDto { id: string; name: string; system: boolean; } export interface AdminSessionDto { id: string; createdAt: string; lastActivityAt: string; userAgent: string | null; approximateIp: string | null; current: boolean; expiresAt: string; revokedAt: string | null; } export interface AdminUserListItemDto { id: string; name: string; email: string | null; active: boolean; roles: AdminRoleSummaryDto[]; lastLoginAt: string | null; createdAt: string; activeSessionCount: number; } export interface AdminUserDetailDto extends AdminUserListItemDto { effectivePermissions: Permission[]; sessions: AdminSessionDto[]; } export interface RoleDto { id: string; name: string; description: string; system: boolean; protected: boolean; permissions: { id: Permission; description: string }[]; userCount?: number; users?: UserDto[]; } export type ProjectRole = 'owner' | 'administrator' | 'editor' | 'reader'; export interface ProjectDto { id: string; name: string; description: string | null; role: ProjectRole; createdAt: string; updatedAt: string; } export interface ProjectMemberDto { userId: string; name: string; email: string | null; role: ProjectRole; active: boolean; joinedAt: string; } export interface ProjectInvitationDto { id: string; projectId: string | null; projectName: string | null; role: ProjectRole; status: 'pending' | 'accepted' | 'declined' | 'revoked' | 'expired'; invitedEmailMasked: string; expiresAt: string; mailStatus: 'not_configured'; canRespond: boolean; reason: 'email_mismatch' | 'email_unverified' | null; invitationPath?: string; } export interface ItemDto { id: string; name: string; description: string | null; status: 'draft' | 'active' | 'archived'; version: number; createdAt: string; updatedAt: string; deletedAt: string | null; } export interface SaveItemDto { name: string; description?: string; status: ItemDto['status']; version?: number; } export interface SessionDto { id: string; createdAt: string; lastActivityAt: string; userAgent: string | null; approximateIp: string | null; current: boolean; revokedAt: string | null; } export interface AuditLogDto { id: string; createdAt: string; actorUserId: string | null; action: string; targetType: string; targetId: string; metadata: Record | null; requestId: string; } export type NotificationType = | 'system' | 'item.created' | 'item.updated' | 'user.role-changed' | 'project.invitation'; export type NotificationStatusFilter = 'all' | 'read' | 'unread'; export interface NotificationDto { id: string; type: NotificationType; title: string; message: string; link: string | null; metadata: Record | null; read: boolean; readAt: string | null; createdAt: string; } export interface NotificationPageDto extends PageDto { unreadCount: number; } export interface CreateNotificationDto { userId: string; type: NotificationType; title: string; message: string; link?: string; metadata?: Record; } `, ], [ 'packages/api-client/src/api-client.service.ts', `// Generated from the backend OpenAPI contract. Do not edit manually. import { Injectable, inject } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import type { Observable } from 'rxjs'; import type { AuditLogDto, AdminSessionDto, AdminUserDetailDto, AdminUserListItemDto, CreateNotificationDto, ItemDto, NotificationDto, NotificationPageDto, NotificationStatusFilter, PageDto, Permission, ProjectDto, ProjectInvitationDto, ProjectMemberDto, ProjectRole, RoleDto, SaveItemDto, SessionDto, UserDto, } from './models'; @Injectable({ providedIn: 'root' }) export class ApiClientService { private readonly http = inject(HttpClient); private readonly base = '/api'; me(): Observable { return this.http.get(\`\${this.base}/me\`, { withCredentials: true }); } updateSettings(body: { tablePageSize?: number; sidebarExpanded?: boolean; }): Observable { return this.http.patch(\`\${this.base}/me/settings\`, body, { withCredentials: true, }); } dashboard(): Observable<{ userCount: number; activeSessions: number; roleCount: number; itemCount: number; }> { return this.http.get<{ userCount: number; activeSessions: number; roleCount: number; itemCount: number; }>(\`\${this.base}/dashboard\`, { withCredentials: true }); } projects(): Observable { return this.http.get(\`\${this.base}/projects\`, { withCredentials: true, }); } project(id: string): Observable { return this.http.get(\`\${this.base}/projects/\${id}\`, { withCredentials: true, }); } createProject(body: { name: string; description?: string }): Observable { return this.http.post(\`\${this.base}/projects\`, body, { withCredentials: true, }); } projectMembers(projectId: string): Observable { return this.http.get( \`\${this.base}/projects/\${projectId}/members\`, { withCredentials: true }, ); } updateProjectMember( projectId: string, userId: string, role: ProjectRole, ): Observable { return this.http.patch( \`\${this.base}/projects/\${projectId}/members/\${userId}\`, { role }, { withCredentials: true }, ); } removeProjectMember(projectId: string, userId: string): Observable { return this.http.delete( \`\${this.base}/projects/\${projectId}/members/\${userId}\`, { withCredentials: true }, ); } createProjectInvitation( projectId: string, body: { email: string; role: ProjectRole }, ): Observable { return this.http.post( \`\${this.base}/projects/\${projectId}/invitations\`, body, { withCredentials: true }, ); } pendingProjectInvitations(): Observable { return this.http.get(\`\${this.base}/invitations\`, { withCredentials: true, }); } acceptPendingProjectInvitation(invitationId: string): Observable { return this.http.post( \`\${this.base}/invitations/by-id/\${invitationId}/accept\`, {}, { withCredentials: true }, ); } declinePendingProjectInvitation(invitationId: string): Observable { return this.http.post( \`\${this.base}/invitations/by-id/\${invitationId}/decline\`, {}, { withCredentials: true }, ); } projectInvitation(token: string): Observable { return this.http.get( \`\${this.base}/invitations/\${encodeURIComponent(token)}\`, { withCredentials: true }, ); } acceptProjectInvitation(token: string): Observable { return this.http.post( \`\${this.base}/invitations/\${encodeURIComponent(token)}/accept\`, {}, { withCredentials: true }, ); } declineProjectInvitation(token: string): Observable { return this.http.post( \`\${this.base}/invitations/\${encodeURIComponent(token)}/decline\`, {}, { withCredentials: true }, ); } items( query: { search?: string; page?: number; pageSize?: number; sort?: string; direction?: string; } = {}, ): Observable> { return this.http.get>(\`\${this.base}/items\`, { params: this.params(query), withCredentials: true, }); } item(id: string): Observable { return this.http.get(\`\${this.base}/items/\${id}\`, { withCredentials: true, }); } createItem(body: SaveItemDto): Observable { return this.http.post(\`\${this.base}/items\`, body, { withCredentials: true, }); } updateItem(id: string, body: Required): Observable { return this.http.put(\`\${this.base}/items/\${id}\`, body, { withCredentials: true, }); } deleteItem(id: string, version: number): Observable { return this.http.delete(\`\${this.base}/items/\${id}\`, { params: { version }, withCredentials: true, }); } users( query: { search?: string; page?: number; pageSize?: number } = {}, ): Observable> { return this.http.get>(\`\${this.base}/users\`, { params: this.params(query), withCredentials: true, }); } setUserActive(id: string, active: boolean): Observable { return this.http.patch( \`\${this.base}/users/\${id}/active\`, { active }, { withCredentials: true }, ); } setUserRoles(id: string, roleIds: string[]): Observable { return this.http.patch( \`\${this.base}/users/\${id}/roles\`, { roleIds }, { withCredentials: true }, ); } adminUsers( query: { search?: string; active?: 'all' | 'active' | 'inactive'; roleId?: string; sort?: 'name' | 'email' | 'lastLoginAt' | 'createdAt'; direction?: 'ASC' | 'DESC'; page?: number; pageSize?: number; } = {}, ): Observable> { return this.http.get>(\`\${this.base}/admin/users\`, { params: this.params(query), withCredentials: true, }); } adminUser(id: string): Observable { return this.http.get(\`\${this.base}/admin/users/\${id}\`, { withCredentials: true, }); } activateAdminUser(id: string): Observable { return this.http.patch( \`\${this.base}/admin/users/\${id}/activate\`, {}, { withCredentials: true }, ); } deactivateAdminUser(id: string): Observable { return this.http.patch( \`\${this.base}/admin/users/\${id}/deactivate\`, {}, { withCredentials: true }, ); } assignAdminUserRole(userId: string, roleId: string): Observable { return this.http.post( \`\${this.base}/admin/users/\${userId}/roles/\${roleId}\`, {}, { withCredentials: true }, ); } removeAdminUserRole(userId: string, roleId: string): Observable { return this.http.delete( \`\${this.base}/admin/users/\${userId}/roles/\${roleId}\`, { withCredentials: true }, ); } adminUserSessions(id: string): Observable { return this.http.get(\`\${this.base}/admin/users/\${id}/sessions\`, { withCredentials: true, }); } revokeAdminUserSession(userId: string, sessionId: string): Observable { return this.http.delete( \`\${this.base}/admin/users/\${userId}/sessions/\${sessionId}\`, { withCredentials: true }, ); } revokeAdminUserSessions(userId: string): Observable<{ revoked: number }> { return this.http.delete<{ revoked: number }>( \`\${this.base}/admin/users/\${userId}/sessions\`, { withCredentials: true }, ); } roles(): Observable { return this.http.get(\`\${this.base}/roles\`, { withCredentials: true, }); } createRole(body: { name: string; permissions: string[] }): Observable { return this.http.post(\`\${this.base}/roles\`, body, { withCredentials: true, }); } updateRole( id: string, body: { name: string; permissions: string[] }, ): Observable { return this.http.put(\`\${this.base}/roles/\${id}\`, body, { withCredentials: true, }); } deleteRole(id: string): Observable { return this.http.delete(\`\${this.base}/roles/\${id}\`, { withCredentials: true, }); } adminRoles(): Observable { return this.http.get(\`\${this.base}/admin/roles\`, { withCredentials: true, }); } adminRole(id: string): Observable { return this.http.get(\`\${this.base}/admin/roles/\${id}\`, { withCredentials: true, }); } createAdminRole(body: { name: string; description?: string; permissions: Permission[]; }): Observable { return this.http.post(\`\${this.base}/admin/roles\`, body, { withCredentials: true, }); } updateAdminRole( id: string, body: { name: string; description?: string; permissions: Permission[] }, ): Observable { return this.http.put(\`\${this.base}/admin/roles/\${id}\`, body, { withCredentials: true, }); } deleteAdminRole(id: string): Observable { return this.http.delete(\`\${this.base}/admin/roles/\${id}\`, { withCredentials: true, }); } sessions(): Observable { return this.http.get(\`\${this.base}/sessions/own\`, { withCredentials: true, }); } revokeSession(id: string): Observable { return this.http.delete(\`\${this.base}/sessions/own/\${id}\`, { withCredentials: true, }); } revokeOtherSessions(): Observable { return this.http.delete(\`\${this.base}/sessions/own\`, { withCredentials: true, }); } revokeUserSessions(userId: string): Observable { return this.http.delete(\`\${this.base}/sessions/users/\${userId}\`, { withCredentials: true, }); } audit(page = 1, pageSize = 20): Observable> { return this.http.get>(\`\${this.base}/audit-log\`, { params: { page, pageSize }, withCredentials: true, }); } notifications( query: { page?: number; pageSize?: number; status?: NotificationStatusFilter; } = {}, ): Observable { return this.http.get(\`\${this.base}/notifications\`, { params: this.params(query), withCredentials: true, }); } unreadNotificationCount(): Observable<{ count: number }> { return this.http.get<{ count: number }>( \`\${this.base}/notifications/unread-count\`, { withCredentials: true }, ); } markNotificationRead(id: string): Observable { return this.http.patch( \`\${this.base}/notifications/\${id}/read\`, {}, { withCredentials: true }, ); } markNotificationUnread(id: string): Observable { return this.http.patch( \`\${this.base}/notifications/\${id}/unread\`, {}, { withCredentials: true }, ); } markAllNotificationsRead(): Observable<{ updated: number }> { return this.http.patch<{ updated: number }>( \`\${this.base}/notifications/read-all\`, {}, { withCredentials: true }, ); } deleteNotification(id: string): Observable { return this.http.delete(\`\${this.base}/notifications/\${id}\`, { withCredentials: true, }); } createAdminNotification( body: CreateNotificationDto, ): Observable { return this.http.post( \`\${this.base}/admin/notifications\`, body, { withCredentials: true }, ); } private params(value: Record): HttpParams { let params = new HttpParams(); Object.entries(value).forEach(([key, entry]) => { if (entry !== undefined && entry !== '') params = params.set(key, String(entry)); }); return params; } } `, ], ]); for (const [file, content] of files) { const target = resolve(root, file); await mkdir(dirname(target), { recursive: true }); const formatted = await prettier.format(content, { parser: file.endsWith('.ts') ? 'typescript' : 'json', singleQuote: true, semi: true, printWidth: 100, trailingComma: 'all', }); await writeFile(target, formatted, 'utf8'); }