mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
51dd3d30f3
- add URI versioning: global prefix changed to api/v1, all frontend call sites updated - add AbortSignal.timeout to all outbound fetch calls (10s API, 15s scrapers) - add X-Request-Id response header and requestId field in error payloads - add response compression via @fastify/compress (gzip + brotli) - add security headers via @fastify/helmet with CSP for the Vite SPA - tune pg pool: max 20, idle/connection/statement timeouts on app and migration pools - add GET /api/v1/health endpoint via @nestjs/terminus with DB connectivity check
73 lines
1.8 KiB
TypeScript
73 lines
1.8 KiB
TypeScript
import { computed, ref, watch } from 'vue'
|
|
import { api } from '@/lib/api'
|
|
|
|
export interface Bookmark {
|
|
id: number
|
|
bookId: number
|
|
cfi: string
|
|
title: string
|
|
createdAt: string
|
|
}
|
|
|
|
export function useBookmarks() {
|
|
const bookmarks = ref<Bookmark[]>([])
|
|
const currentCfi = ref<string | null>(null)
|
|
const loadError = ref<string | null>(null)
|
|
|
|
const isCurrentCfiBookmarked = computed(() => {
|
|
if (!currentCfi.value) return false
|
|
return bookmarks.value.some((b) => b.cfi === currentCfi.value)
|
|
})
|
|
|
|
function setCfi(cfi: string | null) {
|
|
currentCfi.value = cfi
|
|
}
|
|
|
|
async function load(bookId: number) {
|
|
loadError.value = null
|
|
const res = await api(`/api/v1/books/${bookId}/bookmarks`)
|
|
if (!res.ok) {
|
|
loadError.value = 'Failed to load'
|
|
return
|
|
}
|
|
bookmarks.value = await res.json()
|
|
}
|
|
|
|
async function toggle(bookId: number, cfi: string, title: string) {
|
|
const existing = bookmarks.value.find((b) => b.cfi === cfi)
|
|
if (existing) {
|
|
await remove(bookId, existing.id)
|
|
} else {
|
|
const res = await api(`/api/v1/books/${bookId}/bookmarks`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ cfi, title }),
|
|
})
|
|
if (res.ok) {
|
|
const created: Bookmark = await res.json()
|
|
bookmarks.value = [...bookmarks.value, created]
|
|
}
|
|
}
|
|
}
|
|
|
|
async function remove(bookId: number, bookmarkId: number) {
|
|
const res = await api(`/api/v1/books/${bookId}/bookmarks/${bookmarkId}`, {
|
|
method: 'DELETE',
|
|
})
|
|
if (res.ok) {
|
|
bookmarks.value = bookmarks.value.filter((b) => b.id !== bookmarkId)
|
|
}
|
|
}
|
|
|
|
return {
|
|
bookmarks,
|
|
isCurrentCfiBookmarked,
|
|
currentCfi,
|
|
loadError,
|
|
setCfi,
|
|
load,
|
|
toggle,
|
|
remove,
|
|
}
|
|
}
|