add possibility to disable delete and upload of books
This commit is contained in:
@@ -151,6 +151,8 @@ npm run dev
|
|||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
|
#### Backend
|
||||||
|
|
||||||
| Variable | Required | Description |
|
| Variable | Required | Description |
|
||||||
|----------|----------|-------------|
|
|----------|----------|-------------|
|
||||||
| `OPENAI_API_KEY` | Yes | OpenAI API key for embeddings and chat |
|
| `OPENAI_API_KEY` | Yes | OpenAI API key for embeddings and chat |
|
||||||
@@ -159,3 +161,14 @@ npm run dev
|
|||||||
| `DB_USERNAME` | Yes | Database username |
|
| `DB_USERNAME` | Yes | Database username |
|
||||||
| `DB_PASSWORD` | Yes | Database password |
|
| `DB_PASSWORD` | Yes | Database password |
|
||||||
| `FIGURE_STORAGE_PATH` | No | Base path for uploaded PDFs and extracted figures (default: `./uploads`) |
|
| `FIGURE_STORAGE_PATH` | No | Base path for uploaded PDFs and extracted figures (default: `./uploads`) |
|
||||||
|
| `UPLOAD_ENABLED` | No | Set to `false` to disable the book upload endpoint (default: `true`) |
|
||||||
|
| `DELETE_ENABLED` | No | Set to `false` to disable the book delete endpoint (default: `true`) |
|
||||||
|
|
||||||
|
#### Frontend
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `VITE_API_URL` | No | Backend API base URL (default: `/api/v1`) |
|
||||||
|
| `VITE_APP_PASSWORD` | Yes | Shared password for HTTP Basic auth (must match `APP_PASSWORD`) |
|
||||||
|
| `VITE_UPLOAD_ENABLED` | No | Set to `false` to hide the upload UI (default: `true`) |
|
||||||
|
| `VITE_DELETE_ENABLED` | No | Set to `false` to hide the delete button (default: `true`) |
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.aiteacher.book;
|
|||||||
import com.aiteacher.document.FigureEntity;
|
import com.aiteacher.document.FigureEntity;
|
||||||
import com.aiteacher.document.FigureRepository;
|
import com.aiteacher.document.FigureRepository;
|
||||||
import com.aiteacher.document.MarkdownStorageService;
|
import com.aiteacher.document.MarkdownStorageService;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -22,6 +23,12 @@ public class BookController {
|
|||||||
private final FigureRepository figureRepository;
|
private final FigureRepository figureRepository;
|
||||||
private final MarkdownStorageService markdownStorageService;
|
private final MarkdownStorageService markdownStorageService;
|
||||||
|
|
||||||
|
@Value("${app.features.upload-enabled:true}")
|
||||||
|
private boolean uploadEnabled;
|
||||||
|
|
||||||
|
@Value("${app.features.delete-enabled:true}")
|
||||||
|
private boolean deleteEnabled;
|
||||||
|
|
||||||
public BookController(BookService bookService, FigureRepository figureRepository,
|
public BookController(BookService bookService, FigureRepository figureRepository,
|
||||||
MarkdownStorageService markdownStorageService) {
|
MarkdownStorageService markdownStorageService) {
|
||||||
this.bookService = bookService;
|
this.bookService = bookService;
|
||||||
@@ -31,6 +38,7 @@ public class BookController {
|
|||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data")
|
@PostMapping(consumes = "multipart/form-data")
|
||||||
public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) throws IOException {
|
public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) throws IOException {
|
||||||
|
if (!uploadEnabled) return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
|
||||||
Book book = bookService.upload(file);
|
Book book = bookService.upload(file);
|
||||||
return ResponseEntity.status(HttpStatus.ACCEPTED).body(toSummaryResponse(book));
|
return ResponseEntity.status(HttpStatus.ACCEPTED).body(toSummaryResponse(book));
|
||||||
}
|
}
|
||||||
@@ -51,6 +59,7 @@ public class BookController {
|
|||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
public ResponseEntity<Void> delete(@PathVariable UUID id) {
|
public ResponseEntity<Void> delete(@PathVariable UUID id) {
|
||||||
|
if (!deleteEnabled) return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).build();
|
||||||
bookService.delete(id);
|
bookService.delete(id);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ logging:
|
|||||||
"[org.apache.pdfbox]": ERROR
|
"[org.apache.pdfbox]": ERROR
|
||||||
|
|
||||||
app:
|
app:
|
||||||
|
features:
|
||||||
|
upload-enabled: ${UPLOAD_ENABLED:true}
|
||||||
|
delete-enabled: ${DELETE_ENABLED:true}
|
||||||
auth:
|
auth:
|
||||||
password: ${APP_PASSWORD:changeme}
|
password: ${APP_PASSWORD:changeme}
|
||||||
figure-storage:
|
figure-storage:
|
||||||
|
|||||||
@@ -5,3 +5,9 @@ VITE_API_URL=/api/v1
|
|||||||
|
|
||||||
# Shared password for HTTP Basic auth (must match APP_PASSWORD on the backend).
|
# Shared password for HTTP Basic auth (must match APP_PASSWORD on the backend).
|
||||||
VITE_APP_PASSWORD=changeme
|
VITE_APP_PASSWORD=changeme
|
||||||
|
|
||||||
|
# Set to 'false' to hide the upload UI (frontend). Also set UPLOAD_ENABLED=false on the backend to block the endpoint.
|
||||||
|
VITE_UPLOAD_ENABLED=true
|
||||||
|
|
||||||
|
# Set to 'false' to hide the delete button (frontend). Also set DELETE_ENABLED=false on the backend to block the endpoint.
|
||||||
|
VITE_DELETE_ENABLED=true
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
Read
|
Read
|
||||||
</router-link>
|
</router-link>
|
||||||
<button
|
<button
|
||||||
|
v-if="deleteEnabled"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
:disabled="book.status === 'PROCESSING' || deleting"
|
:disabled="book.status === 'PROCESSING' || deleting"
|
||||||
@click="$emit('delete', book.id)"
|
@click="$emit('delete', book.id)"
|
||||||
@@ -59,6 +60,7 @@ import type { Book } from '@/stores/bookStore'
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
book: Book
|
book: Book
|
||||||
deleting?: boolean
|
deleting?: boolean
|
||||||
|
deleteEnabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
|
|||||||
Vendored
+2
@@ -3,6 +3,8 @@
|
|||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_URL: string
|
readonly VITE_API_URL: string
|
||||||
readonly VITE_APP_PASSWORD: string
|
readonly VITE_APP_PASSWORD: string
|
||||||
|
readonly VITE_UPLOAD_ENABLED: string
|
||||||
|
readonly VITE_DELETE_ENABLED: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="upload-view">
|
<div class="upload-view">
|
||||||
<h1 class="page-title">Book Library</h1>
|
<h1 class="page-title">Book Library</h1>
|
||||||
<p class="page-subtitle">Upload medical textbooks (PDF) to build the knowledge base.</p>
|
<p v-if="uploadEnabled" class="page-subtitle">Upload medical textbooks (PDF) to build the knowledge base.</p>
|
||||||
|
|
||||||
<!-- Upload Section -->
|
<!-- Upload Section -->
|
||||||
<div class="upload-section card">
|
<div v-if="uploadEnabled" class="upload-section card">
|
||||||
<h2 class="section-title">Upload a Book</h2>
|
<h2 class="section-title">Upload a Book</h2>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
:key="book.id"
|
:key="book.id"
|
||||||
:book="book"
|
:book="book"
|
||||||
:deleting="deletingId === book.id"
|
:deleting="deletingId === book.id"
|
||||||
|
:delete-enabled="deleteEnabled"
|
||||||
@delete="handleDelete"
|
@delete="handleDelete"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +100,9 @@ import { ref, onMounted, onUnmounted, inject } from 'vue'
|
|||||||
import { useBookStore } from '@/stores/bookStore'
|
import { useBookStore } from '@/stores/bookStore'
|
||||||
import BookCard from '@/components/BookCard.vue'
|
import BookCard from '@/components/BookCard.vue'
|
||||||
|
|
||||||
|
const uploadEnabled = import.meta.env.VITE_UPLOAD_ENABLED !== 'false'
|
||||||
|
const deleteEnabled = import.meta.env.VITE_DELETE_ENABLED !== 'false'
|
||||||
|
|
||||||
const bookStore = useBookStore()
|
const bookStore = useBookStore()
|
||||||
const showToast = inject<(msg: string, type?: 'error' | 'success') => void>('showToast')
|
const showToast = inject<(msg: string, type?: 'error' | 'success') => void>('showToast')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user