Files
FabledCurator/frontend/src/stores/credentials.js
T
bvandeusen 3898ce7be4
CI / lint (push) Successful in 5s
CI / backend-lint-and-test (push) Successful in 22s
CI / frontend-build (push) Failing after 27s
CI / intimp (push) Successful in 3m40s
CI / intapi (push) Successful in 8m6s
CI / intcore (push) Successful in 9m3s
fix(audit-g1): six one-liner drift fixes from 2026-06-02 audit
- 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().
2026-06-02 11:24:05 -04:00

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,
}
})