From 8849deec215e37ddf85cfda449b1dc64d3e78865 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Mon, 5 May 2025 21:28:29 +0200 Subject: [PATCH] Migrate admin API to typescript --- app/src/api/admin.js | 49 ------------ app/src/api/admin.ts | 157 +++++++++++++++++++++++++++++++++++++ app/src/types/adminApi.ts | 60 ++++++++++++++ app/src/types/workspace.ts | 25 ++++++ 4 files changed, 242 insertions(+), 49 deletions(-) delete mode 100644 app/src/api/admin.js create mode 100644 app/src/api/admin.ts create mode 100644 app/src/types/adminApi.ts diff --git a/app/src/api/admin.js b/app/src/api/admin.js deleted file mode 100644 index d8b93cc..0000000 --- a/app/src/api/admin.js +++ /dev/null @@ -1,49 +0,0 @@ -import { apiCall } from './auth'; -import { API_BASE_URL } from '../utils/constants'; - -const ADMIN_BASE_URL = `${API_BASE_URL}/admin`; - -// User Management -export const getUsers = async () => { - const response = await apiCall(`${ADMIN_BASE_URL}/users`); - return response.json(); -}; - -export const createUser = async (userData) => { - const response = await apiCall(`${ADMIN_BASE_URL}/users`, { - method: 'POST', - body: JSON.stringify(userData), - }); - return response.json(); -}; - -export const deleteUser = async (userId) => { - const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, { - method: 'DELETE', - }); - if (response.status === 204) { - return; - } else { - throw new Error('Failed to delete user with status: ', response.status); - } -}; - -export const updateUser = async (userId, userData) => { - const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, { - method: 'PUT', - body: JSON.stringify(userData), - }); - return response.json(); -}; - -// Workspace Management -export const getWorkspaces = async () => { - const response = await apiCall(`${ADMIN_BASE_URL}/workspaces`); - return response.json(); -}; - -// System Statistics -export const getSystemStats = async () => { - const response = await apiCall(`${ADMIN_BASE_URL}/stats`); - return response.json(); -}; diff --git a/app/src/api/admin.ts b/app/src/api/admin.ts new file mode 100644 index 0000000..f7de7b7 --- /dev/null +++ b/app/src/api/admin.ts @@ -0,0 +1,157 @@ +import { apiCall } from './api'; +import { API_BASE_URL, isUser, User } from '../types/authApi'; +import { + CreateUserRequest, + isSystemStats, + SystemStats, + UpdateUserRequest, +} from '@/types/adminApi'; +import { isWorkspace, Workspace } from '@/types/workspace'; + +const ADMIN_BASE_URL = `${API_BASE_URL}/admin`; + +// User Management + +/** + * Fetches all users from the API + * @returns {Promise} A promise that resolves to an array of users + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const getUsers = async (): Promise => { + const response = await apiCall(`${ADMIN_BASE_URL}/users`); + + if (!response.ok) { + const data = await response.json(); + const errorData = data as { message: string }; + throw new Error(errorData.message || 'Failed to fetch users'); + } + + const data = await response.json(); + if (!Array.isArray(data)) { + throw new Error('Invalid users response received from API'); + } + return data.map((user) => { + if (!isUser(user)) { + throw new Error('Invalid user object received from API'); + } + return user as User; + }); +}; + +/** + * Creates a new user in the system + * @param {CreateUserRequest} userData The data for the new user + * @returns {Promise} A promise that resolves to the created user + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const createUser = async ( + userData: CreateUserRequest +): Promise => { + const response = await apiCall(`${ADMIN_BASE_URL}/users`, { + method: 'POST', + body: JSON.stringify(userData), + }); + + if (!response.ok) { + const data = await response.json(); + const errorData = data as { message: string }; + throw new Error(errorData.message || 'Failed to create user'); + } + + const data = await response.json(); + if (!isUser(data)) { + throw new Error('Invalid user object received from API'); + } + return data as User; +}; + +/** + * Deletes a user from the system + * @param {number} userId The ID of the user to delete + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const deleteUser = async (userId: number) => { + const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, { + method: 'DELETE', + }); + if (response.status === 204) { + return; + } else { + throw new Error('Failed to delete user with status: ' + response.status); + } +}; + +/** + * Updates an existing user in the system + * @param {number} userId The ID of the user to update + * @param {UpdateUserRequest} userData The data to update the user with + * @returns {Promise} A promise that resolves to the updated user + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const updateUser = async ( + userId: number, + userData: UpdateUserRequest +): Promise => { + const response = await apiCall(`${ADMIN_BASE_URL}/users/${userId}`, { + method: 'PUT', + body: JSON.stringify(userData), + }); + + if (!response.ok) { + const data = await response.json(); + const errorData = data as { message: string }; + throw new Error(errorData.message || 'Failed to update user'); + } + const data = await response.json(); + if (!isUser(data)) { + throw new Error('Invalid user object received from API'); + } + return data as User; +}; + +// Workspace Management + +/** + * Fetches all workspaces from the API + * @returns {Promise} A promise that resolves to an array of workspaces + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const getWorkspaces = async (): Promise => { + const response = await apiCall(`${ADMIN_BASE_URL}/workspaces`); + if (!response.ok) { + const data = await response.json(); + const errorData = data as { message: string }; + throw new Error(errorData.message || 'Failed to fetch workspaces'); + } + const data = await response.json(); + if (!Array.isArray(data)) { + throw new Error('Invalid workspaces response received from API'); + } + return data.map((workspace) => { + if (!isWorkspace(workspace)) { + throw new Error('Invalid workspace object received from API'); + } + return workspace as Workspace; + }); +}; + +// System Statistics + +/** + * Fetches system-wide statistics from the API + * @returns {Promise} A promise that resolves to the system statistics + * @throws {Error} If the API call fails or returns an invalid response + * */ +export const getSystemStats = async (): Promise => { + const response = await apiCall(`${ADMIN_BASE_URL}/stats`); + if (!response.ok) { + const data = await response.json(); + const errorData = data as { message: string }; + throw new Error(errorData.message || 'Failed to fetch system stats'); + } + const data = await response.json(); + if (!isSystemStats(data)) { + throw new Error('Invalid system stats response received from API'); + } + return data as SystemStats; +}; diff --git a/app/src/types/adminApi.ts b/app/src/types/adminApi.ts new file mode 100644 index 0000000..b70f3c0 --- /dev/null +++ b/app/src/types/adminApi.ts @@ -0,0 +1,60 @@ +import { UserRole } from './authApi'; + +// CreateUserRequest holds the request fields for creating a new user +export interface CreateUserRequest { + email: string; + displayName: string; + password: string; + role: UserRole; +} + +// UpdateUserRequest holds the request fields for updating a user +export interface UpdateUserRequest { + email?: string; + displayName?: string; + password?: string; + role?: UserRole; +} + +// WorkspaceStats holds workspace statistics +export interface WorkspaceStats { + userID: number; + userEmail: string; + workspaceID: number; + workspaceName: string; + workspaceCreatedAt: string; // Using ISO string format for time.Time + fileCountStats?: FileCountStats; +} + +// Define FileCountStats based on the Go struct definition of storage.FileCountStats +export interface FileCountStats { + totalFiles: number; + totalSize: number; +} + +export interface UserStats { + totalUsers: number; + totalWorkspaces: number; + activeUsers: number; // Users with activity in last 30 days +} + +// SystemStats holds system-wide statistics +export interface SystemStats extends FileCountStats, UserStats {} + +// isSystemStats checks if the given object is a valid SystemStats object +export function isSystemStats(obj: unknown): obj is SystemStats { + return ( + typeof obj === 'object' && + obj !== null && + 'totalUsers' in obj && + typeof (obj as SystemStats).totalUsers === 'number' && + 'totalWorkspaces' in obj && + typeof (obj as SystemStats).totalWorkspaces === 'number' && + 'activeUsers' in obj && + typeof (obj as SystemStats).activeUsers === 'number' && + 'totalFiles' in obj && + typeof (obj as SystemStats).totalFiles === 'number' && + 'totalSize' in obj && + typeof (obj as SystemStats).totalSize === 'number' + ); +} diff --git a/app/src/types/workspace.ts b/app/src/types/workspace.ts index a757e7e..15ec4f2 100644 --- a/app/src/types/workspace.ts +++ b/app/src/types/workspace.ts @@ -30,3 +30,28 @@ export const DEFAULT_WORKSPACE: Workspace = { name: '', ...DEFAULT_WORKSPACE_SETTINGS, }; + +export function isWorkspace(obj: unknown): obj is Workspace { + return ( + typeof obj === 'object' && + obj !== null && + 'name' in obj && + typeof (obj as Workspace).name === 'string' && + 'theme' in obj && + typeof (obj as Workspace).theme === 'string' && + 'autoSave' in obj && + typeof (obj as Workspace).autoSave === 'boolean' && + 'gitEnabled' in obj && + typeof (obj as Workspace).gitEnabled === 'boolean' && + 'gitUrl' in obj && + typeof (obj as Workspace).gitUrl === 'string' && + 'gitUser' in obj && + typeof (obj as Workspace).gitUser === 'string' && + 'gitToken' in obj && + typeof (obj as Workspace).gitToken === 'string' && + 'gitAutoCommit' in obj && + typeof (obj as Workspace).gitAutoCommit === 'boolean' && + 'gitCommitMsgTemplate' in obj && + typeof (obj as Workspace).gitCommitMsgTemplate === 'string' + ); +}