3898ce7be4
- BACKFILL_TIMEOUT_SECONDS 1800→1170: keep the subprocess timeout 30s below Celery's hard time_limit=1200 so SIGKILL doesn't beat TimeoutExpired (matched the tick 870s/900s rationale). Backfill runs that hit the cap let the next tick continue via the archive. - recover_interrupted_tasks orphan UPDATE now stamps finished_at; without it cleanup_old_tasks' WHERE finished_at<cutoff never reaped orphan-swept rows. recover_stalled_task_runs also now sets duration_ms (matches celery_signals.finalize's millisecond math). - ExtensionService.quick_add_source arms NEW_SOURCE_BACKFILL_RUNS=3 on Source creation, mirroring SourceService.create. Without it, Firefox quick-add on a creator with >20 unsynced posts walked the full feed until subprocess timeout. Renamed the constant from _NEW_SOURCE_BACKFILL_RUNS so it can be imported cross-module. - gallery_dl.verify() accepts TIER_LIMITED as auth-success alongside NO_NEW_CONTENT — the download path (line 712) already does, and TIER_LIMITED proves auth reached the post and was told it was tier-gated. Verify endpoint previously showed red on this and prompted operators to rotate working cookies. - prune_unused_tags now runs a single DELETE with the NOT-IN predicate find_unused_tags uses, instead of SELECT-ids → DELETE-WHERE-IN. Removes the psycopg 65535-param cliff that would have surfaced on a tag explosion (>65k unused tags). - credentials.upload() reflects the returned record into the store cache (`.set(platform, rec)`) instead of evicting it; previously the card briefly rendered "no credential" between upload and loadAll().
60 lines
1.8 KiB
JavaScript
60 lines
1.8 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { useApi } from '../composables/useApi.js'
|
|
import { useAsyncAction } from '../composables/useAsyncAction.js'
|
|
|
|
export const useCredentialsStore = defineStore('credentials', () => {
|
|
const api = useApi()
|
|
|
|
const byPlatform = ref(new Map())
|
|
const extensionKey = ref(null)
|
|
const { loading, error, run } = useAsyncAction()
|
|
|
|
async function loadAll() {
|
|
await run(async () => {
|
|
const arr = await api.get('/api/credentials')
|
|
const m = new Map()
|
|
for (const c of arr) m.set(c.platform, c)
|
|
byPlatform.value = m
|
|
})
|
|
}
|
|
|
|
async function upload(platform, credential_type, data) {
|
|
const rec = await api.post('/api/credentials', {
|
|
body: { platform, credential_type, data },
|
|
})
|
|
// Reflect the returned record immediately — the previous .delete()
|
|
// call left the card rendering "no credential" for the gap between
|
|
// upload completion and the caller's follow-up loadAll(). Audit
|
|
// 2026-06-02.
|
|
byPlatform.value.set(platform, rec)
|
|
return rec
|
|
}
|
|
|
|
async function remove(platform) {
|
|
await api.delete(`/api/credentials/${platform}`)
|
|
byPlatform.value.delete(platform)
|
|
}
|
|
|
|
// Runs gallery-dl --simulate against an enabled source for the
|
|
// platform. Returns {valid: bool|null, reason, last_verified?}.
|
|
async function verify(platform) {
|
|
return await api.post(`/api/credentials/${platform}/verify`)
|
|
}
|
|
|
|
async function loadKey() {
|
|
const body = await api.get('/api/settings/extension_api_key')
|
|
extensionKey.value = body.key
|
|
}
|
|
|
|
async function rotateKey() {
|
|
const body = await api.post('/api/settings/extension_api_key/rotate')
|
|
extensionKey.value = body.key
|
|
}
|
|
|
|
return {
|
|
byPlatform, extensionKey, loading, error,
|
|
loadAll, upload, remove, verify, loadKey, rotateKey,
|
|
}
|
|
})
|