import { defineStore } from 'pinia' import { ref } from 'vue' import { useApi } from '../composables/useApi.js' export const useAdminStore = defineStore('admin', () => { const api = useApi() const lastError = ref(null) // --- Tier-C: artist cascade --------------------------------------- async function projectArtistCascade(slug) { lastError.value = null try { return await api.post( `/api/admin/artists/${encodeURIComponent(slug)}/cascade-delete`, { body: { dry_run: true } }, ) } catch (e) { lastError.value = e.message throw e } } async function dispatchArtistCascade(slug, confirm) { lastError.value = null try { return await api.post( `/api/admin/artists/${encodeURIComponent(slug)}/cascade-delete`, { body: { dry_run: false, confirm } }, ) } catch (e) { lastError.value = e.message throw e } } // --- Tier-C: bulk image delete ------------------------------------ async function projectBulkImageDelete(imageIds) { lastError.value = null try { return await api.post( '/api/admin/images/bulk-delete', { body: { image_ids: imageIds, dry_run: true } }, ) } catch (e) { lastError.value = e.message throw e } } async function dispatchBulkImageDelete(imageIds, confirm) { lastError.value = null try { return await api.post( '/api/admin/images/bulk-delete', { body: { image_ids: imageIds, dry_run: false, confirm } }, ) } catch (e) { lastError.value = e.message throw e } } // --- Tier-B: tag delete + merge ----------------------------------- async function deleteTag(tagId) { lastError.value = null try { return await api.delete(`/api/admin/tags/${tagId}`) } catch (e) { lastError.value = e.message throw e } } async function mergeTags(destId, sourceId) { lastError.value = null try { return await api.post( `/api/admin/tags/${destId}/merge`, { body: { source_id: sourceId } }, ) } catch (e) { lastError.value = e.message throw e } } async function tagUsageCount(tagId) { lastError.value = null try { const body = await api.get(`/api/admin/tags/${tagId}/usage-count`) return body.count } catch (e) { lastError.value = e.message throw e } } // --- Tier-A: prune unused ----------------------------------------- async function pruneUnusedTags({ dryRun = true } = {}) { lastError.value = null try { return await api.post( '/api/admin/tags/prune-unused', { body: { dry_run: dryRun } }, ) } catch (e) { lastError.value = e.message throw e } } // --- Task progress polling (taps FC-3i activity dashboard) -------- /** * Polls /api/system/activity/runs?queue=maintenance every 3s, * resolves when a task_run row with the given celery task_id * reaches a terminal status (ok / error / timeout). Returns the * row. Times out after 30 min by default. */ async function pollTaskUntilDone(taskId, { timeoutMs = 1_800_000 } = {}) { const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { const body = await api.get( '/api/system/activity/runs', { params: { queue: 'maintenance', limit: 20 } }, ) const row = (body.runs || []).find(r => r.celery_task_id === taskId) if (row && ['ok', 'error', 'timeout'].includes(row.status)) { return row } await new Promise((r) => setTimeout(r, 3000)) } throw new Error(`task ${taskId} did not finish within ${timeoutMs}ms`) } return { lastError, projectArtistCascade, dispatchArtistCascade, projectBulkImageDelete, dispatchBulkImageDelete, deleteTag, mergeTags, tagUsageCount, pruneUnusedTags, pollTaskUntilDone, } })