package com.aiteacher.book; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; @Service public class BookService { private final BookRepository bookRepository; private final BookEmbeddingService bookEmbeddingService; private final Path bookStoragePath; public BookService( BookRepository bookRepository, BookEmbeddingService bookEmbeddingService, @Value("${app.figure-storage.base-path:./uploads}") String basePath) { this.bookRepository = bookRepository; this.bookEmbeddingService = bookEmbeddingService; this.bookStoragePath = Paths.get(basePath).toAbsolutePath().normalize().resolve("books"); } public Book upload(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".pdf")) { throw new IllegalArgumentException("Only PDF files are accepted."); } String title = deriveTitle(originalFilename); Book book = new Book(title, originalFilename, file.getSize()); book = bookRepository.save(book); // Persist PDF in a stable location for potential re-embedding Files.createDirectories(bookStoragePath); Path pdfPath = bookStoragePath.resolve(book.getId() + ".pdf"); file.transferTo(pdfPath.toFile()); UUID bookId = book.getId(); bookEmbeddingService.embedBook(bookId, title, pdfPath); return book; } public Book reembed(UUID id) { Book book = bookRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("Book not found.")); if (book.getStatus() == BookStatus.PROCESSING) { throw new IllegalStateException("Book is already being processed."); } Path pdfPath = bookStoragePath.resolve(id + ".pdf"); if (!Files.exists(pdfPath)) { throw new IllegalStateException( "Original PDF not found. Please re-upload the book before re-embedding."); } bookEmbeddingService.deleteBookChunks(id); bookEmbeddingService.embedBook(id, book.getTitle(), pdfPath); return book; } public List listAll() { return bookRepository.findAll(); } public Book getById(UUID id) { return bookRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("Book not found.")); } public void delete(UUID id) { Book book = bookRepository.findById(id) .orElseThrow(() -> new NoSuchElementException("Book not found.")); if (book.getStatus() == BookStatus.PROCESSING) { throw new IllegalStateException("Cannot delete a book that is currently being processed."); } bookEmbeddingService.deleteBookChunks(id); // Delete the stored PDF Path pdfPath = bookStoragePath.resolve(id + ".pdf"); try { Files.deleteIfExists(pdfPath); } catch (IOException ex) { // Non-fatal — log only } bookRepository.deleteById(id); } private String deriveTitle(String filename) { String name = filename.replaceAll("(?i)\\.pdf$", ""); name = name.replaceAll("[-_]", " "); if (!name.isEmpty()) { name = Character.toUpperCase(name.charAt(0)) + name.substring(1); } return name; } }