ytptube/ui/app/composables/useHistoryState.ts
2026-04-21 21:48:38 +03:00

184 lines
4.1 KiB
TypeScript

import { computed, ref } from 'vue';
import { useNotification } from '~/composables/useNotification';
import { useYtpConfig } from '~/composables/useYtpConfig';
import { parse_api_error, parse_list_response, request } from '~/utils';
import type { Pagination } from '~/types/responses';
import type { StoreItem } from '~/types/store';
type HistoryLoadOptions = {
order?: 'ASC' | 'DESC';
status?: string;
perPage?: number;
};
const items = ref<StoreItem[]>([]);
const pagination = ref<Pagination>({
page: 1,
per_page: 50,
total: 0,
total_pages: 0,
has_next: false,
has_prev: false,
});
const isLoading = ref<boolean>(false);
const isLoaded = ref<boolean>(false);
const lastError = ref<string | null>(null);
const throwInstead = ref(false);
const readJson = async (response: Response): Promise<unknown> => {
try {
const clone = response.clone();
return await clone.json();
} catch {
return null;
}
};
const ensureSuccess = async (response: Response): Promise<void> => {
if (response.ok) {
return;
}
const payload = await readJson(response);
const message = await parse_api_error(payload);
throw new Error(message);
};
const handleError = (error: unknown): void => {
const message = error instanceof Error ? error.message : 'Unexpected error occurred.';
lastError.value = message;
useNotification().error(message);
};
const pageSize = computed<number>(() => {
const config = useYtpConfig();
return Number(config.app.default_pagination || 50);
});
const buildQuery = (
page: number,
perPage: number,
order: 'ASC' | 'DESC' = 'DESC',
status?: string,
): string => {
const params = new URLSearchParams({
type: 'done',
page: String(page),
per_page: String(perPage),
order,
});
if (status) {
params.set('status', status);
}
return params.toString();
};
const loadHistory = async (page: number = 1, options: HistoryLoadOptions = {}): Promise<void> => {
const { order = 'DESC', status, perPage = pageSize.value } = options;
isLoading.value = true;
try {
const response = await request(`/api/history?${buildQuery(page, perPage, order, status)}`);
await ensureSuccess(response);
const data = await response.json();
const { items: loadedItems, pagination: paginationData } =
await parse_list_response<StoreItem>(data);
items.value = loadedItems;
pagination.value = paginationData;
isLoaded.value = true;
lastError.value = null;
} catch (error) {
handleError(error);
if (throwInstead.value) {
throw error;
}
} finally {
isLoading.value = false;
}
};
const reloadHistory = async (options: HistoryLoadOptions = {}): Promise<void> => {
const targetPage = isLoaded.value ? pagination.value.page : 1;
await loadHistory(targetPage, options);
};
const deleteHistoryItems = async (
options: {
ids?: string[];
status?: string;
removeFile?: boolean;
} = {},
): Promise<number> => {
const { ids, status, removeFile = true } = options;
if (!ids && !status) {
throw new Error('Either ids or status filter must be provided');
}
try {
const response = await request('/api/history', {
method: 'DELETE',
body: JSON.stringify({
type: 'done',
ids,
status,
remove_file: removeFile,
}),
});
await ensureSuccess(response);
const result = (await response.json()) as {
deleted?: number;
error?: string;
message?: string;
};
if (result.error || result.message) {
throw new Error(result.error || result.message);
}
return Number(result.deleted ?? 0);
} catch (error) {
handleError(error);
if (throwInstead.value) {
throw error;
}
return 0;
}
};
const resetHistory = (): void => {
items.value = [];
pagination.value = {
page: 1,
per_page: pageSize.value,
total: 0,
total_pages: 0,
has_next: false,
has_prev: false,
};
isLoaded.value = false;
lastError.value = null;
};
export const useHistoryState = () => {
return {
items,
pagination,
isLoading,
isLoaded,
lastError,
pageSize,
loadHistory,
reloadHistory,
deleteHistoryItems,
resetHistory,
};
};