Corp/frontend/src/lib/api/documents.ts
2026-06-01 16:47:22 +03:00

164 lines
9.7 KiB
TypeScript

import type { GetTokenFn, RawRequestFn, RequestFn } from './utils';
function parseDownloadFilename(response: Response, fallback: string): string {
const disposition = response.headers.get('content-disposition') || '';
const encodedMatch = disposition.match(/filename\*=UTF-8''([^;]+)/);
const plainMatch = disposition.match(/filename="?([^"]+)"?/);
return encodedMatch?.[1]
? decodeURIComponent(encodedMatch[1])
: plainMatch?.[1] || fallback;
}
async function fetchDownload(
endpoint: string,
options: RequestInit,
fallbackFilename: string,
requestRaw?: RawRequestFn,
getToken?: GetTokenFn
): Promise<{ blob: Blob; filename: string }> {
let response: Response;
if (requestRaw) {
response = await requestRaw(endpoint, options);
} else {
const headers: Record<string, string> = {
...(options.headers as Record<string, string>),
};
const token = getToken?.();
if (token) headers.Authorization = `Bearer ${token}`;
response = await fetch(endpoint, { ...options, headers });
if (!response.ok) {
let errorDetails = response.statusText;
try {
const data = await response.json();
errorDetails = data.detail || JSON.stringify(data);
} catch {}
throw new Error(`API Error: ${response.status} ${errorDetails}`);
}
}
return {
blob: await response.blob(),
filename: parseDownloadFilename(response, fallbackFilename),
};
}
export function createDocumentsApi(request: RequestFn, getToken?: GetTokenFn, requestRaw?: RawRequestFn) {
return {
getDocuments: (params?: { search?: string; type?: string; status?: string; page?: number; page_size?: number; limit?: number; folder_id?: number }) => {
const qp = new URLSearchParams();
if (params?.search) qp.append('search', params.search);
if (params?.type) qp.append('type', params.type);
if (params?.status) qp.append('status', params.status);
if (params?.page) qp.append('page', params.page.toString());
const pageSize = params?.page_size ?? params?.limit;
if (pageSize) qp.append('page_size', pageSize.toString());
if (params?.folder_id !== undefined) qp.append('folder_id', params.folder_id.toString());
const qs = qp.toString();
return request(`/api/v1/documents/${qs ? '?' + qs : ''}`);
},
getDocument: (id: number) => request(`/api/v1/documents/${id}/`),
createDocument: (data: { title?: string; description?: string; file: File | Blob; extracted_text?: string; sent_to_all?: boolean; recipient_ids?: number[]; department_ids?: number[]; folder_id?: number | null; acknowledgement_required?: boolean; tag_ids?: number[] }) => {
const fd = new FormData();
if (data.title?.trim()) fd.append('title', data.title.trim());
if (data.description) fd.append('description', data.description);
if (data.extracted_text) fd.append('extracted_text', data.extracted_text);
if (data.folder_id) fd.append('folder', String(data.folder_id));
fd.append('sent_to_all', String(data.sent_to_all ?? true));
if (data.acknowledgement_required !== undefined) fd.append('acknowledgement_required', String(data.acknowledgement_required));
data.recipient_ids?.forEach(id => fd.append('recipient_ids', String(id)));
data.department_ids?.forEach(id => fd.append('department_ids', String(id)));
data.tag_ids?.forEach(id => fd.append('tag_ids', String(id)));
fd.append('file', data.file);
return request('/api/v1/documents/', { method: 'POST', body: fd });
},
updateDocument: (id: number, data: { title?: string; description?: string; file?: File; tag_ids?: number[]; folder?: number | null }) => {
const fd = new FormData();
if (data.title !== undefined) fd.append('title', data.title);
if (data.description !== undefined) fd.append('description', data.description);
if (data.file) fd.append('file', data.file);
if (data.folder !== undefined) fd.append('folder', data.folder === null ? '' : String(data.folder));
data.tag_ids?.forEach(tagId => fd.append('tag_ids', String(tagId)));
return request(`/api/v1/documents/${id}/`, { method: 'PATCH', body: fd });
},
deleteDocument: (id: number): Promise<void> => request(`/api/v1/documents/${id}/`, { method: 'DELETE' }),
downloadDocumentsArchive: async (ids: number[]): Promise<{ blob: Blob; filename: string }> => {
return fetchDownload(
'/api/v1/documents/archive/',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ document_ids: ids }),
},
'documents.zip',
requestRaw,
getToken
);
},
acknowledgeDocument: (id: number) => request(`/api/v1/documents/${id}/acknowledge/`, { method: 'POST', body: JSON.stringify({}) }),
getDocumentAcknowledgements: (id: number, search?: string) => {
const qp = new URLSearchParams();
if (search) qp.append('search', search);
const qs = qp.toString();
return request(`/api/v1/documents/${id}/acknowledgements/${qs ? '?' + qs : ''}`);
},
// Folders
getFolders: (params?: { parent_id?: number; root?: boolean; page?: number; page_size?: number; limit?: number }) => {
const qp = new URLSearchParams();
if (params?.parent_id !== undefined) qp.append('parent_id', params.parent_id.toString());
if (params?.root) qp.append('root', 'true');
if (params?.page) qp.append('page', params.page.toString());
const pageSize = params?.page_size ?? params?.limit;
if (pageSize) qp.append('page_size', pageSize.toString());
const qs = qp.toString();
return request(`/api/v1/folders/${qs ? '?' + qs : ''}`);
},
getFolder: (id: number) => request(`/api/v1/folders/${id}/`),
createFolder: (data: { name: string; parent?: number | null }) => request('/api/v1/folders/', { method: 'POST', body: JSON.stringify(data) }),
updateFolder: (id: number, data: { name?: string; parent?: number | null }) => request(`/api/v1/folders/${id}/`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteFolder: (id: number): Promise<void> => request(`/api/v1/folders/${id}/`, { method: 'DELETE' }),
downloadFolderArchive: async (id: number): Promise<{ blob: Blob; filename: string }> => {
return fetchDownload(
`/api/v1/folders/${id}/archive/`,
{},
`folder-${id}.zip`,
requestRaw,
getToken
);
},
getFolderChildren: (id: number) => request(`/api/v1/folders/${id}/children/`),
getFolderDocuments: (id: number) => request(`/api/v1/folders/${id}/documents/`),
// Comments
getDocumentComments: (documentId: number) => request(`/api/v1/document-comments/?document=${documentId}`),
createDocumentComment: (data: { document: number; text: string; parent?: number }) =>
request('/api/v1/document-comments/', { method: 'POST', body: JSON.stringify({ document_id: data.document, text: data.text, parent_id: data.parent }) }),
updateDocumentComment: (id: number, text: string) => request(`/api/v1/document-comments/${id}/`, { method: 'PATCH', body: JSON.stringify({ text }) }),
deleteDocumentComment: (id: number): Promise<void> => request(`/api/v1/document-comments/${id}/`, { method: 'DELETE' }),
getCommentReplies: (commentId: number) => request(`/api/v1/document-comments/${commentId}/replies/`),
// Tags
getDocumentTags: (params?: { page?: number; page_size?: number; limit?: number }) => {
const qp = new URLSearchParams();
if (params?.page) qp.append('page', params.page.toString());
const pageSize = params?.page_size ?? params?.limit;
if (pageSize) qp.append('page_size', pageSize.toString());
const qs = qp.toString();
return request(`/api/v1/document-tags/${qs ? '?' + qs : ''}`);
},
getDocumentTag: (id: number) => request(`/api/v1/document-tags/${id}/`),
createDocumentTag: (data: { name: string; color?: string }) => request('/api/v1/document-tags/', { method: 'POST', body: JSON.stringify(data) }),
updateDocumentTag: (id: number, data: { name?: string; color?: string }) => request(`/api/v1/document-tags/${id}/`, { method: 'PATCH', body: JSON.stringify(data) }),
deleteDocumentTag: (id: number): Promise<void> => request(`/api/v1/document-tags/${id}/`, { method: 'DELETE' }),
getDocumentsByTag: (tagId: number) => request(`/api/v1/document-tags/${tagId}/documents/`),
// Related
getRelatedDocuments: (id: number) => request(`/api/v1/documents/${id}/related/`),
addRelatedDocument: (id: number, relatedId: number) => request(`/api/v1/documents/${id}/add_related/`, { method: 'POST', body: JSON.stringify({ related_document_id: relatedId }) }),
removeRelatedDocument: (id: number, relatedId: number) => request(`/api/v1/documents/${id}/remove_related/`, { method: 'DELETE', body: JSON.stringify({ related_document_id: relatedId }) }),
// Thumbnails
getDocumentThumbnail: (id: number, size?: 'small' | 'medium' | 'large' | 'original'): string => {
const sizeParam = size ? `?size=${size}` : '';
return `/api/v1/documents/${id}/thumbnail/${sizeParam}`;
},
};
}