feat(api): restructure api with axios and chainage related changes

This commit is contained in:
2026-03-18 18:42:40 +05:30
parent 7f8854ed78
commit 598098d7e8
31 changed files with 933 additions and 931 deletions

View File

@@ -0,0 +1,58 @@
import axiosClient from "../axios/axios";
import {
Chainage, ChainageCreate, ChainageUpdate,
PaginatedResponse, PaginationParams
} from "@/types";
/**
* Chainage Service
*/
export const chainageService = {
/**
* Fetch all chainages
*/
getChainages: async (params?: PaginationParams): Promise<PaginatedResponse<Chainage>> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<PaginatedResponse<Chainage>>(`/chainages/`, {
params: { skip, limit }
});
return response.data;
},
/**
* Fetch chainages filtered by package ID
*/
getChainagesByPackage: async (packageId: string, params?: PaginationParams): Promise<PaginatedResponse<Chainage>> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<PaginatedResponse<Chainage>>(`/chainages/`, {
params: { package_id: packageId, skip, limit }
});
return response.data;
},
/**
* Create a new chainage
*/
createChainage: async (data: ChainageCreate): Promise<Chainage> => {
const response = await axiosClient.post<Chainage>("/chainages/", data);
return response.data;
},
/**
* Update an existing chainage
*/
updateChainage: async (chainageId: string, data: ChainageUpdate): Promise<Chainage> => {
const response = await axiosClient.put<Chainage>(`/chainages/${chainageId}`, data);
return response.data;
},
/**
* Delete a chainage
*/
deleteChainage: async (chainageId: string): Promise<{ message: string }> => {
const response = await axiosClient.delete<{ message: string }>(`/chainages/${chainageId}`);
return response.data;
}
};

View File

@@ -0,0 +1,56 @@
import axiosClient from "../axios/axios";
import { Detection } from "@/types";
import { projectService } from "./project.service";
/**
* Detection Service
*/
export const detectionService = {
/**
* Fetch all detections from completed videos
* Uses the summary endpoint to get detections for each project
*/
getAllDetections: async (): Promise<Detection[]> => {
try {
// First get all projects
const projectsResponse = await projectService.getProjects();
const projects = projectsResponse.items;
// Then fetch detections for each project
const allDetections: Detection[] = []
for (const project of projects) {
try {
const response = await axiosClient.get<{
packages: {
[key: string]: {
chainages: {
[key: string]: {
detections: Detection[]
}
}
}
}
}>(`/summary/projects/${project.id}`);
const summary = response.data;
// Extract detections from the nested structure
for (const pkg of Object.values(summary.packages || {})) {
for (const loc of Object.values(pkg.chainages || {})) {
allDetections.push(...(loc.detections || []))
}
}
} catch (e) {
// Skip projects that fail to load
console.warn(`Failed to load detections for project ${project.id}:`, e)
}
}
return allDetections;
} catch (e) {
console.error("Failed to fetch all detections:", e)
return []
}
}
};

View File

@@ -0,0 +1,7 @@
export * from "./project.service";
export * from "./package.service";
export { chainageService } from "./chainage.service";
export * from "./video.service";
export * from "./detection.service";
export * from "./session.service";
export { projectDataService } from "./project.service";

View File

@@ -0,0 +1,58 @@
import axiosClient from "../axios/axios";
import {
Package, PackageCreate, PackageUpdate,
PaginatedResponse, PaginationParams
} from "@/types";
/**
* Package Service
*/
export const packageService = {
/**
* Fetch all packages
*/
getPackages: async (params?: PaginationParams): Promise<PaginatedResponse<Package>> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<PaginatedResponse<Package>>(`/packages/`, {
params: { skip, limit }
});
return response.data;
},
/**
* Fetch packages filtered by project ID
*/
getPackagesByProject: async (projectId: string, params?: PaginationParams): Promise<PaginatedResponse<Package>> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<PaginatedResponse<Package>>(`/packages/`, {
params: { project_id: projectId, skip, limit }
});
return response.data;
},
/**
* Create a new package
*/
createPackage: async (data: PackageCreate): Promise<Package> => {
const response = await axiosClient.post<Package>("/packages/", data);
return response.data;
},
/**
* Update an existing package
*/
updatePackage: async (packageId: string, data: PackageUpdate): Promise<Package> => {
const response = await axiosClient.put<Package>(`/packages/${packageId}`, data);
return response.data;
},
/**
* Delete a package
*/
deletePackage: async (packageId: string): Promise<{ message: string }> => {
const response = await axiosClient.delete<{ message: string }>(`/packages/${packageId}`);
return response.data;
}
};

View File

@@ -0,0 +1,100 @@
import axiosClient from "../axios/axios";
import {
Project, ProjectCreate, ProjectUpdate,
PaginatedResponse, PaginationParams
} from "@/types";
/**
* Project Service
*/
export const projectService = {
/**
* Fetch all projects
*/
getProjects: async (params?: PaginationParams): Promise<PaginatedResponse<Project>> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<PaginatedResponse<Project>>(`/projects/`, {
params: { skip, limit }
});
return response.data;
},
/**
* Create a new project
*/
createProject: async (data: ProjectCreate): Promise<Project> => {
const response = await axiosClient.post<Project>("/projects/", data);
return response.data;
},
/**
* Update an existing project
*/
updateProject: async (projectId: string, data: ProjectUpdate): Promise<Project> => {
const response = await axiosClient.put<Project>(`/projects/${projectId}`, data);
return response.data;
},
/**
* Delete a project
*/
deleteProject: async (projectId: string): Promise<{ message: string }> => {
const response = await axiosClient.delete<{ message: string }>(`/projects/${projectId}`);
return response.data;
},
/**
* Fetch project summary (detections across packages and chainages)
*/
getProjectSummary: async (projectId: string): Promise<any> => {
const response = await axiosClient.get(`/summary/projects/${projectId}`);
return response.data;
},
/**
* Fetch project summary filtered by video ID
*/
getProjectSummaryByVideo: async (projectId: string, videoId: string): Promise<any> => {
const response = await axiosClient.get(`/summary/projects/${projectId}`, {
params: { video_id: videoId }
});
return response.data;
}
};
/**
* Service to handle data extraction from project summaries
* Moved from legacy project-service.ts
*/
export const projectDataService = {
/**
* Extracts all detections from a project summary, optionally filtered by package and chainage
*/
extractDetections(
projectSummary: any,
selectedPackageId?: string | null,
selectedChainageId?: string | null
): any[] {
if (!projectSummary) return []
const detections: any[] = []
const packagesToProcess = selectedPackageId && selectedPackageId !== "all"
? { [selectedPackageId]: projectSummary.packages[selectedPackageId] }
: projectSummary.packages || {}
for (const [pkgName, pkg] of Object.entries(packagesToProcess)) {
const chainagesToProcess = selectedChainageId && selectedChainageId !== "all"
? { [selectedChainageId]: (pkg as any).chainages[selectedChainageId] }
: (pkg as any).chainages || {}
for (const [chnName, chn] of Object.entries(chainagesToProcess)) {
if (!chn) continue
const chainageDetections = (chn as any).detections || []
detections.push(...chainageDetections)
}
}
return detections
}
};

View File

@@ -0,0 +1,73 @@
import { SessionContext, VideoResultData, emptySessionContext } from "@/types";
// Session Storage Keys
const SESSION_KEY = "visionroad_session";
const VIDEO_DATA_KEY = "visionroad_video_data";
const DETECTION_TYPE_KEY = "visionroad_detection_type";
/**
* Session Service
*/
export const sessionService = {
/**
* Save session to localStorage
*/
saveSession: (session: SessionContext): void => {
if (typeof window !== "undefined") {
localStorage.setItem(SESSION_KEY, JSON.stringify(session))
}
},
/**
* Load session from localStorage
*/
loadSession: (): SessionContext => {
if (typeof window !== "undefined") {
const stored = localStorage.getItem(SESSION_KEY)
if (stored) {
return JSON.parse(stored)
}
}
return emptySessionContext;
},
/**
* Clear all session data
*/
clearSession: (): void => {
if (typeof window !== "undefined") {
localStorage.removeItem(SESSION_KEY)
localStorage.removeItem(VIDEO_DATA_KEY)
localStorage.removeItem(DETECTION_TYPE_KEY)
}
},
/**
* Save video result data
*/
saveVideoData: (data: VideoResultData): void => {
if (typeof window !== "undefined") {
localStorage.setItem(VIDEO_DATA_KEY, JSON.stringify(data))
}
},
/**
* Load video result data
*/
loadVideoData: (): VideoResultData | null => {
if (typeof window !== "undefined") {
const stored = localStorage.getItem(VIDEO_DATA_KEY)
if (stored) {
return JSON.parse(stored)
}
}
return null;
},
/**
* Check if session is complete
*/
isSessionValid: (session: SessionContext): boolean => {
return !!(session.projectId && session.packageId && session.chainageId)
}
};

View File

@@ -0,0 +1,79 @@
import axiosClient from "../axios/axios";
import { Video, PaginationParams } from "@/types";
/**
* Video Service
*/
export const videoService = {
/**
* Fetch all videos and transform them to match the Video interface
*/
getVideos: async (params?: PaginationParams): Promise<Video[]> => {
const skip = params?.skip ?? 0;
const limit = params?.limit ?? 100;
const response = await axiosClient.get<{
videos: Array<{
video_id: string;
status: string;
progress: number;
summary?: {
unique_defected_sign_board?: number;
unique_pothole?: number;
unique_road_crack?: number;
unique_damaged_road_marking?: number;
unique_good_sign_board?: number;
total_road_damage?: number;
total_detections?: number;
};
}>;
}>(`/videos`, {
params: { skip, limit }
});
// Transform the response to match our Video interface
return response.data.videos.map(v => ({
id: v.video_id,
filename: v.video_id,
detection_type: "pot-sign-detection" as const,
status: v.status as Video["status"],
unique_defected_sign_board: v.summary?.unique_defected_sign_board,
unique_pothole: v.summary?.unique_pothole,
unique_road_crack: v.summary?.unique_road_crack,
unique_damaged_road_marking: v.summary?.unique_damaged_road_marking,
unique_good_sign_board: v.summary?.unique_good_sign_board,
total_road_damage: v.summary?.total_road_damage,
total_detections: v.summary?.total_detections,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}));
},
/**
* Upload a video for processing
*/
uploadVideo: async (formData: FormData): Promise<any> => {
const response = await axiosClient.post("/upload", formData, {
headers: {
"Content-Type": "multipart/form-data"
}
});
return response.data;
},
/**
* Get processing status of a video
*/
getVideoStatus: async (videoId: string): Promise<any> => {
const response = await axiosClient.get(`/status/${videoId}`);
return response.data;
},
/**
* Get analysis results for a video
*/
getVideoResults: async (videoId: string): Promise<any> => {
const response = await axiosClient.get(`/results/${videoId}`);
return response.data;
}
};