Skip to content

Commit c663b19

Browse files
committed
Merge develop into master for the release
2 parents ab1abba + 01cdbf7 commit c663b19

File tree

192 files changed

+17931
-15574
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

192 files changed

+17931
-15574
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Organize, read, annotate, sync across devices, and share, all without relying on
2626
<p align="center">
2727
<a href="https://booklore.org/">🌐 Website</a> ·
2828
<a href="https://booklore.org/docs/getting-started">📖 Docs</a> ·
29-
<a href="#🎮-live-demo">🎮 Demo</a> ·
30-
<a href="#🚀-quick-start">🚀 Quick Start</a> ·
29+
<a href="#-live-demo">🎮 Demo</a> ·
30+
<a href="#-quick-start">🚀 Quick Start</a> ·
3131
<a href="https://discord.gg/Ee5hd458Uz">💬 Discord</a>
3232
</p>
3333

booklore-api/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ out/
3535

3636
### VS Code ###
3737
.vscode/
38+
39+
### Local scripts ###
40+
local/

booklore-api/src/main/java/org/booklore/BookloreApplication.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
@SpringBootApplication
1515
public class BookloreApplication {
1616

17+
static {
18+
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
19+
}
20+
1721
public static void main(String[] args) {
1822
SpringApplication.run(BookloreApplication.class, args);
1923
}

booklore-api/src/main/java/org/booklore/config/security/SecurityConfig.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,11 @@ public RestTemplate noRedirectRestTemplate() {
262262
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
263263
super.prepareConnection(connection, httpMethod);
264264
connection.setInstanceFollowRedirects(false);
265+
String targetHost = FileService.getTargetHost();
266+
if (targetHost != null) {
267+
connection.setRequestProperty("Host", targetHost);
268+
}
265269
if (connection instanceof HttpsURLConnection httpsConnection) {
266-
String targetHost = FileService.getTargetHost();
267270
if (targetHost != null) {
268271
// Set original host for SNI (even if connecting to IP)
269272
SSLSocketFactory defaultFactory = httpsConnection.getSSLSocketFactory();

booklore-api/src/main/java/org/booklore/controller/AdditionalFileController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import org.booklore.config.security.annotation.CheckBookAccess;
44
import org.booklore.model.dto.BookFile;
5+
import org.booklore.model.dto.request.DetachBookFileRequest;
6+
import org.booklore.model.dto.response.DetachBookFileResponse;
57
import org.booklore.model.enums.BookFileType;
8+
import org.booklore.service.book.BookFileDetachmentService;
69
import org.booklore.service.file.AdditionalFileService;
710
import org.booklore.service.upload.FileUploadService;
811
import lombok.AllArgsConstructor;
@@ -22,6 +25,7 @@ public class AdditionalFileController {
2225

2326
private final AdditionalFileService additionalFileService;
2427
private final FileUploadService fileUploadService;
28+
private final BookFileDetachmentService bookFileDetachmentService;
2529

2630
@GetMapping
2731
@CheckBookAccess(bookIdParam = "bookId")
@@ -69,4 +73,14 @@ public ResponseEntity<Void> deleteAdditionalFile(
6973
additionalFileService.deleteAdditionalFile(fileId);
7074
return ResponseEntity.noContent().build();
7175
}
76+
77+
@PostMapping("/{fileId}/detach")
78+
@CheckBookAccess(bookIdParam = "bookId")
79+
@PreAuthorize("@securityUtil.canManageLibrary() or @securityUtil.isAdmin()")
80+
public ResponseEntity<DetachBookFileResponse> detachFile(
81+
@PathVariable Long bookId,
82+
@PathVariable Long fileId,
83+
@RequestBody DetachBookFileRequest request) {
84+
return ResponseEntity.ok(bookFileDetachmentService.detachBookFile(bookId, fileId, request.copyMetadata()));
85+
}
7286
}

booklore-api/src/main/java/org/booklore/controller/BookController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.booklore.model.dto.request.ReadProgressRequest;
1313
import org.booklore.model.dto.request.ReadStatusUpdateRequest;
1414
import org.booklore.model.dto.request.ShelvesAssignmentRequest;
15+
import org.booklore.model.dto.response.AttachBookFileResponse;
1516
import org.booklore.model.dto.response.BookDeletionResponse;
1617
import org.booklore.model.dto.response.BookStatusUpdateResponse;
1718
import org.booklore.model.dto.response.DuplicateGroup;
@@ -285,7 +286,7 @@ public ResponseEntity<List<DuplicateGroup>> findDuplicates(
285286
})
286287
@PostMapping("/{targetBookId}/attach-file")
287288
@PreAuthorize("@securityUtil.canManageLibrary() or @securityUtil.isAdmin()")
288-
public ResponseEntity<Book> attachBookFiles(
289+
public ResponseEntity<AttachBookFileResponse> attachBookFiles(
289290
@Parameter(description = "ID of the target book to attach the files to") @PathVariable Long targetBookId,
290291
@Parameter(description = "Request containing source book IDs and delete option") @RequestBody @Valid AttachBookFileRequest request) {
291292
return ResponseEntity.ok(bookFileAttachmentService.attachBookFiles(targetBookId, request.getSourceBookIds(), request.isMoveFiles()));

booklore-api/src/main/java/org/booklore/controller/KomgaController.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import lombok.RequiredArgsConstructor;
77
import lombok.extern.slf4j.Slf4j;
88
import org.booklore.config.JacksonConfig;
9+
import org.booklore.config.security.service.AuthenticationService;
10+
import org.booklore.config.security.userdetails.OpdsUserDetails;
911
import org.booklore.mapper.komga.KomgaMapper;
1012
import org.booklore.model.dto.komga.KomgaBookDto;
1113
import org.booklore.model.dto.komga.KomgaLibraryDto;
1214
import org.booklore.model.dto.komga.KomgaPageableDto;
1315
import org.booklore.model.dto.komga.KomgaSeriesDto;
1416
import org.booklore.service.book.BookService;
1517
import org.booklore.service.komga.KomgaService;
18+
import org.booklore.service.opds.OpdsBookService;
1619
import org.booklore.service.opds.OpdsUserV2Service;
1720
import org.springframework.beans.factory.annotation.Qualifier;
1821
import org.springframework.core.io.Resource;
@@ -36,6 +39,8 @@ public class KomgaController {
3639

3740
private final KomgaService komgaService;
3841
private final BookService bookService;
42+
private final OpdsBookService opdsBookService;
43+
private final AuthenticationService authenticationService;
3944
private final OpdsUserV2Service opdsUserV2Service;
4045
private final KomgaMapper komgaMapper;
4146

@@ -147,6 +152,7 @@ public ResponseEntity<Resource> getBookPage(
147152
@Parameter(description = "Book ID") @PathVariable Long bookId,
148153
@Parameter(description = "Page number") @PathVariable Integer pageNumber,
149154
@Parameter(description = "Convert image format (e.g., 'png')") @RequestParam(required = false) String convert) {
155+
opdsBookService.validateBookContentAccess(bookId, getOpdsUserId());
150156
try {
151157
boolean convertToPng = "png".equalsIgnoreCase(convert);
152158
Resource pageImage = komgaService.getBookPageImage(bookId, pageNumber, convertToPng);
@@ -166,13 +172,15 @@ public ResponseEntity<Resource> getBookPage(
166172
@GetMapping("/v1/books/{bookId}/file")
167173
public ResponseEntity<Resource> downloadBook(
168174
@Parameter(description = "Book ID") @PathVariable Long bookId) {
175+
opdsBookService.validateBookContentAccess(bookId, getOpdsUserId());
169176
return bookService.downloadBook(bookId);
170177
}
171178

172179
@Operation(summary = "Get book thumbnail")
173180
@GetMapping("/v1/books/{bookId}/thumbnail")
174181
public ResponseEntity<Resource> getBookThumbnail(
175182
@Parameter(description = "Book ID") @PathVariable Long bookId) {
183+
opdsBookService.validateBookContentAccess(bookId, getOpdsUserId());
176184
Resource coverImage = bookService.getBookThumbnail(bookId);
177185
return ResponseEntity.ok()
178186
.header("Content-Type", "image/jpeg")
@@ -208,4 +216,11 @@ public ResponseEntity<String> getCollections(
208216
@Parameter(description = "Return all collections without paging") @RequestParam(defaultValue = "false") boolean unpaged) {
209217
return writeJson(komgaService.getCollections(page, size, unpaged));
210218
}
219+
220+
private Long getOpdsUserId() {
221+
OpdsUserDetails details = authenticationService.getOpdsUser();
222+
return details != null && details.getOpdsUserV2() != null
223+
? details.getOpdsUserV2().getUserId()
224+
: null;
225+
}
211226
}

booklore-api/src/main/java/org/booklore/controller/OpdsController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.booklore.controller;
22

3+
import org.booklore.config.security.service.AuthenticationService;
4+
import org.booklore.config.security.userdetails.OpdsUserDetails;
35
import org.booklore.service.book.BookDownloadService;
46
import org.booklore.service.book.BookService;
7+
import org.booklore.service.opds.OpdsBookService;
58
import org.booklore.service.opds.OpdsFeedService;
69
import io.swagger.v3.oas.annotations.Operation;
710
import io.swagger.v3.oas.annotations.Parameter;
@@ -34,6 +37,8 @@ public class OpdsController {
3437
private final OpdsFeedService opdsFeedService;
3538
private final BookService bookService;
3639
private final BookDownloadService bookDownloadService;
40+
private final OpdsBookService opdsBookService;
41+
private final AuthenticationService authenticationService;
3742

3843
@Operation(summary = "Download book file", description = "Download the book file by its ID. Optionally specify a fileId to download a specific format.")
3944
@ApiResponses({
@@ -44,6 +49,7 @@ public class OpdsController {
4449
public ResponseEntity<Resource> downloadBook(
4550
@Parameter(description = "ID of the book to download") @PathVariable("bookId") Long bookId,
4651
@Parameter(description = "Optional ID of a specific file format to download") @RequestParam(required = false) Long fileId) {
52+
opdsBookService.validateBookContentAccess(bookId, getOpdsUserId());
4753
if (fileId != null) {
4854
return bookDownloadService.downloadBookFile(bookId, fileId);
4955
}
@@ -57,6 +63,7 @@ public ResponseEntity<Resource> downloadBook(
5763
})
5864
@GetMapping("/{bookId}/cover")
5965
public ResponseEntity<Resource> getBookCover(@Parameter(description = "ID of the book") @PathVariable long bookId) {
66+
opdsBookService.validateBookContentAccess(bookId, getOpdsUserId());
6067
Resource coverImage = bookService.getBookThumbnail(bookId);
6168
String contentType = "image/jpeg";
6269
return ResponseEntity.ok()
@@ -169,4 +176,11 @@ public ResponseEntity<String> getSearchDescription() {
169176
.contentType(MediaType.parseMediaType("application/opensearchdescription+xml;charset=utf-8"))
170177
.body(searchDoc);
171178
}
179+
180+
private Long getOpdsUserId() {
181+
OpdsUserDetails details = authenticationService.getOpdsUser();
182+
return details != null && details.getOpdsUserV2() != null
183+
? details.getOpdsUserV2().getUserId()
184+
: null;
185+
}
172186
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package org.booklore.model.dto.request;
2+
3+
public record DetachBookFileRequest(boolean copyMetadata) {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.booklore.model.dto.response;
2+
3+
import org.booklore.model.dto.Book;
4+
5+
import java.util.List;
6+
7+
public record AttachBookFileResponse(Book updatedBook, List<Long> deletedSourceBookIds) {}

0 commit comments

Comments
 (0)