Describe the bug
Removing an attachment from a card does nothing. The action menu stays open, the attachment stays in the list, and no error is shown to the user.
The reason is a mismatch between the OCS route the frontend calls and the route the backend registers.
What the frontend sends (Chrome DevTools, Network tab):
DELETE /ocs/v2.php/apps/deck/api/v1.0/cards/138/attachment/file:1117?boardId=15
Result: 404 Not Found
What the backend registers (appinfo/routes.php, v1.18.3, line 165):
['name' => 'attachment_ocs#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE'],
Two separate problems:
1. Path: the frontend uses /attachment/ (singular), the route is /attachments/ (plural).
2. Parameters: AttachmentOcsController::delete() expects a numeric attachmentId and a separate type query parameter, but the frontend passes the composite value file:1117 in the path segment.
// lib/Controller/AttachmentOcsController.php, v1.18.3
public function delete(int $cardId, int $attachmentId, string $type = 'file', ?int $boardId = null): DataResponse
The offending frontend code (src/services/AttachmentApi.js, v1.18.3):
async deleteAttachment(attachment, boardId) {
await axios({
method: 'DELETE',
url: this.ocsUrl(`/cards/${attachment.cardId}/attachment/${attachment.type}:${attachment.id}`),
params: {
boardId: boardId ?? null,
},
})
}
I verified both problems on a live instance by patching the shipped bundle:
• DELETE .../cards/165/attachment/file:1126?boardId=17 -- 404, as shipped.
• DELETE .../cards/165/attachments/file:1126?boardId=17 -- 403. The route matches, but (int) "file:1126" becomes 0, so the permission check runs against a non-existent attachment.
• DELETE .../cards/165/attachments/1126?type=file&boardId=17 -- 200, works.
Steps to reproduce
1. Open any card and switch to the Attachments tab.
2. Upload a file, or attach an existing one from Files.
3. Open the ... menu next to the attachment and click "Remove attachment".
Expected behaviour
The attachment is removed from the card. If the request fails, the user sees an error message.
Actual behaviour
Nothing happens. The menu stays open, the attachment remains, and no error is shown. The failure is only visible in the browser's network tab as a 404.
Additional notes
• restoreAttachment() looks affected as well. It sends GET .../cards/{cardId}/attachment/{type}:{id}/restore, while the route is PUT .../cards/{cardId}/attachments/{attachmentId}/restore -- wrong path and wrong verb. I did not test this.
• updateAttachment() builds the same singular path and may have the same problem.
• main looks only partly fixed. AttachmentApi.js on main now uses /attachments/ (plural), but still passes the composite ${type}:${id} in the path segment rather than a numeric id plus a type parameter. Worth checking whether that path actually resolves.
• No error handling. Even once the route is fixed, a failing request should surface a message instead of failing silently. That is what made this hard to diagnose.
• There is also a console error when opening a card with an attachment, which may or may not be connected:
TypeError: Cannot read properties of undefined (reading 'id')
at o.currentCard (deck-main.js)
Suggested fix
async deleteAttachment(attachment, boardId) {
await axios({
method: 'DELETE',
url: this.ocsUrl(`/cards/${attachment.cardId}/attachments/${attachment.id}`),
params: {
type: attachment.type,
boardId: boardId ?? null,
},
})
}
Server configuration
• Nextcloud version: 34.0.3
• Deck version: 1.18.3
• Operating system: Ubuntu 24.04.4 LTS
• Web server: Apache behind Plesk, PHP-FPM
• PHP version: 8.3
• Database: MariaDB 10.11.14
Client configuration
• Browser: Google Chrome
• Operating system: Windows
Reproduced on three independent instances, all running Nextcloud 34 with Deck 1.18.3.
Describe the bug
Removing an attachment from a card does nothing. The action menu stays open, the attachment stays in the list, and no error is shown to the user.
The reason is a mismatch between the OCS route the frontend calls and the route the backend registers.
What the frontend sends (Chrome DevTools, Network tab):
What the backend registers (appinfo/routes.php, v1.18.3, line 165):
['name' => 'attachment_ocs#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE'],Two separate problems:
1. Path: the frontend uses /attachment/ (singular), the route is /attachments/ (plural).
2. Parameters: AttachmentOcsController::delete() expects a numeric attachmentId and a separate type query parameter, but the frontend passes the composite value file:1117 in the path segment.
The offending frontend code (src/services/AttachmentApi.js, v1.18.3):
I verified both problems on a live instance by patching the shipped bundle:
• DELETE .../cards/165/attachment/file:1126?boardId=17 -- 404, as shipped.
• DELETE .../cards/165/attachments/file:1126?boardId=17 -- 403. The route matches, but (int) "file:1126" becomes 0, so the permission check runs against a non-existent attachment.
• DELETE .../cards/165/attachments/1126?type=file&boardId=17 -- 200, works.
Steps to reproduce
1. Open any card and switch to the Attachments tab.
2. Upload a file, or attach an existing one from Files.
3. Open the ... menu next to the attachment and click "Remove attachment".
Expected behaviour
The attachment is removed from the card. If the request fails, the user sees an error message.
Actual behaviour
Nothing happens. The menu stays open, the attachment remains, and no error is shown. The failure is only visible in the browser's network tab as a 404.
Additional notes
• restoreAttachment() looks affected as well. It sends GET .../cards/{cardId}/attachment/{type}:{id}/restore, while the route is PUT .../cards/{cardId}/attachments/{attachmentId}/restore -- wrong path and wrong verb. I did not test this.
• updateAttachment() builds the same singular path and may have the same problem.
• main looks only partly fixed. AttachmentApi.js on main now uses /attachments/ (plural), but still passes the composite ${type}:${id} in the path segment rather than a numeric id plus a type parameter. Worth checking whether that path actually resolves.
• No error handling. Even once the route is fixed, a failing request should surface a message instead of failing silently. That is what made this hard to diagnose.
• There is also a console error when opening a card with an attachment, which may or may not be connected:
Suggested fix
Server configuration
• Nextcloud version: 34.0.3
• Deck version: 1.18.3
• Operating system: Ubuntu 24.04.4 LTS
• Web server: Apache behind Plesk, PHP-FPM
• PHP version: 8.3
• Database: MariaDB 10.11.14
Client configuration
• Browser: Google Chrome
• Operating system: Windows
Reproduced on three independent instances, all running Nextcloud 34 with Deck 1.18.3.