import apiClient from "@app/services/apiClient"; import { supabase, isSupabaseConfigured } from "@app/services/supabaseClient"; export interface User { id: number; username: string; email?: string; supabaseId?: string | null; roleName: string; // Translation key like "adminUserSettings.admin" rolesAsString?: string; // Actual role ID like "ROLE_ADMIN" enabled: boolean; isFirstLogin?: boolean; authenticationType?: string; team?: { id: number; name: string; }; createdAt?: string; updatedAt?: string; // Enriched client-side fields isActive?: boolean; lastRequest?: number; // timestamp in milliseconds } export interface AdminSettingsData { users: User[]; userSessions: Record; userLastRequest: Record; // username -> timestamp in milliseconds totalUsers: number; activeUsers: number; disabledUsers: number; currentUsername?: string; roleDetails?: Record; teams?: Array<{ id: number; name: string; [key: string]: unknown }>; maxPaidUsers?: number; // License information maxAllowedUsers: number; availableSlots: number; licenseMaxUsers: number; premiumEnabled: boolean; } export interface CreateUserRequest { username: string; password?: string; role: string; teamId?: number; authType: "password" | "SSO"; forceChange?: boolean; } export interface UpdateUserRoleRequest { username: string; role: string; teamId?: number; } export interface InviteUsersRequest { emails: string; // Comma-separated email addresses role: string; teamId?: number; } export interface InviteUsersResponse { successCount: number; failureCount: number; message?: string; errors?: string; error?: string; } export interface InviteLinkRequest { email?: string; role: string; teamId?: number; expiryHours?: number; sendEmail?: boolean; } export interface InviteLinkResponse { token: string; inviteUrl: string; email: string; expiresAt: string; expiryHours: number; emailSent?: boolean; emailError?: string; error?: string; } export interface InviteToken { id: number; email: string; role: string; teamId?: number; createdBy: string; createdAt: string; expiresAt: string; } /** * User Management Service * Provides functions to interact with user management backend APIs */ export const userManagementService = { /** * Get all users with session data (admin only) */ async getUsers(): Promise { const response = await apiClient.get( "/api/v1/proprietary/ui-data/admin-settings", ); return response.data; }, /** * Get users without a team */ async getUsersWithoutTeam(): Promise { const response = await apiClient.get("/api/v1/users/without-team"); return response.data; }, /** * Create a new user (admin only) */ async createUser(data: CreateUserRequest): Promise { const formData = new FormData(); formData.append("username", data.username); if (data.password) { formData.append("password", data.password); } formData.append("role", data.role); if (data.teamId) { formData.append("teamId", data.teamId.toString()); } formData.append("authType", data.authType); if (data.forceChange !== undefined) { formData.append("forceChange", data.forceChange.toString()); } await apiClient.post("/api/v1/user/admin/saveUser", formData, { suppressErrorToast: true, // Component will handle error display }); }, /** * Update user role and/or team (admin only) */ async updateUserRole(data: UpdateUserRoleRequest): Promise { const formData = new FormData(); formData.append("username", data.username); formData.append("role", data.role); if (data.teamId) { formData.append("teamId", data.teamId.toString()); } await apiClient.post("/api/v1/user/admin/changeRole", formData, { suppressErrorToast: true, }); }, /** * Enable or disable a user (admin only) */ async toggleUserEnabled(username: string, enabled: boolean): Promise { const formData = new FormData(); formData.append("enabled", enabled.toString()); await apiClient.post( `/api/v1/user/admin/changeUserEnabled/${username}`, formData, { suppressErrorToast: true, }, ); }, /** * Delete a user (admin only) */ async deleteUser( user: User, options?: { notifyUser?: boolean }, ): Promise { if (isSupabaseConfigured && supabase) { if (!user.email) { throw new Error( "Email missing for this user. Please contact support for manual removal.", ); } const { error } = await supabase.functions.invoke("delete-user", { body: { target_email: user.email, notify_user: options?.notifyUser ?? true, }, }); if (error) { throw new Error(error.message || "Supabase deletion failed"); } return; } }, /** * Invite users via email (admin only) * Sends comma-separated email addresses, creates accounts with random passwords, * and sends invitation emails */ async inviteUsers(data: InviteUsersRequest): Promise { const formData = new FormData(); formData.append("emails", data.emails); formData.append("role", data.role); if (data.teamId) { formData.append("teamId", data.teamId.toString()); } const response = await apiClient.post( "/api/v1/user/admin/inviteUsers", formData, { suppressErrorToast: true, // Component will handle error display }, ); return response.data; }, /** * Generate an invite link (admin only) */ async generateInviteLink( data: InviteLinkRequest, ): Promise { const formData = new FormData(); // Only append email if it's provided and not empty if (data.email && data.email.trim()) { formData.append("email", data.email); } formData.append("role", data.role); if (data.teamId) { formData.append("teamId", data.teamId.toString()); } if (data.expiryHours) { formData.append("expiryHours", data.expiryHours.toString()); } if (data.sendEmail !== undefined) { formData.append("sendEmail", data.sendEmail.toString()); } const response = await apiClient.post( "/api/v1/invite/generate", formData, { suppressErrorToast: true, }, ); return response.data; }, /** * Get list of active invite links (admin only) */ async getInviteLinks(): Promise { const response = await apiClient.get<{ invites: InviteToken[] }>( "/api/v1/invite/list", ); return response.data.invites; }, /** * Revoke an invite link (admin only) */ async revokeInviteLink(inviteId: number): Promise { await apiClient.delete(`/api/v1/invite/revoke/${inviteId}`, { suppressErrorToast: true, }); }, /** * Clean up expired invite links (admin only) */ async cleanupExpiredInvites(): Promise<{ deletedCount: number }> { const response = await apiClient.post<{ deletedCount: number }>( "/api/v1/invite/cleanup", ); return response.data; }, };