Notes / src /services /api.ts
harma-boutique-2026's picture
Upload 26 files
f975c6d verified
Raw History Blame Contribute Delete
10.1 kB
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<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const token = getStoredToken();
const headers: Record<string, string> = {
...(options.headers as Record<string, string>),
};
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<User> {
const data = await request<{ user: User }>('/api/auth/me');
return data.user;
},
async changePassword(newPassword: string): Promise<string> {
const data = await request<{ message: string }>('/api/auth/change-password', {
method: 'POST',
body: JSON.stringify({ newPassword }),
});
return data.message;
},
// USER MANAGEMENT
async getUsers(): Promise<User[]> {
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<string> {
const data = await request<{ message: string }>(`/api/users/${encodeURIComponent(username)}`, {
method: 'PUT',
body: JSON.stringify(updateData),
});
return data.message;
},
async deleteUser(username: string): Promise<string> {
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<NoteItem[]> {
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<string> {
const data = await request<{ message: string }>(`/api/notes/${noteId}`, {
method: 'DELETE',
});
return data.message;
},
// GROUPS & CHATS
async getGroups(): Promise<GroupInfo[]> {
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<string> {
const data = await request<{ message: string }>(`/api/groups/${encodeURIComponent(groupName)}`, {
method: 'DELETE',
});
return data.message;
},
async removeGroupMember(groupName: string, username: string): Promise<string> {
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<GroupMember[]> {
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<string[]> {
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<ChatMessage[]> {
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<any> {
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<ChatMessage> {
const data = await request<{ message: ChatMessage }>('/api/chats', {
method: 'POST',
body: JSON.stringify({ group, text, attachment }),
});
return data.message;
},
// NOTIFICATIONS
async getNotifications(): Promise<AppNotification[]> {
const data = await request<{ notifications: AppNotification[] }>('/api/notifications');
return data.notifications;
},
async markNotificationsRead(): Promise<void> {
await request('/api/notifications/read', { method: 'POST' });
},
// VOICE MEETINGS / CALLS
async getCalls(group?: string): Promise<VoiceCall[]> {
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<VoiceCall> {
const data = await request<{ call: VoiceCall }>('/api/calls', {
method: 'POST',
body: JSON.stringify({ group, mode, invitedUsers }),
});
return data.call;
},
async joinCall(callId: string): Promise<VoiceCall> {
const data = await request<{ call: VoiceCall }>(`/api/calls/${encodeURIComponent(callId)}/join`, {
method: 'POST',
});
return data.call;
},
async leaveCall(callId: string): Promise<void> {
await request(`/api/calls/${encodeURIComponent(callId)}/leave`, {
method: 'POST',
});
},
async sendCallSignal(callId: string, to: string, type: string, data: any): Promise<void> {
await request(`/api/calls/${encodeURIComponent(callId)}/signals`, {
method: 'POST',
body: JSON.stringify({ to, type, data }),
});
},
async getCallSignals(callId: string): Promise<any[]> {
const data = await request<{ signals: any[] }>(`/api/calls/${encodeURIComponent(callId)}/signals`);
return data.signals;
},
async getChatUsers(group: string): Promise<ChatUser[]> {
const query = group ? `?group=${encodeURIComponent(group)}` : '';
const data = await request<{ users: ChatUser[] }>(`/api/chat-users${query}`);
return data.users;
},
};