mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 16:31:12 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
31
src-old/services/reports/index.ts
Normal file
31
src-old/services/reports/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Reports Service - Main export file
|
||||
* Centralized exports for the Django Reports API integration
|
||||
*/
|
||||
|
||||
// Main service instance (singleton)
|
||||
export { reportsService, ReportsService } from './reportsService';
|
||||
|
||||
// Type definitions
|
||||
export type {
|
||||
Report,
|
||||
ReportStatus,
|
||||
ReportType,
|
||||
EntityType,
|
||||
SubmitReportData,
|
||||
LegacySubmitReportData,
|
||||
ReportFilters,
|
||||
PaginatedReports,
|
||||
ReportStats,
|
||||
UpdateReportStatusData,
|
||||
LegacyReport,
|
||||
ServiceResponse,
|
||||
} from './types';
|
||||
|
||||
// Data transformation functions
|
||||
export {
|
||||
mapSubmitReportToBackend,
|
||||
mapReportToLegacy,
|
||||
mapReportsToLegacy,
|
||||
extractUsernameFromEmail,
|
||||
} from './mappers';
|
||||
90
src-old/services/reports/mappers.ts
Normal file
90
src-old/services/reports/mappers.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Data transformation functions for Reports API
|
||||
* Handles bidirectional mapping between frontend (Supabase) and backend (Django) formats
|
||||
*/
|
||||
|
||||
import type {
|
||||
Report,
|
||||
LegacyReport,
|
||||
SubmitReportData,
|
||||
LegacySubmitReportData,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Map legacy frontend submission data to Django format
|
||||
* Converts Supabase field names to Django field names
|
||||
*
|
||||
* @param data - Report submission data with Supabase field names
|
||||
* @returns Report submission data with Django field names
|
||||
*/
|
||||
export function mapSubmitReportToBackend(
|
||||
data: LegacySubmitReportData
|
||||
): SubmitReportData {
|
||||
return {
|
||||
entity_type: data.reported_entity_type,
|
||||
entity_id: data.reported_entity_id,
|
||||
report_type: data.report_type,
|
||||
description: data.reason || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Django report response to legacy frontend format
|
||||
* Converts Django field names to Supabase field names for backward compatibility
|
||||
* Adds synthetic profile structures from email data
|
||||
*
|
||||
* @param report - Report data from Django API
|
||||
* @returns Report data in legacy Supabase format
|
||||
*/
|
||||
export function mapReportToLegacy(report: Report): LegacyReport {
|
||||
return {
|
||||
id: report.id,
|
||||
reported_entity_type: report.entity_type,
|
||||
reported_entity_id: report.entity_id,
|
||||
report_type: report.report_type,
|
||||
reason: report.description,
|
||||
status: report.status,
|
||||
reporter_id: report.reported_by_id,
|
||||
reviewed_by: report.reviewed_by_id,
|
||||
reviewed_at: report.reviewed_at,
|
||||
resolution_notes: report.resolution_notes,
|
||||
created_at: report.created_at,
|
||||
updated_at: report.updated_at,
|
||||
// Create synthetic profile objects from email data
|
||||
reporter_profile: report.reported_by_email
|
||||
? {
|
||||
username: report.reported_by_email.split('@')[0],
|
||||
display_name: null,
|
||||
}
|
||||
: null,
|
||||
reviewer_profile: report.reviewed_by_email
|
||||
? {
|
||||
username: report.reviewed_by_email.split('@')[0],
|
||||
display_name: null,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map array of Django reports to legacy format
|
||||
* Convenience function for bulk transformations
|
||||
*
|
||||
* @param reports - Array of reports from Django API
|
||||
* @returns Array of reports in legacy Supabase format
|
||||
*/
|
||||
export function mapReportsToLegacy(reports: Report[]): LegacyReport[] {
|
||||
return reports.map(mapReportToLegacy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract username from email address
|
||||
* Fallback for creating synthetic usernames
|
||||
*
|
||||
* @param email - Email address
|
||||
* @returns Username (part before @) or null
|
||||
*/
|
||||
export function extractUsernameFromEmail(email: string | null): string | null {
|
||||
if (!email) return null;
|
||||
return email.split('@')[0];
|
||||
}
|
||||
305
src-old/services/reports/reportsService.ts
Normal file
305
src-old/services/reports/reportsService.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Reports Service - API client for Django Reports System
|
||||
* Provides abstraction layer between frontend and Django backend
|
||||
*/
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
import { logger } from '@/lib/logger';
|
||||
import type {
|
||||
Report,
|
||||
SubmitReportData,
|
||||
LegacySubmitReportData,
|
||||
ReportFilters,
|
||||
PaginatedReports,
|
||||
ReportStats,
|
||||
UpdateReportStatusData,
|
||||
ServiceResponse,
|
||||
} from './types';
|
||||
import { mapSubmitReportToBackend } from './mappers';
|
||||
|
||||
/**
|
||||
* Reports Service Class
|
||||
* Handles all communication with Django Reports API endpoints
|
||||
*/
|
||||
class ReportsService {
|
||||
private baseUrl: string;
|
||||
|
||||
constructor() {
|
||||
// Use environment variable or fallback to relative path
|
||||
this.baseUrl = import.meta.env.VITE_DJANGO_API_URL || '/api/v1';
|
||||
logger.log('[ReportsService] Initialized with base URL:', this.baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication token from current Supabase session
|
||||
* @returns JWT token for Django API authentication
|
||||
* @throws Error if no active session
|
||||
*/
|
||||
private async getAuthToken(): Promise<string> {
|
||||
const { data: { session }, error } = await supabase.auth.getSession();
|
||||
|
||||
if (error) {
|
||||
logger.error('[ReportsService] Failed to get session', { error });
|
||||
throw new Error('Failed to retrieve authentication session');
|
||||
}
|
||||
|
||||
if (!session?.access_token) {
|
||||
logger.error('[ReportsService] No active session');
|
||||
throw new Error('Authentication required. Please sign in.');
|
||||
}
|
||||
|
||||
return session.access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make authenticated request to Django API
|
||||
* @param endpoint - API endpoint path (e.g., '/reports/')
|
||||
* @param options - Fetch options
|
||||
* @returns Parsed JSON response
|
||||
* @throws Error with details from Django API
|
||||
*/
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const token = await this.getAuthToken();
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
|
||||
logger.log('[ReportsService] Making request', {
|
||||
method: options.method || 'GET',
|
||||
url,
|
||||
hasBody: !!options.body
|
||||
});
|
||||
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
// Handle non-JSON responses (e.g., 204 No Content)
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
// Django returns errors in { detail: "message" } format
|
||||
const errorMessage = data.detail || data.message || 'Request failed';
|
||||
logger.error('[ReportsService] Request failed', {
|
||||
status: response.status,
|
||||
error: errorMessage,
|
||||
url,
|
||||
});
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
logger.log('[ReportsService] Request successful', {
|
||||
status: response.status,
|
||||
url
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a new report
|
||||
* @param data - Report submission data (supports legacy Supabase format)
|
||||
* @returns Created report
|
||||
*/
|
||||
async submitReport(
|
||||
data: SubmitReportData | LegacySubmitReportData
|
||||
): Promise<ServiceResponse<Report>> {
|
||||
try {
|
||||
// Handle both new format and legacy Supabase format
|
||||
const payload = 'reported_entity_type' in data
|
||||
? mapSubmitReportToBackend(data)
|
||||
: data;
|
||||
|
||||
const report = await this.request<Report>('/reports/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
return { success: true, data: report };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Submit report',
|
||||
metadata: {
|
||||
entityType: 'entity_type' in data ? data.entity_type : data.reported_entity_type,
|
||||
reportType: data.report_type,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to submit report',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List reports with optional filters and pagination
|
||||
* @param filters - Optional filters (status, type, entity)
|
||||
* @param page - Page number (1-indexed)
|
||||
* @param pageSize - Number of items per page
|
||||
* @returns Paginated list of reports
|
||||
*/
|
||||
async listReports(
|
||||
filters: ReportFilters = {},
|
||||
page: number = 1,
|
||||
pageSize: number = 50
|
||||
): Promise<ServiceResponse<PaginatedReports>> {
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
});
|
||||
|
||||
// Add optional filters
|
||||
if (filters.status) params.append('status', filters.status);
|
||||
if (filters.report_type) params.append('report_type', filters.report_type);
|
||||
if (filters.entity_type) params.append('entity_type', filters.entity_type);
|
||||
if (filters.entity_id) params.append('entity_id', filters.entity_id);
|
||||
|
||||
const data = await this.request<PaginatedReports>(
|
||||
`/reports/?${params.toString()}`
|
||||
);
|
||||
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'List reports',
|
||||
metadata: { filters, page, pageSize },
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to list reports',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single report by ID
|
||||
* @param id - Report UUID
|
||||
* @returns Report details
|
||||
*/
|
||||
async getReport(id: string): Promise<ServiceResponse<Report>> {
|
||||
try {
|
||||
const report = await this.request<Report>(`/reports/${id}/`);
|
||||
return { success: true, data: report };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Get report',
|
||||
metadata: { reportId: id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get report',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update report status (moderators only)
|
||||
* @param id - Report UUID
|
||||
* @param status - New status
|
||||
* @param resolutionNotes - Optional resolution notes
|
||||
* @returns Updated report
|
||||
*/
|
||||
async updateReportStatus(
|
||||
id: string,
|
||||
status: string,
|
||||
resolutionNotes?: string
|
||||
): Promise<ServiceResponse<Report>> {
|
||||
try {
|
||||
const payload: UpdateReportStatusData = {
|
||||
status: status as any,
|
||||
resolution_notes: resolutionNotes,
|
||||
};
|
||||
|
||||
const report = await this.request<Report>(`/reports/${id}/`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
return { success: true, data: report };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Update report status',
|
||||
metadata: { reportId: id, newStatus: status },
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update report',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a report (moderators only)
|
||||
* @param id - Report UUID
|
||||
* @returns Success status
|
||||
*/
|
||||
async deleteReport(id: string): Promise<ServiceResponse<void>> {
|
||||
try {
|
||||
await this.request<void>(`/reports/${id}/`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Delete report',
|
||||
metadata: { reportId: id },
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete report',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get report statistics (moderators only)
|
||||
* @returns Report statistics
|
||||
*/
|
||||
async getStatistics(): Promise<ServiceResponse<ReportStats>> {
|
||||
try {
|
||||
const stats = await this.request<ReportStats>('/reports/stats/');
|
||||
return { success: true, data: stats };
|
||||
} catch (error) {
|
||||
const errorId = handleError(error, {
|
||||
action: 'Get report statistics',
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to get statistics',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base URL for debugging/testing
|
||||
* @returns Current base URL
|
||||
*/
|
||||
getBaseUrl(): string {
|
||||
return this.baseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const reportsService = new ReportsService();
|
||||
|
||||
// Export class for testing
|
||||
export { ReportsService };
|
||||
149
src-old/services/reports/types.ts
Normal file
149
src-old/services/reports/types.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* TypeScript interfaces for Django Reports API
|
||||
* Matches the Django backend schema from api/v1/schemas.py
|
||||
*/
|
||||
|
||||
/**
|
||||
* Report status enum matching Django model choices
|
||||
*/
|
||||
export type ReportStatus = 'pending' | 'reviewing' | 'resolved' | 'dismissed';
|
||||
|
||||
/**
|
||||
* Report type enum matching Django model choices
|
||||
*/
|
||||
export type ReportType =
|
||||
| 'spam'
|
||||
| 'inappropriate'
|
||||
| 'misinformation'
|
||||
| 'duplicate'
|
||||
| 'copyright'
|
||||
| 'other';
|
||||
|
||||
/**
|
||||
* Entity type enum matching Django model choices
|
||||
*/
|
||||
export type EntityType = 'review' | 'photo' | 'profile' | 'submission';
|
||||
|
||||
/**
|
||||
* Main Report interface matching Django ReportOut schema
|
||||
*/
|
||||
export interface Report {
|
||||
id: string;
|
||||
entity_type: EntityType;
|
||||
entity_id: string;
|
||||
report_type: ReportType;
|
||||
description: string | null;
|
||||
status: ReportStatus;
|
||||
reported_by_id: string;
|
||||
reported_by_email: string | null;
|
||||
reviewed_by_id: string | null;
|
||||
reviewed_by_email: string | null;
|
||||
reviewed_at: string | null;
|
||||
resolution_notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request payload for submitting a new report
|
||||
* Matches Django ReportCreate schema
|
||||
*/
|
||||
export interface SubmitReportData {
|
||||
entity_type: EntityType;
|
||||
entity_id: string;
|
||||
report_type: ReportType;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frontend-compatible report submission (Supabase field names)
|
||||
* Used by existing components before refactoring
|
||||
*/
|
||||
export interface LegacySubmitReportData {
|
||||
reported_entity_type: EntityType;
|
||||
reported_entity_id: string;
|
||||
report_type: ReportType;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query filters for listing reports
|
||||
* All parameters optional for flexible filtering
|
||||
*/
|
||||
export interface ReportFilters {
|
||||
status?: ReportStatus;
|
||||
report_type?: ReportType;
|
||||
entity_type?: EntityType;
|
||||
entity_id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated reports response matching Django ReportListOut schema
|
||||
*/
|
||||
export interface PaginatedReports {
|
||||
items: Report[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report statistics response matching Django ReportStatsOut schema
|
||||
*/
|
||||
export interface ReportStats {
|
||||
total_reports: number;
|
||||
pending_reports: number;
|
||||
reviewing_reports: number;
|
||||
resolved_reports: number;
|
||||
dismissed_reports: number;
|
||||
reports_by_type: Record<string, number>;
|
||||
reports_by_entity_type: Record<string, number>;
|
||||
average_resolution_time_hours: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request payload for updating report status
|
||||
* Matches Django ReportUpdate schema
|
||||
*/
|
||||
export interface UpdateReportStatusData {
|
||||
status: ReportStatus;
|
||||
resolution_notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy report format for backward compatibility with Supabase components
|
||||
* This maintains the field names that existing components expect
|
||||
*/
|
||||
export interface LegacyReport {
|
||||
id: string;
|
||||
reported_entity_type: EntityType;
|
||||
reported_entity_id: string;
|
||||
report_type: ReportType;
|
||||
reason: string | null;
|
||||
status: ReportStatus;
|
||||
reporter_id: string;
|
||||
reviewed_by: string | null;
|
||||
reviewed_at: string | null;
|
||||
resolution_notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
// Synthetic fields for component compatibility
|
||||
reporter_profile?: {
|
||||
username: string | null;
|
||||
display_name: string | null;
|
||||
} | null;
|
||||
reviewer_profile?: {
|
||||
username: string | null;
|
||||
display_name: string | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service response wrapper for error handling
|
||||
*/
|
||||
export interface ServiceResponse<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user