639 lines
21 KiB
TypeScript
639 lines
21 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, turnstileToken?: string) {
|
|
const body: Record<string, string> = { email, password };
|
|
if (turnstileToken) body.turnstile_token = turnstileToken;
|
|
const data = await this.request<{ access_token: string; user: any }>('/api/v1/admin/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify(body),
|
|
});
|
|
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' });
|
|
}
|
|
|
|
async adminUpdateProfile(id: string, fields: Record<string, any>) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/profile`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(fields),
|
|
});
|
|
}
|
|
|
|
async adminListFollows(id: string, relation: 'followers' | 'following', limit = 50) {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/follows?relation=${relation}&limit=${limit}`);
|
|
}
|
|
|
|
async adminManageFollow(id: string, action: 'add' | 'remove', userId: string, relation: 'follower' | 'following') {
|
|
return this.request<any>(`/api/v1/admin/users/${id}/follows`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action, user_id: userId, relation }),
|
|
});
|
|
}
|
|
|
|
// 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' });
|
|
}
|
|
|
|
async bulkUpdatePosts(ids: string[], action: string, reason?: string) {
|
|
return this.request<any>('/api/v1/admin/posts/bulk', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ids, action, reason }),
|
|
});
|
|
}
|
|
|
|
async bulkUpdateUsers(ids: string[], action: string, reason?: string) {
|
|
return this.request<any>('/api/v1/admin/users/bulk', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ids, action, reason }),
|
|
});
|
|
}
|
|
|
|
// 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 }),
|
|
});
|
|
}
|
|
|
|
async bulkReviewModeration(ids: string[], action: string, reason?: string) {
|
|
return this.request<any>('/api/v1/admin/moderation/bulk', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ids, 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 }),
|
|
});
|
|
}
|
|
|
|
async bulkUpdateReports(ids: string[], action: string) {
|
|
return this.request<any>('/api/v1/admin/reports/bulk', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ids, action }),
|
|
});
|
|
}
|
|
|
|
// 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),
|
|
});
|
|
}
|
|
|
|
// Neighborhoods
|
|
async listNeighborhoods(params: {
|
|
limit?: number;
|
|
offset?: number;
|
|
search?: string;
|
|
zip?: string;
|
|
sort?: 'name' | 'zip' | 'members' | 'created';
|
|
order?: 'asc' | 'desc';
|
|
} = {}) {
|
|
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.zip) qs.set('zip', params.zip);
|
|
if (params.sort) qs.set('sort', params.sort);
|
|
if (params.order) qs.set('order', params.order);
|
|
return this.request<any>(`/api/v1/admin/neighborhoods?${qs}`);
|
|
}
|
|
|
|
async setNeighborhoodAdmin(id: string, userId: string, action: 'assign' | 'remove') {
|
|
return this.request<any>(`/api/v1/admin/neighborhoods/${id}/admins`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ user_id: userId, action }),
|
|
});
|
|
}
|
|
|
|
async listNeighborhoodAdmins(id: string) {
|
|
return this.request<any>(`/api/v1/admin/neighborhoods/${id}/admins`);
|
|
}
|
|
|
|
async listNeighborhoodBoardEntries(id: string, params: {
|
|
limit?: number;
|
|
offset?: number;
|
|
search?: string;
|
|
active?: 'true' | 'false';
|
|
} = {}) {
|
|
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.active) qs.set('active', params.active);
|
|
return this.request<any>(`/api/v1/admin/neighborhoods/${id}/board?${qs}`);
|
|
}
|
|
|
|
async updateNeighborhoodBoardEntry(id: string, entryId: string, isActive: boolean) {
|
|
return this.request<any>(`/api/v1/admin/neighborhoods/${id}/board/${entryId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ is_active: isActive }),
|
|
});
|
|
}
|
|
|
|
async pinNeighborhoodBoardEntry(id: string, entryId: string, isPinned: boolean) {
|
|
return this.request<any>(`/api/v1/admin/neighborhoods/${id}/board/${entryId}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ is_pinned: isPinned }),
|
|
});
|
|
}
|
|
|
|
// 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}`);
|
|
}
|
|
|
|
// R2 Storage
|
|
async getStorageStats() {
|
|
return this.request<any>('/api/v1/admin/storage/stats');
|
|
}
|
|
|
|
async listStorageObjects(params: { bucket?: string; prefix?: string; cursor?: string; limit?: number } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.bucket) qs.set('bucket', params.bucket);
|
|
if (params.prefix) qs.set('prefix', params.prefix);
|
|
if (params.cursor) qs.set('cursor', params.cursor);
|
|
if (params.limit) qs.set('limit', String(params.limit));
|
|
return this.request<any>(`/api/v1/admin/storage/objects?${qs}`);
|
|
}
|
|
|
|
async getStorageObject(bucket: string, key: string) {
|
|
const qs = new URLSearchParams({ bucket, key });
|
|
return this.request<any>(`/api/v1/admin/storage/object?${qs}`);
|
|
}
|
|
|
|
async deleteStorageObject(bucket: string, key: string) {
|
|
return this.request<any>('/api/v1/admin/storage/object', {
|
|
method: 'DELETE',
|
|
body: JSON.stringify({ bucket, key }),
|
|
});
|
|
}
|
|
|
|
// Reserved Usernames
|
|
async listReservedUsernames(params: { category?: string; search?: string; limit?: number; offset?: number } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.category) qs.set('category', params.category);
|
|
if (params.search) qs.set('search', params.search);
|
|
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/usernames/reserved?${qs}`);
|
|
}
|
|
|
|
async addReservedUsername(data: { username: string; category?: string; reason?: string }) {
|
|
return this.request<any>('/api/v1/admin/usernames/reserved', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async bulkAddReservedUsernames(data: { usernames: string[]; category?: string; reason?: string }) {
|
|
return this.request<any>('/api/v1/admin/usernames/reserved/bulk', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async removeReservedUsername(id: string) {
|
|
return this.request<any>(`/api/v1/admin/usernames/reserved/${id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
// Username Claim Requests
|
|
async listClaimRequests(params: { status?: string; limit?: number; offset?: number } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.status) qs.set('status', params.status);
|
|
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/usernames/claims?${qs}`);
|
|
}
|
|
|
|
async reviewClaimRequest(id: string, decision: string, notes?: string) {
|
|
return this.request<any>(`/api/v1/admin/usernames/claims/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ decision, notes }),
|
|
});
|
|
}
|
|
|
|
// AI Engines
|
|
async getAIEngines() {
|
|
return this.request<any>('/api/v1/admin/ai-engines');
|
|
}
|
|
|
|
// AI Moderation
|
|
async listOpenRouterModels(params: { capability?: string; search?: string } = {}) {
|
|
const qs = new URLSearchParams();
|
|
if (params.capability) qs.set('capability', params.capability);
|
|
if (params.search) qs.set('search', params.search);
|
|
return this.request<any>(`/api/v1/admin/ai/models?${qs}`);
|
|
}
|
|
|
|
async listLocalModels() {
|
|
return this.request<any>('/api/v1/admin/ai/models/local');
|
|
}
|
|
|
|
async getAIModerationConfigs() {
|
|
return this.request<any>('/api/v1/admin/ai/config');
|
|
}
|
|
|
|
async setAIModerationConfig(data: { moderation_type: string; model_id: string; model_name: string; system_prompt: string; enabled: boolean; engines?: string[] }) {
|
|
return this.request<any>('/api/v1/admin/ai/config', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async testAIModeration(data: { moderation_type: string; content?: string; image_url?: string; engine?: string }) {
|
|
return this.request<any>('/api/v1/admin/ai/test', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async uploadTestImage(file: File) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
const token = this.getToken();
|
|
const headers: Record<string, string> = {};
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const response = await fetch(`${API_BASE}/api/v1/admin/upload-test-image`, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`Upload failed: ${response.status} - ${errorText}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
// AI Moderation Audit Log
|
|
async getAIModerationLog(params: { limit?: number; offset?: number; decision?: string; content_type?: string; search?: string; feedback?: 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.decision) qs.set('decision', params.decision);
|
|
if (params.content_type) qs.set('content_type', params.content_type);
|
|
if (params.search) qs.set('search', params.search);
|
|
if (params.feedback) qs.set('feedback', params.feedback);
|
|
return this.request<any>(`/api/v1/admin/ai/moderation-log?${qs}`);
|
|
}
|
|
|
|
async submitAIModerationFeedback(id: string, correct: boolean, reason: string) {
|
|
return this.request<any>(`/api/v1/admin/ai/moderation-log/${id}/feedback`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ correct, reason }),
|
|
});
|
|
}
|
|
|
|
async exportAITrainingData() {
|
|
return this.request<any>('/api/v1/admin/ai/training-data');
|
|
}
|
|
|
|
// Admin Content Tools
|
|
async adminCreateUser(data: {
|
|
email: string;
|
|
password: string;
|
|
handle: string;
|
|
display_name: string;
|
|
bio?: string;
|
|
role?: string;
|
|
verified?: boolean;
|
|
official?: boolean;
|
|
skip_email?: boolean;
|
|
}) {
|
|
return this.request<any>('/api/v1/admin/users/create', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
// Official Accounts
|
|
async listOfficialProfiles() {
|
|
return this.request<any>('/api/v1/admin/official-profiles');
|
|
}
|
|
|
|
async listOfficialAccounts() {
|
|
return this.request<any>('/api/v1/admin/official-accounts');
|
|
}
|
|
|
|
async getOfficialAccount(id: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}`);
|
|
}
|
|
|
|
async upsertOfficialAccount(data: any) {
|
|
return this.request<any>('/api/v1/admin/official-accounts', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async deleteOfficialAccount(id: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
async toggleOfficialAccount(id: string, enabled: boolean) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}/toggle`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify({ enabled }),
|
|
});
|
|
}
|
|
|
|
async triggerOfficialPost(id: string, count?: number | 'all') {
|
|
const q = count !== undefined ? `?count=${count}` : '';
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}/trigger${q}`, { method: 'POST' });
|
|
}
|
|
|
|
async previewOfficialPost(id: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}/preview`, { method: 'POST' });
|
|
}
|
|
|
|
async fetchNewsArticles(id: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}/articles`);
|
|
}
|
|
|
|
async getPostedArticles(id: string, limit = 50, status = 'posted') {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${id}/posted?limit=${limit}&status=${status}`);
|
|
}
|
|
|
|
async skipArticle(articleId: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/articles/${articleId}/skip`, { method: 'POST' });
|
|
}
|
|
|
|
async deleteArticle(articleId: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/articles/${articleId}`, { method: 'DELETE' });
|
|
}
|
|
|
|
async postSpecificArticle(articleId: string) {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/articles/${articleId}/post`, { method: 'POST' });
|
|
}
|
|
|
|
async cleanupPendingArticles(configId: string, before: string, action: 'skip' | 'delete') {
|
|
return this.request<any>(`/api/v1/admin/official-accounts/${configId}/articles/cleanup`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ before, action }),
|
|
});
|
|
}
|
|
|
|
async adminImportContent(data: {
|
|
author_id: string;
|
|
content_type: string;
|
|
items: Array<{
|
|
body?: string;
|
|
media_url?: string;
|
|
thumbnail_url?: string;
|
|
duration_ms?: number;
|
|
tags?: string[];
|
|
category_id?: string;
|
|
is_nsfw?: boolean;
|
|
nsfw_reason?: string;
|
|
visibility?: string;
|
|
beacon_type?: string;
|
|
lat?: number;
|
|
long?: number;
|
|
}>;
|
|
}) {
|
|
return this.request<any>('/api/v1/admin/content/import', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
// Safe Domains
|
|
async listSafeDomains(category?: string) {
|
|
const params = category ? `?category=${category}` : '';
|
|
return this.request<any>(`/api/v1/admin/safe-domains${params}`);
|
|
}
|
|
|
|
async upsertSafeDomain(data: { domain: string; category: string; is_approved: boolean; notes: string }) {
|
|
return this.request<any>('/api/v1/admin/safe-domains', { method: 'POST', body: JSON.stringify(data) });
|
|
}
|
|
|
|
async deleteSafeDomain(id: string) {
|
|
return this.request<any>(`/api/v1/admin/safe-domains/${id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
async checkURLSafety(url: string) {
|
|
return this.request<any>(`/api/v1/admin/safe-domains/check?url=${encodeURIComponent(url)}`);
|
|
}
|
|
|
|
// Email Templates
|
|
async listEmailTemplates() {
|
|
return this.request<any>('/api/v1/admin/email-templates');
|
|
}
|
|
|
|
async getEmailTemplate(id: string) {
|
|
return this.request<any>(`/api/v1/admin/email-templates/${id}`);
|
|
}
|
|
|
|
async updateEmailTemplate(id: string, data: Record<string, any>) {
|
|
return this.request<any>(`/api/v1/admin/email-templates/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
async sendTestEmail(templateId: string, toEmail: string) {
|
|
return this.request<any>('/api/v1/admin/email-templates/test', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ template_id: templateId, to_email: toEmail }),
|
|
});
|
|
}
|
|
}
|
|
|
|
export const api = new ApiClient();
|