Improve teh securty of teh DB

This commit is contained in:
Urtzi Alfaro
2025-10-19 19:22:37 +02:00
parent 62971c07d7
commit 05da20357d
87 changed files with 7998 additions and 932 deletions

View File

@@ -0,0 +1,141 @@
// frontend/src/api/hooks/equipment.ts
/**
* React hooks for Equipment API integration
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-hot-toast';
import { equipmentService } from '../services/equipment';
import type { Equipment } from '../types/equipment';
// Query Keys
export const equipmentKeys = {
all: ['equipment'] as const,
lists: () => [...equipmentKeys.all, 'list'] as const,
list: (tenantId: string, filters?: Record<string, any>) =>
[...equipmentKeys.lists(), tenantId, filters] as const,
details: () => [...equipmentKeys.all, 'detail'] as const,
detail: (tenantId: string, equipmentId: string) =>
[...equipmentKeys.details(), tenantId, equipmentId] as const,
};
/**
* Hook to fetch equipment list
*/
export function useEquipment(
tenantId: string,
filters?: {
status?: string;
type?: string;
is_active?: boolean;
},
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: equipmentKeys.list(tenantId, filters),
queryFn: () => equipmentService.getEquipment(tenantId, filters),
enabled: !!tenantId && (options?.enabled ?? true),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
/**
* Hook to fetch a specific equipment item
*/
export function useEquipmentById(
tenantId: string,
equipmentId: string,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: equipmentKeys.detail(tenantId, equipmentId),
queryFn: () => equipmentService.getEquipmentById(tenantId, equipmentId),
enabled: !!tenantId && !!equipmentId && (options?.enabled ?? true),
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
/**
* Hook to create equipment
*/
export function useCreateEquipment(tenantId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (equipmentData: Equipment) =>
equipmentService.createEquipment(tenantId, equipmentData),
onSuccess: (newEquipment) => {
// Invalidate and refetch equipment lists
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
// Add to cache
queryClient.setQueryData(
equipmentKeys.detail(tenantId, newEquipment.id),
newEquipment
);
toast.success('Equipment created successfully');
},
onError: (error: any) => {
console.error('Error creating equipment:', error);
toast.error(error.response?.data?.detail || 'Error creating equipment');
},
});
}
/**
* Hook to update equipment
*/
export function useUpdateEquipment(tenantId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ equipmentId, equipmentData }: {
equipmentId: string;
equipmentData: Partial<Equipment>;
}) => equipmentService.updateEquipment(tenantId, equipmentId, equipmentData),
onSuccess: (updatedEquipment, { equipmentId }) => {
// Update cached data
queryClient.setQueryData(
equipmentKeys.detail(tenantId, equipmentId),
updatedEquipment
);
// Invalidate lists to refresh
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
toast.success('Equipment updated successfully');
},
onError: (error: any) => {
console.error('Error updating equipment:', error);
toast.error(error.response?.data?.detail || 'Error updating equipment');
},
});
}
/**
* Hook to delete equipment
*/
export function useDeleteEquipment(tenantId: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (equipmentId: string) =>
equipmentService.deleteEquipment(tenantId, equipmentId),
onSuccess: (_, equipmentId) => {
// Remove from cache
queryClient.removeQueries({
queryKey: equipmentKeys.detail(tenantId, equipmentId)
});
// Invalidate lists to refresh
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
toast.success('Equipment deleted successfully');
},
onError: (error: any) => {
console.error('Error deleting equipment:', error);
toast.error(error.response?.data?.detail || 'Error deleting equipment');
},
});
}

View File

@@ -0,0 +1,178 @@
// frontend/src/api/services/equipment.ts
/**
* Equipment API service
*/
import { apiClient } from '../client';
import type {
Equipment,
EquipmentCreate,
EquipmentUpdate,
EquipmentResponse,
EquipmentListResponse
} from '../types/equipment';
class EquipmentService {
private readonly baseURL = '/tenants';
/**
* Helper to convert snake_case API response to camelCase Equipment
*/
private convertToEquipment(response: EquipmentResponse): Equipment {
return {
id: response.id,
tenant_id: response.tenant_id,
name: response.name,
type: response.type,
model: response.model || '',
serialNumber: response.serial_number || '',
location: response.location || '',
status: response.status,
installDate: response.install_date || new Date().toISOString().split('T')[0],
lastMaintenance: response.last_maintenance_date || new Date().toISOString().split('T')[0],
nextMaintenance: response.next_maintenance_date || new Date().toISOString().split('T')[0],
maintenanceInterval: response.maintenance_interval_days || 30,
temperature: response.current_temperature || undefined,
targetTemperature: response.target_temperature || undefined,
efficiency: response.efficiency_percentage || 0,
uptime: response.uptime_percentage || 0,
energyUsage: response.energy_usage_kwh || 0,
utilizationToday: 0, // Not in backend yet
alerts: [], // Not in backend yet
maintenanceHistory: [], // Not in backend yet
specifications: {
power: response.power_kw || 0,
capacity: response.capacity || 0,
dimensions: {
width: 0, // Not in backend separately
height: 0,
depth: 0
},
weight: response.weight_kg || 0
},
is_active: response.is_active,
created_at: response.created_at,
updated_at: response.updated_at
};
}
/**
* Helper to convert Equipment to API request format (snake_case)
*/
private convertToApiFormat(equipment: Partial<Equipment>): EquipmentCreate | EquipmentUpdate {
return {
name: equipment.name,
type: equipment.type,
model: equipment.model,
serial_number: equipment.serialNumber,
location: equipment.location,
status: equipment.status,
install_date: equipment.installDate,
last_maintenance_date: equipment.lastMaintenance,
next_maintenance_date: equipment.nextMaintenance,
maintenance_interval_days: equipment.maintenanceInterval,
efficiency_percentage: equipment.efficiency,
uptime_percentage: equipment.uptime,
energy_usage_kwh: equipment.energyUsage,
power_kw: equipment.specifications?.power,
capacity: equipment.specifications?.capacity,
weight_kg: equipment.specifications?.weight,
current_temperature: equipment.temperature,
target_temperature: equipment.targetTemperature,
is_active: equipment.is_active
};
}
/**
* Get all equipment for a tenant
*/
async getEquipment(
tenantId: string,
filters?: {
status?: string;
type?: string;
is_active?: boolean;
}
): Promise<Equipment[]> {
const params = new URLSearchParams();
if (filters?.status) params.append('status', filters.status);
if (filters?.type) params.append('type', filters.type);
if (filters?.is_active !== undefined) params.append('is_active', String(filters.is_active));
const queryString = params.toString();
const url = `${this.baseURL}/${tenantId}/production/equipment${queryString ? `?${queryString}` : ''}`;
const data: EquipmentListResponse = await apiClient.get(url, {
headers: { 'X-Tenant-ID': tenantId }
});
return data.equipment.map(eq => this.convertToEquipment(eq));
}
/**
* Get a specific equipment item
*/
async getEquipmentById(
tenantId: string,
equipmentId: string
): Promise<Equipment> {
const data: EquipmentResponse = await apiClient.get(
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
return this.convertToEquipment(data);
}
/**
* Create a new equipment item
*/
async createEquipment(
tenantId: string,
equipmentData: Equipment
): Promise<Equipment> {
const apiData = this.convertToApiFormat(equipmentData);
const data: EquipmentResponse = await apiClient.post(
`${this.baseURL}/${tenantId}/production/equipment`,
apiData,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
return this.convertToEquipment(data);
}
/**
* Update an equipment item
*/
async updateEquipment(
tenantId: string,
equipmentId: string,
equipmentData: Partial<Equipment>
): Promise<Equipment> {
const apiData = this.convertToApiFormat(equipmentData);
const data: EquipmentResponse = await apiClient.put(
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
apiData,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
return this.convertToEquipment(data);
}
/**
* Delete an equipment item
*/
async deleteEquipment(tenantId: string, equipmentId: string): Promise<void> {
await apiClient.delete(
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
{
headers: { 'X-Tenant-ID': tenantId }
}
);
}
}
export const equipmentService = new EquipmentService();

View File

@@ -172,9 +172,11 @@ class TrainingService {
* Get WebSocket URL for real-time training updates
*/
getTrainingWebSocketUrl(tenantId: string, jobId: string): string {
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL?.replace(/^http/, 'ws');
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL
?.replace(/^http(s?):/, 'ws$1:'); // http: → ws:, https: → wss:
return `${baseWsUrl}/tenants/${tenantId}/training/jobs/${jobId}/live`;
}
/**
* Helper method to construct WebSocket connection

View File

@@ -32,6 +32,7 @@ export interface EquipmentSpecifications {
export interface Equipment {
id: string;
tenant_id?: string;
name: string;
type: 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
model: string;
@@ -51,4 +52,90 @@ export interface Equipment {
alerts: EquipmentAlert[];
maintenanceHistory: MaintenanceHistory[];
specifications: EquipmentSpecifications;
is_active?: boolean;
created_at?: string;
updated_at?: string;
}
// API Request/Response types
export type EquipmentType = 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
export type EquipmentStatus = 'operational' | 'maintenance' | 'down' | 'warning';
export interface EquipmentCreate {
name: string;
type: EquipmentType;
model?: string;
serial_number?: string;
location?: string;
status?: EquipmentStatus;
install_date?: string;
last_maintenance_date?: string;
next_maintenance_date?: string;
maintenance_interval_days?: number;
efficiency_percentage?: number;
uptime_percentage?: number;
energy_usage_kwh?: number;
power_kw?: number;
capacity?: number;
weight_kg?: number;
current_temperature?: number;
target_temperature?: number;
notes?: string;
}
export interface EquipmentUpdate {
name?: string;
type?: EquipmentType;
model?: string;
serial_number?: string;
location?: string;
status?: EquipmentStatus;
install_date?: string;
last_maintenance_date?: string;
next_maintenance_date?: string;
maintenance_interval_days?: number;
efficiency_percentage?: number;
uptime_percentage?: number;
energy_usage_kwh?: number;
power_kw?: number;
capacity?: number;
weight_kg?: number;
current_temperature?: number;
target_temperature?: number;
is_active?: boolean;
notes?: string;
}
export interface EquipmentResponse {
id: string;
tenant_id: string;
name: string;
type: EquipmentType;
model: string | null;
serial_number: string | null;
location: string | null;
status: EquipmentStatus;
install_date: string | null;
last_maintenance_date: string | null;
next_maintenance_date: string | null;
maintenance_interval_days: number | null;
efficiency_percentage: number | null;
uptime_percentage: number | null;
energy_usage_kwh: number | null;
power_kw: number | null;
capacity: number | null;
weight_kg: number | null;
current_temperature: number | null;
target_temperature: number | null;
is_active: boolean;
notes: string | null;
created_at: string;
updated_at: string;
}
export interface EquipmentListResponse {
equipment: EquipmentResponse[];
total_count: number;
page: number;
page_size: number;
}

View File

@@ -465,6 +465,12 @@ export interface ProductSuggestionResponse {
is_seasonal: boolean;
suggested_supplier: string | null;
notes: string | null;
sales_data?: {
total_quantity: number;
average_daily_sales: number;
peak_day: string;
frequency: number;
};
}
export interface BusinessModelAnalysisResponse {