import { User, NoteItem, ChatMessage, AppNotification, GroupInfo, VoiceCall, ChatUser, GroupMember } from '../types'; const TOKEN_KEY = 'school_archive_token'; const SAVED_USER_KEY = 'school_archive_saved_user'; export function getStoredToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setStoredToken(token: string, username?: string): void { localStorage.setItem(TOKEN_KEY, token); if (username) { localStorage.setItem(SAVED_USER_KEY, username); } } export function clearStoredToken(): void { localStorage.removeItem(TOKEN_KEY); // Keep SAVED_USER_KEY so username input can auto-fill if desired, or clear } export function getSavedUsername(): string | null { return localStorage.getItem(SAVED_USER_KEY); } async function request(endpoint: string, options: RequestInit = {}): Promise { const token = getStoredToken(); const headers: Record = { ...(options.headers as Record), }; if (token) { headers['Authorization'] = `Bearer ${token}`; } // Only set Content-Type to application/json if body is NOT FormData if (options.body && !(options.body instanceof FormData) && !headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } const res = await fetch(endpoint, { ...options, headers, }); const contentType = res.headers.get('content-type') || ''; let data: any = {}; if (contentType.includes('application/json')) { try { data = await res.json(); } catch (err: any) { console.error('JSON parsing failed:', err); data = {}; } } else { // If response is non-JSON (e.g. HTML 404/500 or Vite fallback) const text = await res.text(); if (!res.ok) { if (res.status === 401) { clearStoredToken(); window.dispatchEvent(new Event('auth_unauthorized')); } throw new Error(`Server request failed (${res.status} ${res.statusText || 'Error'}). Response: ${text.substring(0, 100)}...`); } throw new Error(`Received non-JSON response from server (Content-Type: ${contentType}). Response: ${text.substring(0, 100)}...`); } if (!res.ok) { if (res.status === 401) { clearStoredToken(); window.dispatchEvent(new Event('auth_unauthorized')); } throw new Error(data.error || `Request failed with status ${res.status}`); } return data as T; } export const api = { // AUTH async login(username: string, password: string): Promise<{ token: string; user: User }> { const data = await request<{ token: string; user: User }>('/api/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); setStoredToken(data.token, data.user.username); return data; }, async getMe(): Promise { const data = await request<{ user: User }>('/api/auth/me'); return data.user; }, async changePassword(newPassword: string): Promise { const data = await request<{ message: string }>('/api/auth/change-password', { method: 'POST', body: JSON.stringify({ newPassword }), }); return data.message; }, // USER MANAGEMENT async getUsers(): Promise { const data = await request<{ users: User[] }>('/api/users'); return data.users; }, async createUser(userData: { username: string; password: string; role?: 'admin' | 'user'; permissions?: any; }): Promise<{ user: User; message: string }> { return request<{ user: User; message: string }>('/api/users', { method: 'POST', body: JSON.stringify(userData), }); }, async updateUser( username: string, updateData: { password?: string; permissions?: any; role?: 'admin' | 'user' } ): Promise { const data = await request<{ message: string }>(`/api/users/${encodeURIComponent(username)}`, { method: 'PUT', body: JSON.stringify(updateData), }); return data.message; }, async deleteUser(username: string): Promise { const data = await request<{ message: string }>(`/api/users/${encodeURIComponent(username)}`, { method: 'DELETE', }); return data.message; }, // NOTES async getNotes(subject?: string, subSubject?: string, search?: string): Promise { const params = new URLSearchParams(); if (subject) params.append('subject', subject); if (subSubject) params.append('subSubject', subSubject); if (search) params.append('search', search); const queryStr = params.toString() ? `?${params.toString()}` : ''; const data = await request<{ notes: NoteItem[] }>(`/api/notes${queryStr}`); return data.notes; }, async uploadNote( file: File, subject: string, subSubject?: string, description?: string ): Promise<{ note: NoteItem; message: string }> { const formData = new FormData(); formData.append('file', file); formData.append('subject', subject); if (subSubject) formData.append('subSubject', subSubject); if (description) formData.append('description', description); return request<{ note: NoteItem; message: string }>('/api/notes/upload', { method: 'POST', body: formData, }); }, async deleteNote(noteId: string): Promise { const data = await request<{ message: string }>(`/api/notes/${noteId}`, { method: 'DELETE', }); return data.message; }, // GROUPS & CHATS async getGroups(): Promise { const data = await request<{ groups: GroupInfo[] }>('/api/groups'); return data.groups; }, async createGroup(name: string, description?: string): Promise<{ group: GroupInfo; message: string }> { return request<{ group: GroupInfo; message: string }>('/api/groups', { method: 'POST', body: JSON.stringify({ name, description }), }); }, async deleteGroup(groupName: string): Promise { const data = await request<{ message: string }>(`/api/groups/${encodeURIComponent(groupName)}`, { method: 'DELETE', }); return data.message; }, async removeGroupMember(groupName: string, username: string): Promise { const data = await request<{ message: string }>('/api/groups/remove-member', { method: 'POST', body: JSON.stringify({ groupName, username }), }); return data.message; }, async getGroupMembers(groupName: string): Promise { const data = await request<{ members: GroupMember[] }>(`/api/groups/${encodeURIComponent(groupName)}/members`); return data.members; }, async blockUser(targetUsername: string, action: 'block' | 'unblock'): Promise<{ blockedUsers: string[]; message: string }> { return request<{ blockedUsers: string[]; message: string }>('/api/users/block', { method: 'POST', body: JSON.stringify({ targetUsername, action }), }); }, async joinLeaveGroup(groupName: string, action: 'join' | 'leave'): Promise { const data = await request<{ joinedGroups: string[]; message: string }>('/api/groups/join-leave', { method: 'POST', body: JSON.stringify({ groupName, action }), }); return data.joinedGroups; }, async getChats(group?: string): Promise { const query = group ? `?group=${encodeURIComponent(group)}` : ''; const data = await request<{ chats: ChatMessage[] }>(`/api/chats${query}`); return data.chats; }, async uploadChatFile(file: File, videoDuration?: number): Promise { const formData = new FormData(); formData.append('file', file); if (videoDuration !== undefined) { formData.append('videoDuration', videoDuration.toString()); } const data = await request<{ attachment: any }>('/api/chats/upload-file', { method: 'POST', body: formData, }); return data.attachment; }, async sendChatMessage(group: string, text?: string, attachment?: any): Promise { const data = await request<{ message: ChatMessage }>('/api/chats', { method: 'POST', body: JSON.stringify({ group, text, attachment }), }); return data.message; }, // NOTIFICATIONS async getNotifications(): Promise { const data = await request<{ notifications: AppNotification[] }>('/api/notifications'); return data.notifications; }, async markNotificationsRead(): Promise { await request('/api/notifications/read', { method: 'POST' }); }, // VOICE MEETINGS / CALLS async getCalls(group?: string): Promise { const query = group ? `?group=${encodeURIComponent(group)}` : ''; const data = await request<{ calls: VoiceCall[] }>(`/api/calls${query}`); return data.calls; }, async createCall(group: string, mode: 'public' | 'secret', invitedUsers: string[] = []): Promise { const data = await request<{ call: VoiceCall }>('/api/calls', { method: 'POST', body: JSON.stringify({ group, mode, invitedUsers }), }); return data.call; }, async joinCall(callId: string): Promise { const data = await request<{ call: VoiceCall }>(`/api/calls/${encodeURIComponent(callId)}/join`, { method: 'POST', }); return data.call; }, async leaveCall(callId: string): Promise { await request(`/api/calls/${encodeURIComponent(callId)}/leave`, { method: 'POST', }); }, async sendCallSignal(callId: string, to: string, type: string, data: any): Promise { await request(`/api/calls/${encodeURIComponent(callId)}/signals`, { method: 'POST', body: JSON.stringify({ to, type, data }), }); }, async getCallSignals(callId: string): Promise { const data = await request<{ signals: any[] }>(`/api/calls/${encodeURIComponent(callId)}/signals`); return data.signals; }, async getChatUsers(group: string): Promise { const query = group ? `?group=${encodeURIComponent(group)}` : ''; const data = await request<{ users: ChatUser[] }>(`/api/chat-users${query}`); return data.users; }, };