[V2] feat(attachments): add PDF/A-3b conversion, attachment listing, renaming, and deletion (#5304)

# Description of Changes

This pull request introduces major improvements to the PDF attachment
API, adding new endpoints for listing, renaming, and deleting
attachments in PDFs, as well as improving error handling and content
negotiation. It also adds support for converting PDFs to PDF/A-3b format
when adding attachments and introduces stricter validation for
attachment uploads. The exception handling is improved to ensure
consistent JSON error responses, even when the client requests a PDF.

**API Feature Additions:**

* Added new endpoints in `AttachmentController` for listing
(`/list-attachments`), renaming (`/rename-attachment`), and deleting
(`/delete-attachment`) PDF attachments, with corresponding request and
response models: `ListAttachmentsRequest`, `RenameAttachmentRequest`,
`DeleteAttachmentRequest`, and `AttachmentInfo`.

* Enhanced the `/add-attachments` endpoint to optionally convert the
resulting PDF to PDF/A-3b format, controlled by a new `convertToPdfA3b`
flag in `AddAttachmentRequest`.

**Validation and Robustness:**

* Introduced strict validation for attachment uploads, enforcing
non-empty attachments, a maximum size per attachment (50 MB), and a
total size limit (200 MB).

**Content Negotiation:**

* Updated `WebMvcConfig` to configure content negotiation, allowing both
PDF and JSON responses, and preventing 406 errors when clients request
PDFs but errors must be returned as JSON.


<img width="370" height="997" alt="image"
src="https://github.com/user-attachments/assets/571504d4-e97e-4b30-ae97-3defba217b47"
/>
<img width="1415" height="649" alt="image"
src="https://github.com/user-attachments/assets/bb8863fc-0be8-4bf2-af7d-73a229010f9a"
/>
<img width="1415" height="649" alt="image"
src="https://github.com/user-attachments/assets/68092672-5be5-4ef7-9cbc-1fb008b728e1"
/>
<img width="1415" height="649" alt="image"
src="https://github.com/user-attachments/assets/c4b0eda5-2573-4e38-8284-c077acb83f7f"
/>



<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.

---------

Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
Balázs Szücs
2025-12-24 22:35:36 +01:00
committed by GitHub
parent 1b0a1e938e
commit 54cd804319
15 changed files with 1376 additions and 374 deletions

View File

@@ -1394,6 +1394,11 @@ header = "Add Attachments"
add = "Add Attachment"
remove = "Remove Attachment"
embed = "Embed Attachment"
convertToPdfA3b = "Convert to PDF/A-3b"
convertToPdfA3bDescription = "Creates an archival PDF with embedded attachments"
convertToPdfA3bTooltip = "PDF/A-3b is an archival format ensuring long-term preservation. It allows embedding arbitrary file formats as attachments. Conversion requires Ghostscript and may take longer for large files."
convertToPdfA3bTooltipHeader = "About PDF/A-3b Conversion"
convertToPdfA3bTooltipTitle = "What it does"
submit = "Add Attachments"
[watermark]

View File

@@ -1,13 +1,14 @@
/**
* AddAttachmentsSettings - Shared settings component for both tool UI and automation
*
* Allows selecting files to attach to PDFs.
* Allows selecting files to attach to PDFs with optional PDF/A-3b conversion support.
*/
import { Stack, Text, Group, ActionIcon, ScrollArea, Button } from "@mantine/core";
import { Stack, Text, Group, ActionIcon, ScrollArea, Button, Checkbox } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { AddAttachmentsParameters } from "@app/hooks/tools/addAttachments/useAddAttachmentsParameters";
import LocalIcon from "@app/components/shared/LocalIcon";
import { Tooltip } from "@app/components/shared/Tooltip";
interface AddAttachmentsSettingsProps {
parameters: AddAttachmentsParameters;
@@ -103,6 +104,40 @@ const AddAttachmentsSettings = ({ parameters, onParameterChange, disabled = fals
</ScrollArea.Autosize>
</Stack>
)}
{/* PDF/A-3b conversion option with informative tooltip */}
<Group gap="xs" align="flex-start">
<Checkbox
label={
<Group gap={4}>
<Text size="sm">{t("attachments.convertToPdfA3b", "Convert to PDF/A-3b")}</Text>
<Tooltip
header={{
title: t("attachments.convertToPdfA3bTooltipHeader", "About PDF/A-3b Conversion")
}}
tips={[
{
title: t("attachments.convertToPdfA3bTooltipTitle", "What it does"),
description: t(
"attachments.convertToPdfA3bTooltip",
"PDF/A-3b is an archival format ensuring long-term preservation. It allows embedding arbitrary file formats as attachments. Conversion requires Ghostscript and may take longer for large files."
)
}
]}
sidebarTooltip={true}
pinOnClick={true}
>
<LocalIcon icon="info-outline-rounded" width="1.25rem" height="1.25rem" style={{ color: 'var(--icon-files-color)', cursor: 'help' }} />
</Tooltip>
</Group>
}
description={t("attachments.convertToPdfA3bDescription", "Creates an archival PDF with embedded attachments")}
checked={parameters.convertToPdfA3b}
onChange={(event) => onParameterChange('convertToPdfA3b', event.currentTarget.checked)}
disabled={disabled}
styles={{ root: { flex: 1 } }}
/>
</Group>
</Stack>
);
};

View File

@@ -16,6 +16,8 @@ const buildFormData = (parameters: AddAttachmentsParameters, file: File): FormDa
if (attachment) formData.append("attachments", attachment);
});
formData.append("convertToPdfA3b", String(parameters.convertToPdfA3b));
return formData;
};

View File

@@ -2,10 +2,12 @@ import { useState } from 'react';
export interface AddAttachmentsParameters {
attachments: File[];
convertToPdfA3b: boolean;
}
const defaultParameters: AddAttachmentsParameters = {
attachments: []
attachments: [],
convertToPdfA3b: false
};
export const useAddAttachmentsParameters = () => {
@@ -33,3 +35,5 @@ export const useAddAttachmentsParameters = () => {
validateParameters
};
};
export const DEFAULT_ADD_ATTACHMENTS_PARAMETERS: AddAttachmentsParameters = defaultParameters;