init
This commit is contained in:
71
frontend/src/api/client.ts
Normal file
71
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import axios from 'axios';
|
||||
import type { Repository, RepositoryCreate, Review, ReviewStats } from '../types';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/api';
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Repositories
|
||||
export const getRepositories = async () => {
|
||||
const response = await api.get<{ items: Repository[]; total: number }>('/repositories');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getRepository = async (id: number) => {
|
||||
const response = await api.get<Repository>(`/repositories/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const createRepository = async (data: RepositoryCreate) => {
|
||||
const response = await api.post<Repository>('/repositories', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateRepository = async (id: number, data: Partial<RepositoryCreate>) => {
|
||||
const response = await api.put<Repository>(`/repositories/${id}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteRepository = async (id: number) => {
|
||||
const response = await api.delete(`/repositories/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const scanRepository = async (id: number) => {
|
||||
const response = await api.post(`/repositories/${id}/scan`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Reviews
|
||||
export const getReviews = async (params?: {
|
||||
skip?: number;
|
||||
limit?: number;
|
||||
repository_id?: number;
|
||||
status?: string;
|
||||
}) => {
|
||||
const response = await api.get<{ items: Review[]; total: number }>('/reviews', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getReview = async (id: number) => {
|
||||
const response = await api.get<Review>(`/reviews/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const retryReview = async (id: number) => {
|
||||
const response = await api.post(`/reviews/${id}/retry`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getReviewStats = async () => {
|
||||
const response = await api.get<ReviewStats>('/reviews/stats/dashboard');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default api;
|
||||
|
||||
99
frontend/src/api/websocket.ts
Normal file
99
frontend/src/api/websocket.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { WebSocketMessage } from '../types';
|
||||
|
||||
const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:8000';
|
||||
|
||||
export class WebSocketClient {
|
||||
private ws: WebSocket | null = null;
|
||||
private listeners: Map<string, Set<(data: any) => void>> = new Map();
|
||||
private reconnectAttempts = 0;
|
||||
private maxReconnectAttempts = 5;
|
||||
private reconnectDelay = 3000;
|
||||
|
||||
connect() {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(`${WS_URL}/ws/reviews`);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
this.notifyListeners('connection', { status: 'connected' });
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data);
|
||||
this.notifyListeners(message.type, message);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.notifyListeners('connection', { status: 'error', error });
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('WebSocket disconnected');
|
||||
this.notifyListeners('connection', { status: 'disconnected' });
|
||||
this.reconnect();
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to create WebSocket:', error);
|
||||
this.reconnect();
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
private reconnect() {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
|
||||
setTimeout(() => this.connect(), this.reconnectDelay);
|
||||
}
|
||||
}
|
||||
|
||||
on(event: string, callback: (data: any) => void) {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
this.listeners.get(event)!.add(callback);
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => {
|
||||
const callbacks = this.listeners.get(event);
|
||||
if (callbacks) {
|
||||
callbacks.delete(callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private notifyListeners(event: string, data: any) {
|
||||
const callbacks = this.listeners.get(event);
|
||||
if (callbacks) {
|
||||
callbacks.forEach((callback) => callback(data));
|
||||
}
|
||||
}
|
||||
|
||||
send(message: any) {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.warn('WebSocket is not connected');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const wsClient = new WebSocketClient();
|
||||
|
||||
Reference in New Issue
Block a user