frontend_nyla/src/api/fileApi.ts

282 lines
7.7 KiB
TypeScript

import { ApiRequestOptions } from '../hooks/useApi';
// ============================================================================
// TYPES & INTERFACES
// ============================================================================
export interface FileInfo {
id: string;
mandateId: string;
fileName: string;
mimeType: string;
fileHash: string;
fileSize: number;
creationDate: number;
[key: string]: any; // Allow additional properties
}
export interface AttributeDefinition {
name: string;
label: string;
type: 'string' | 'number' | 'date' | 'boolean' | 'enum';
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
}
export interface PaginationParams {
page?: number;
pageSize?: number;
sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
filters?: Record<string, any>;
search?: string;
groupId?: string;
saveGroupTree?: any[];
}
export interface PaginatedResponse<T> {
items: T[];
pagination?: {
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
};
}
// Type for the request function passed to API functions
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
// ============================================================================
// API REQUEST FUNCTIONS
// ============================================================================
/**
* Fetch file attributes from backend
* Endpoint: GET /api/attributes/FileItem
*/
export async function fetchFileAttributes(request: ApiRequestFunction): Promise<AttributeDefinition[]> {
const data = await request({
url: '/api/attributes/FileItem',
method: 'get'
});
// Handle different response formats
if (Array.isArray(data)) {
return data;
}
if (data && typeof data === 'object' && 'attributes' in data && Array.isArray(data.attributes)) {
return data.attributes;
}
// Try to find any array property in the response
if (data && typeof data === 'object') {
const keys = Object.keys(data);
for (const key of keys) {
if (Array.isArray((data as any)[key])) {
return (data as any)[key];
}
}
}
return [];
}
/**
* Fetch list of files with optional pagination
* Endpoint: GET /api/files/list
*/
export async function fetchFiles(
request: ApiRequestFunction,
params?: PaginationParams
): Promise<PaginatedResponse<FileInfo> | FileInfo[]> {
const requestParams: any = {};
// Build pagination object if provided
if (params) {
const paginationObj: any = {};
if (params.page !== undefined) paginationObj.page = params.page;
if (params.pageSize !== undefined) paginationObj.pageSize = params.pageSize;
if (params.sort) paginationObj.sort = params.sort;
if (params.filters) paginationObj.filters = params.filters;
if (params.search) paginationObj.search = params.search;
if (params.groupId) paginationObj.groupId = params.groupId;
if (params.saveGroupTree !== undefined) paginationObj.saveGroupTree = params.saveGroupTree;
if (Object.keys(paginationObj).length > 0) {
requestParams.pagination = JSON.stringify(paginationObj);
}
}
const data = await request({
url: '/api/files/list',
method: 'get',
params: requestParams
});
return data;
}
/**
* Fetch a single file by ID
* Endpoint: GET /api/files/{fileId}
*/
export async function fetchFileById(
request: ApiRequestFunction,
fileId: string
): Promise<FileInfo | null> {
try {
const data = await request({
url: `/api/files/${fileId}`,
method: 'get'
});
return data || null;
} catch (error: any) {
console.error('Error fetching file by ID:', error);
return null;
}
}
/**
* Update a file
* Endpoint: PUT /api/files/{fileId}
*/
export async function updateFile(
request: ApiRequestFunction,
fileId: string,
fileData: Partial<FileInfo>
): Promise<FileInfo> {
return await request({
url: `/api/files/${fileId}`,
method: 'put',
data: fileData
});
}
/**
* Delete a file
* Endpoint: DELETE /api/files/{fileId}
*/
export async function deleteFile(
request: ApiRequestFunction,
fileId: string
): Promise<void> {
await request({
url: `/api/files/${fileId}`,
method: 'delete'
});
}
/**
* Delete multiple files
* Endpoint: DELETE /api/files/{fileId} (called multiple times)
*/
export async function deleteFiles(
request: ApiRequestFunction,
fileIds: string[]
): Promise<Array<{ success: boolean; fileId: string; error?: any }>> {
const uniqueIds = [...new Set(fileIds.filter(Boolean))];
if (uniqueIds.length === 0) return [];
await request({
url: '/api/files/batch-delete',
method: 'post',
data: { fileIds: uniqueIds }
});
return uniqueIds.map(fileId => ({ success: true, fileId }));
}
// ============================================================================
// GROUP BULK API FUNCTIONS
// ============================================================================
/** Patch scope for all files in a group (recursive) */
export async function patchGroupScope(
request: ApiRequestFunction,
groupId: string,
scope: string
): Promise<any> {
return await request({
url: `/api/files/groups/${groupId}/scope`,
method: 'patch',
data: { scope },
});
}
/** Patch neutralize for all files in a group (recursive, incl. knowledge purge/reindex) */
export async function patchGroupNeutralize(
request: ApiRequestFunction,
groupId: string,
neutralize: boolean
): Promise<any> {
return await request({
url: `/api/files/groups/${groupId}/neutralize`,
method: 'patch',
data: { neutralize },
});
}
/** Download all files in a group as ZIP */
export async function downloadGroupZip(groupId: string): Promise<void> {
const { default: api } = await import('../api');
const response = await api.get(`/api/files/groups/${groupId}/download`, {
responseType: 'blob',
});
const url = window.URL.createObjectURL(response.data);
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `group-${groupId}.zip`);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
}
/** Delete a group and optionally all its files */
export async function deleteGroup(
request: ApiRequestFunction,
groupId: string,
deleteItems: boolean = false
): Promise<any> {
return await request({
url: `/api/files/groups/${groupId}`,
method: 'delete',
params: { deleteItems },
});
}
/** Collect all file IDs belonging to a group recursively (client-side, from known groupTree) */
export function collectGroupItemIds(
groupTree: Array<{ id: string; itemIds: string[]; subGroups: any[] }>,
groupId: string
): string[] {
const collect = (nodes: Array<{ id: string; itemIds: string[]; subGroups: any[] }>): string[] | null => {
for (const node of nodes) {
if (node.id === groupId) {
const ids: string[] = [...node.itemIds];
const sub = (n: { id: string; itemIds: string[]; subGroups: any[] }) => {
ids.push(...n.itemIds);
n.subGroups.forEach(sub);
};
node.subGroups.forEach(sub);
return ids;
}
const found = collect(node.subGroups);
if (found) return found;
}
return null;
};
return collect(groupTree) ?? [];
}
// Note: The following operations require special handling (FormData, blob responses)
// and should use the api instance directly from '../api' rather than the request function:
// - uploadFile: Requires FormData with multipart/form-data
// - downloadFile: Requires blob responseType
// - previewFile: Requires flexible responseType (json or blob)
// These are kept in the hooks for now due to their special requirements