232 lines
7.5 KiB
TypeScript
232 lines
7.5 KiB
TypeScript
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://api.sojorn.net';
|
|
|
|
class ApiClient {
|
|
private token: string | null = null;
|
|
|
|
setToken(token: string | null) {
|
|
this.token = token;
|
|
if (token) {
|
|
if (typeof window !== 'undefined') localStorage.setItem('admin_token', token);
|
|
} else {
|
|
if (typeof window !== 'undefined') localStorage.removeItem('admin_token');
|
|
}
|
|
}
|
|
|
|
getToken(): string | null {
|
|
if (this.token) return this.token;
|
|
if (typeof window !== 'undefined') {
|
|
this.token = localStorage.getItem('admin_token');
|
|
}
|
|
return this.token;
|
|
}
|
|
|
|
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
const token = this.getToken();
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const res = await fetch(`${API_BASE}${path}`, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (res.status === 401) {
|
|
this.setToken(null);
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/login';
|
|
}
|
|
throw new Error('Unauthorized');
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
throw new Error(body.error || `Request failed: ${res.status}`);
|
|
}
|
|
|
|
return res.json();
|
|
}
|
|
|
|
// Auth
|
|
async login(email: string, password: string) {
|
|
const data = await this.request<{ access_token: string; user: any }>('/api/v1/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ email, password }),
|
|
});
|
|
this.setToken(data.access_token);
|
|
return data;
|
|
}
|
|
|
|
// Dashboard
|
|
async getDashboardStats() {
|
|
return this.request<any>('/api/v1/admin/dashboard');
|
|
}
|
|
|
|
async getGrowthStats(days = 30) {
|
|
return this.request<any>(`/api/v1/admin/growth?days=${days}`);
|
|
}
|
|
|
|
// Users
|
|
async listUsers(params: { limit?: number; offset?: number; search?: string; status?: string; role?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
if (params.search) qs.set('search', params.search);
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.role) qs.set('role', params.role);
|
|
return this.request<any>(`/api/v1/admin/users?${qs}`);
|
|
}
|
|
|
|
async getUser(id: string) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}`);
|
|
}
|
|
|
|
async updateUserStatus(id: string, status: string, reason: string) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/status`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ status, reason }),
|
|
});
|
|
}
|
|
|
|
async updateUserRole(id: string, role: string) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/role`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ role }),
|
|
});
|
|
}
|
|
|
|
async updateUserVerification(id: string, isOfficial: boolean, isVerified: boolean) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/verification`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ is_official: isOfficial, is_verified: isVerified }),
|
|
});
|
|
}
|
|
|
|
async resetUserStrikes(id: string) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/reset-strikes`, { method: 'POST' });
|
|
}
|
|
|
|
// Posts
|
|
async listPosts(params: { limit?: number; offset?: number; search?: string; status?: string; author_id?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
if (params.search) qs.set('search', params.search);
|
|
if (params.status) qs.set('status', params.status);
|
|
if (params.author_id) qs.set('author_id', params.author_id);
|
|
return this.request<any>(`/api/v1/admin/posts?${qs}`);
|
|
}
|
|
|
|
async getPost(id: string) {
|
|
return this.request<any>(`/api/v1/admin/posts/${id}`);
|
|
}
|
|
|
|
async updatePostStatus(id: string, status: string, reason?: string) {
|
|
return this.request<any>(`/api/v1/admin/posts/${id}/status`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ status, reason }),
|
|
});
|
|
}
|
|
|
|
async deletePost(id: string) {
|
|
return this.request<any>(`/api/v1/admin/posts/${id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
// Moderation
|
|
async getModerationQueue(params: { limit?: number; offset?: number; status?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
if (params.status) qs.set('status', params.status || 'pending');
|
|
return this.request<any>(`/api/v1/admin/moderation?${qs}`);
|
|
}
|
|
|
|
async reviewModerationFlag(id: string, action: string, reason?: string) {
|
|
return this.request<any>(`/api/v1/admin/moderation/${id}/review`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ action, reason }),
|
|
});
|
|
}
|
|
|
|
// Appeals
|
|
async listAppeals(params: { limit?: number; offset?: number; status?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
if (params.status) qs.set('status', params.status || 'pending');
|
|
return this.request<any>(`/api/v1/admin/appeals?${qs}`);
|
|
}
|
|
|
|
async reviewAppeal(id: string, decision: string, reviewDecision: string, restoreContent = false) {
|
|
return this.request<any>(`/api/v1/admin/appeals/${id}/review`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ decision, review_decision: reviewDecision, restore_content: restoreContent }),
|
|
});
|
|
}
|
|
|
|
// Reports
|
|
async listReports(params: { limit?: number; offset?: number; status?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
if (params.status) qs.set('status', params.status || 'pending');
|
|
return this.request<any>(`/api/v1/admin/reports?${qs}`);
|
|
}
|
|
|
|
async updateReportStatus(id: string, status: string) {
|
|
return this.request<any>(`/api/v1/admin/reports/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ status }),
|
|
});
|
|
}
|
|
|
|
// Algorithm
|
|
async getAlgorithmConfig() {
|
|
return this.request<any>('/api/v1/admin/algorithm');
|
|
}
|
|
|
|
async updateAlgorithmConfig(key: string, value: string) {
|
|
return this.request<any>('/api/v1/admin/algorithm', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({ key, value }),
|
|
});
|
|
}
|
|
|
|
// Categories
|
|
async listCategories() {
|
|
return this.request<any>('/api/v1/admin/categories');
|
|
}
|
|
|
|
async createCategory(data: { slug: string; name: string; description?: string; is_sensitive?: boolean }) {
|
|
return this.request<any>('/api/v1/admin/categories', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async updateCategory(id: string, data: { name?: string; description?: string; is_sensitive?: boolean }) {
|
|
return this.request<any>(`/api/v1/admin/categories/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
// System
|
|
async getSystemHealth() {
|
|
return this.request<any>('/api/v1/admin/health');
|
|
}
|
|
|
|
async getAuditLog(params: { limit?: number; offset?: number } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
if (params.offset) qs.set('offset', String(params.offset));
|
|
return this.request<any>(`/api/v1/admin/audit-log?${qs}`);
|
|
}
|
|
}
|
|
|
|
export const api = new ApiClient();
|