Files
compor-academy/services/api.ts
Cauê Faleiros 4736fb5208
All checks were successful
CI / Validate frontend and API (push) Successful in 50s
CI / Build and publish Docker images (push) Successful in 20s
feat: expand admin and instructor tools
2026-09-01 13:19:29 -03:00

115 lines
3.9 KiB
TypeScript

const API_URL = import.meta.env.VITE_API_URL || '/api/v1';
const SESSION_KEY = 'compor_hub_session';
export type ApiRole = 'student' | 'instructor' | 'admin';
export interface ApiUser {
id: string;
email: string;
name: string;
role: ApiRole;
}
export interface ManagedUser {
id: string;
email: string;
name: string;
role: ApiRole;
isActive: boolean;
createdAt: string;
}
export interface ManagedUserDetail extends ManagedUser {
completedLessons: number;
lastLearningAt: string | null;
}
export interface PlatformAnalytics { totalUsers: number; activeUsers: number; publishedCourses: number; completedLessons: number; comments: number; }
export interface InstructorAnalytics { courses: number; lessons: number; learners: number; completedLessons: number; comments: number; }
interface Session {
token: string;
user: ApiUser;
}
export class ApiError extends Error {
constructor(message: string, public status: number) {
super(message);
}
}
export function getSession(): Session | null {
const value = localStorage.getItem(SESSION_KEY);
if (!value) return null;
try {
return JSON.parse(value) as Session;
} catch {
localStorage.removeItem(SESSION_KEY);
return null;
}
}
export function saveSession(session: Session) {
localStorage.setItem(SESSION_KEY, JSON.stringify(session));
}
export function clearSession() {
localStorage.removeItem(SESSION_KEY);
}
export async function apiRequest<T>(path: string, options: RequestInit = {}): Promise<T> {
const session = getSession();
const headers = new Headers(options.headers);
if (options.body) headers.set('Content-Type', 'application/json');
if (session) headers.set('Authorization', `Bearer ${session.token}`);
const response = await fetch(`${API_URL}${path}`, { ...options, headers });
if (response.status === 204) return undefined as T;
const body = await response.json().catch(() => ({}));
if (!response.ok) {
throw new ApiError(body.error || 'The request could not be completed', response.status);
}
return body as T;
}
export const authApi = {
async login(email: string, password: string) {
return apiRequest<Session>('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) });
},
async register(name: string, email: string, password: string) {
return apiRequest<Session>('/auth/register', { method: 'POST', body: JSON.stringify({ name, email, password }) });
},
async me() {
return apiRequest<{ user: ApiUser }>('/auth/me');
},
async acceptInvitation(token: string, name: string, password: string) {
return apiRequest<Session>('/auth/accept-invitation', { method: 'POST', body: JSON.stringify({ token, name, password }) });
},
async resetPassword(token: string, password: string) {
return apiRequest<void>('/auth/reset-password', { method: 'POST', body: JSON.stringify({ token, password }) });
},
};
export const adminApi = {
async dashboard() { return apiRequest<{ data: PlatformAnalytics }>('/admin/dashboard'); },
async listUsers() {
return apiRequest<{ data: ManagedUser[] }>('/admin/users');
},
async updateUser(userId: string, update: Partial<Pick<ManagedUser, 'role' | 'isActive'>>) {
return apiRequest<{ data: ManagedUser }>(`/admin/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(update),
});
},
async invite(email: string, role: 'student' | 'instructor') {
return apiRequest<{ data: { inviteUrl: string } }>('/admin/invitations', { method: 'POST', body: JSON.stringify({ email, role }) });
},
async passwordReset(userId: string) {
return apiRequest<{ data: { resetUrl: string } }>(`/admin/users/${userId}/password-reset`, { method: 'POST' });
},
async userDetail(userId: string) { return apiRequest<{ data: ManagedUserDetail }>(`/admin/users/${userId}`); },
};
export const instructorApi = {
async analytics() { return apiRequest<{ data: InstructorAnalytics }>('/manage/courses/analytics'); },
};