56970fb66d
Operator-flagged 2026-05-28, two asks.
**1. Credential Verify (was missing vs GS — and now actually verifies).**
GS's Verify was a stub (`TODO: implement actual verification` — just
stamped last_verified). FC does a real check, which matters given the
recent auth pain (subscribestar age cookie, HF host-only PHPSESSID):
- GalleryDLService.verify(url, platform, cookies_path, auth_token) runs
gallery-dl in `--simulate --range 1-1` mode (no download) against the
URL with the materialized credentials, then reuses _categorize_error:
returncode 0 / NO_NEW_CONTENT → valid; AUTH_ERROR → invalid; other →
inconclusive (reason surfaced). 45s timeout.
- POST /api/credentials/<platform>/verify picks an enabled Source for
the platform to probe, runs verify, and on success stamps
credential.last_verified (new CredentialService.mark_verified).
Returns {valid: bool|null, reason, last_verified?}. valid=null means
untestable (no credential, or no enabled source to point at).
- CredentialCard gains a Verify button (on credentialed cards) + a
result chip (Verified ✓ / Failed / Untestable) and a toast with the
reason. SettingsTab reloads on @verified so last_verified refreshes.
**2. Live download-activity feedback.** The Downloads tab was static —
no way to tell if downloads were succeeding without manually hitting
Refresh. It now auto-polls: stats every 4s, and the event list too
while anything is queued/running. Polling pauses when the tab is
backgrounded (document.hidden) and the list reload is skipped on idle
ticks to stay light. A pulsing "● live" indicator next to the stat
chips shows when auto-refresh is active (queued+running > 0); honors
prefers-reduced-motion.
Tests: verify endpoint — untestable with no credential, untestable with
no enabled source, valid+stamped on success (gallery-dl mocked), and
auth-failure reported without stamping.
62 lines
1.6 KiB
JavaScript
62 lines
1.6 KiB
JavaScript
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import { useApi } from '../composables/useApi.js'
|
|
|
|
export const useCredentialsStore = defineStore('credentials', () => {
|
|
const api = useApi()
|
|
|
|
const byPlatform = ref(new Map())
|
|
const extensionKey = ref(null)
|
|
const loading = ref(false)
|
|
const error = ref(null)
|
|
|
|
async function loadAll() {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
const arr = await api.get('/api/credentials')
|
|
const m = new Map()
|
|
for (const c of arr) m.set(c.platform, c)
|
|
byPlatform.value = m
|
|
} catch (e) {
|
|
error.value = e
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function upload(platform, credential_type, data) {
|
|
const rec = await api.post('/api/credentials', {
|
|
body: { platform, credential_type, data },
|
|
})
|
|
byPlatform.value.delete(platform)
|
|
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,
|
|
}
|
|
})
|