wiki/src/api/fileApi.ts
ValueOn AG 5fdb462528
Some checks failed
Deploy Nyla Frontend to Production / build-and-deploy (push) Failing after 1s
Sync: full codebase from GitHub wiki main
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 23:54:32 +02:00

387 lines
10 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;
viewKey?: string;
groupByLevels?: Array<{ field: string; nullLabel?: string; direction?: 'asc' | 'desc' }>;
}
export interface PaginatedResponse<T> {
items: T[];
pagination?: {
currentPage: number;
pageSize: number;
totalItems: number;
totalPages: number;
};
groupLayout?: import('./connectionApi').GroupLayout;
appliedView?: { viewKey?: string; displayName?: string };
}
// 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.viewKey) paginationObj.viewKey = params.viewKey;
if (params.groupByLevels !== undefined) paginationObj.groupByLevels = params.groupByLevels;
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 },
});
}
/** @deprecated Group tree removed — use view-based grouping (viewKey). Returns empty array. */
export function collectGroupItemIds(
_groupTree: Array<{ id: string; itemIds: string[]; subGroups: any[] }>,
_groupId: string
): string[] {
const collect = (): string[] | null => null;
return collect() ?? [];
}
// 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
// ============================================================================
// FOLDER TYPES & API FUNCTIONS
// ============================================================================
export interface FolderInfo {
id: string;
name: string;
parentId: string | null;
mandateId: string;
featureInstanceId: string;
scope: string;
neutralize: boolean;
contextOrphan?: boolean;
sysCreatedBy?: string;
sysCreatedAt?: number;
sysModifiedAt?: number;
}
export async function getFolderTree(
request: ApiRequestFunction,
owner: 'me' | 'shared' = 'me',
): Promise<FolderInfo[]> {
const data = await request({
url: '/api/files/folders/tree',
method: 'get',
params: { owner },
});
return Array.isArray(data) ? data : [];
}
export async function createFolder(
request: ApiRequestFunction,
name: string,
parentId?: string | null,
): Promise<FolderInfo> {
return await request({
url: '/api/files/folders',
method: 'post',
data: { name, parentId: parentId ?? null },
});
}
export async function renameFolder(
request: ApiRequestFunction,
folderId: string,
name: string,
): Promise<FolderInfo> {
return await request({
url: `/api/files/folders/${folderId}`,
method: 'patch',
data: { name },
});
}
export async function moveFolder(
request: ApiRequestFunction,
folderId: string,
parentId: string | null,
): Promise<FolderInfo> {
return await request({
url: `/api/files/folders/${folderId}/move`,
method: 'post',
data: { parentId },
});
}
export async function deleteFolderCascade(
request: ApiRequestFunction,
folderId: string,
): Promise<{ deletedFolders: number; deletedFiles: number }> {
return await request({
url: `/api/files/folders/${folderId}`,
method: 'delete',
params: { cascade: true },
});
}
export async function patchFolderScope(
request: ApiRequestFunction,
folderId: string,
scope: string,
cascadeToFiles: boolean = false,
): Promise<{ folderId: string; scope: string; filesUpdated: number }> {
return await request({
url: `/api/files/folders/${folderId}/scope`,
method: 'patch',
data: { scope, cascadeToFiles },
});
}
export async function patchFolderNeutralize(
request: ApiRequestFunction,
folderId: string,
neutralize: boolean,
): Promise<{ folderId: string; neutralize: boolean; filesUpdated: number }> {
return await request({
url: `/api/files/folders/${folderId}/neutralize`,
method: 'patch',
data: { neutralize },
});
}
export async function moveFiles(
request: ApiRequestFunction,
fileIds: string[],
targetFolderId: string | null,
): Promise<void> {
await Promise.all(
fileIds.map((fileId) =>
request({
url: `/api/files/${fileId}`,
method: 'put',
data: { folderId: targetFolderId },
}),
),
);
}