Files
osdb-1-claude-opusplan/frontend/src/api/publications.ts
T
juliaandClaude Sonnet 4.6 1b381d9385 feat: protect write endpoints with JWT auth (story #08)
Add bcrypt user file auth, JWT middleware on all write routes, Pinia
authStore with localStorage persistence, login view with redirect
support, and v-if guards on all CRUD controls and admin nav link.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 11:18:42 +02:00

185 lines
6.0 KiB
TypeScript

export interface Publication {
id: number
title: string
author: string | null
osdb_pub_id: string
image_url: string | null
created_at: string
updated_at: string
}
export interface PublicationWriteDTO {
title: string
author?: string | null
osdb_pub_id: string
}
export interface PublicationFruit {
fruit_id: number
name: string
osdb_number: string
}
export interface PublicationDescription {
id: number
publication_id: number
fruit_id: number
fruit_name: string
publication_title: string
url: string
created_at: string
}
export interface PublicationFruitImage {
id: number
publication_id: number
publication_title: string
publication_author: string | null
fruit_id: number
filename: string | null
image_type: string
url: string
created_at: string
}
export function pubImageUrl(pubId: number): string {
return `/api/v1/publications/${pubId}/image`
}
export function pubDescriptionUrl(pubId: number, descId: number): string {
return `/api/v1/publications/${pubId}/descriptions/${descId}`
}
export function pubFruitImageUrl(pubId: number, imgId: number): string {
return `/api/v1/publications/${pubId}/fruit-images/${imgId}`
}
import { bearerHeader, handleUnauthorized } from './authHeader'
async function checkOk(res: Response): Promise<Response> {
if (!res.ok) {
if (res.status === 401) handleUnauthorized()
let msg = `${res.status}`
try {
const body = await res.json()
msg = body.error ?? body.errors?.join(', ') ?? msg
} catch {
// ignore
}
const err = new Error(msg) as Error & { status: number }
err.status = res.status
throw err
}
return res
}
export async function listPublications(): Promise<Publication[]> {
const res = await fetch('/api/v1/publications')
return (await checkOk(res)).json()
}
export async function getPublication(id: number): Promise<Publication> {
const res = await fetch(`/api/v1/publications/${id}`)
return (await checkOk(res)).json()
}
export async function createPublication(dto: PublicationWriteDTO): Promise<Publication> {
const res = await fetch('/api/v1/publications', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...bearerHeader() },
body: JSON.stringify(dto),
})
return (await checkOk(res)).json()
}
export async function updatePublication(id: number, dto: PublicationWriteDTO): Promise<Publication> {
const res = await fetch(`/api/v1/publications/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...bearerHeader() },
body: JSON.stringify(dto),
})
return (await checkOk(res)).json()
}
export async function deletePublication(id: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${id}`, { method: 'DELETE', headers: bearerHeader() })
await checkOk(res)
}
export async function uploadCoverImage(pubId: number, file: File): Promise<void> {
const form = new FormData()
form.append('image', file)
const res = await fetch(`/api/v1/publications/${pubId}/image`, { method: 'POST', body: form, headers: bearerHeader() })
await checkOk(res)
}
export async function deleteCoverImage(pubId: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${pubId}/image`, { method: 'DELETE', headers: bearerHeader() })
await checkOk(res)
}
export async function listPubFruits(pubId: number): Promise<PublicationFruit[]> {
const res = await fetch(`/api/v1/publications/${pubId}/fruits`)
return (await checkOk(res)).json()
}
export async function linkFruit(pubId: number, fruitId: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${pubId}/fruits`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...bearerHeader() },
body: JSON.stringify({ fruit_id: fruitId }),
})
await checkOk(res)
}
export async function unlinkFruit(pubId: number, fruitId: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${pubId}/fruits/${fruitId}`, { method: 'DELETE', headers: bearerHeader() })
await checkOk(res)
}
export async function listDescriptions(pubId: number): Promise<PublicationDescription[]> {
const res = await fetch(`/api/v1/publications/${pubId}/descriptions`)
return (await checkOk(res)).json()
}
export async function uploadDescription(pubId: number, fruitId: number, file: File): Promise<PublicationDescription> {
const form = new FormData()
form.append('pdf', file)
form.append('fruit_id', String(fruitId))
const res = await fetch(`/api/v1/publications/${pubId}/descriptions`, { method: 'POST', body: form, headers: bearerHeader() })
return (await checkOk(res)).json()
}
export async function deleteDescription(pubId: number, descId: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${pubId}/descriptions/${descId}`, { method: 'DELETE', headers: bearerHeader() })
await checkOk(res)
}
export async function listPubFruitImages(pubId: number): Promise<PublicationFruitImage[]> {
const res = await fetch(`/api/v1/publications/${pubId}/fruit-images`)
return (await checkOk(res)).json()
}
export async function uploadPubFruitImage(pubId: number, fruitId: number, file: File): Promise<PublicationFruitImage> {
const form = new FormData()
form.append('image', file)
form.append('fruit_id', String(fruitId))
const res = await fetch(`/api/v1/publications/${pubId}/fruit-images`, { method: 'POST', body: form, headers: bearerHeader() })
return (await checkOk(res)).json()
}
export async function deletePubFruitImage(pubId: number, imgId: number): Promise<void> {
const res = await fetch(`/api/v1/publications/${pubId}/fruit-images/${imgId}`, { method: 'DELETE', headers: bearerHeader() })
await checkOk(res)
}
export async function getFruitDescriptions(fruitId: number): Promise<PublicationDescription[]> {
const res = await fetch(`/api/v1/fruits/${fruitId}/descriptions`)
return (await checkOk(res)).json()
}
export async function getFruitPublicationImages(fruitId: number): Promise<PublicationFruitImage[]> {
const res = await fetch(`/api/v1/fruits/${fruitId}/publication-images`)
return (await checkOk(res)).json()
}