Merge branch 'V2' into codex/add-pdf-to-json-and-json-to-pdf-features

This commit is contained in:
Anthony Stirling
2025-11-19 10:56:57 +00:00
committed by GitHub
206 changed files with 100129 additions and 94267 deletions

View File

@@ -29,7 +29,6 @@ import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.swagger.JsonDataResponse;
import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
@@ -49,13 +48,12 @@ public class EditTableOfContentsController {
@AutoJobPostMapping(
value = "/extract-bookmarks",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@JsonDataResponse
@Operation(
summary = "Extract PDF Bookmarks",
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
@ResponseBody
public List<Map<String, Object>> extractBookmarks(@RequestParam("file") MultipartFile file)
throws Exception {
public ResponseEntity<List<Map<String, Object>>> extractBookmarks(
@RequestParam("file") MultipartFile file) throws Exception {
PDDocument document = null;
try {
document = pdfDocumentFactory.load(file);
@@ -63,10 +61,10 @@ public class EditTableOfContentsController {
if (outline == null) {
log.info("No outline/bookmarks found in PDF");
return new ArrayList<>();
return ResponseEntity.ok(new ArrayList<>());
}
return extractBookmarkItems(document, outline);
return ResponseEntity.ok(extractBookmarkItems(document, outline));
} finally {
if (document != null) {
document.close();

View File

@@ -10,6 +10,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import io.swagger.v3.oas.annotations.Hidden;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.EndpointConfiguration;
import stirling.software.SPDF.config.InitialSetup;
import stirling.software.common.annotations.api.ConfigApi;
@@ -20,6 +22,7 @@ import stirling.software.common.service.UserServiceInterface;
@ConfigApi
@Hidden
@Slf4j
public class ConfigController {
private final ApplicationProperties applicationProperties;
@@ -59,9 +62,15 @@ public class ConfigController {
// Extract values from ApplicationProperties
configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar());
configData.put("languages", applicationProperties.getUi().getLanguages());
configData.put("logoStyle", applicationProperties.getUi().getLogoStyle());
// Security settings
configData.put("enableLogin", applicationProperties.getSecurity().getEnableLogin());
// enableLogin requires both the config flag AND proprietary features to be loaded
// If userService is null, proprietary module isn't loaded
// (DISABLE_ADDITIONAL_FEATURES=true or DOCKER_ENABLE_SECURITY=false)
boolean enableLogin =
applicationProperties.getSecurity().getEnableLogin() && userService != null;
configData.put("enableLogin", enableLogin);
// Mail settings - check both SMTP enabled AND invites enabled
boolean smtpEnabled = applicationProperties.getMail().isEnabled();

View File

@@ -193,6 +193,7 @@ stirling:
ui:
appNameNavbar: '' # name displayed on the navigation bar
logoStyle: classic # Options: 'classic' (default - classic S icon) or 'modern' (minimalist logo)
languages: [] # If empty, all languages are enabled. To display only German and Polish ["de_DE", "pl_PL"]. British English is always enabled.
endpoints:

View File

@@ -24,6 +24,7 @@ import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
@@ -86,9 +87,13 @@ class EditTableOfContentsControllerTest {
when(mockOutlineItem.getNextSibling()).thenReturn(null);
// When
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
ResponseEntity<List<Map<String, Object>>> response =
editTableOfContentsController.extractBookmarks(mockFile);
// Then
assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode());
List<Map<String, Object>> result = response.getBody();
assertNotNull(result);
assertEquals(1, result.size());
@@ -108,9 +113,13 @@ class EditTableOfContentsControllerTest {
when(mockCatalog.getDocumentOutline()).thenReturn(null);
// When
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
ResponseEntity<List<Map<String, Object>>> response =
editTableOfContentsController.extractBookmarks(mockFile);
// Then
assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode());
List<Map<String, Object>> result = response.getBody();
assertNotNull(result);
assertTrue(result.isEmpty());
verify(mockDocument).close();
@@ -142,9 +151,13 @@ class EditTableOfContentsControllerTest {
when(childItem.getNextSibling()).thenReturn(null);
// When
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
ResponseEntity<List<Map<String, Object>>> response =
editTableOfContentsController.extractBookmarks(mockFile);
// Then
assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode());
List<Map<String, Object>> result = response.getBody();
assertNotNull(result);
assertEquals(1, result.size());
@@ -178,9 +191,13 @@ class EditTableOfContentsControllerTest {
when(mockOutlineItem.getNextSibling()).thenReturn(null);
// When
List<Map<String, Object>> result = editTableOfContentsController.extractBookmarks(mockFile);
ResponseEntity<List<Map<String, Object>>> response =
editTableOfContentsController.extractBookmarks(mockFile);
// Then
assertNotNull(response);
assertEquals(HttpStatus.OK, response.getStatusCode());
List<Map<String, Object>> result = response.getBody();
assertNotNull(result);
assertEquals(1, result.size());