Skip to content

DAM: Allow replacing files with same category instead of same mimetype - #6079

Open
VPS-julia wants to merge 7 commits into
mainfrom
claude/com-3100-planning-f0ko2t
Open

DAM: Allow replacing files with same category instead of same mimetype#6079
VPS-julia wants to merge 7 commits into
mainfrom
claude/com-3100-planning-f0ko2t

Conversation

@VPS-julia

@VPS-julia VPS-julia commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Task: https://vivid-planet.atlassian.net/browse/COM-3100

Summary

This change allows DAM files to be replaced with files of the same category rather than requiring an exact mimetype match. For example, a JPEG image can now be replaced with a WebP, and an MP3 audio file can be replaced with an OGG file, as long as they belong to the same category.

Key Changes

  • New file categorization utility: Created getDamFileCategory() helper function in both API and admin packages that categorizes mimetypes into five categories:

    • pixelImage: JPEG, PNG, WebP, GIF, etc.
    • svgImage: SVG (kept separate from pixel images)
    • audio: MP3, OGG, WAV, etc.
    • video: MP4, WebM, QuickTime, etc.
    • document: PDF, DOCX, VTT, ZIP, and other files
  • Updated file replacement logic: Modified FilesService.replaceFile() to:

    • Validate that the replacement file belongs to the same category instead of checking for exact mimetype match
    • Throw CometValidationException instead of generic Error for better error handling
    • Automatically adjust the file's extension to match the new file (e.g., photo.jpgphoto.webp)
    • Create image metadata if the replacement file is an image but the original wasn't
    • Prevent replacement if a file with the new name already exists in the same folder
  • Updated admin UI:

    • Modified ReplaceFileButton to accept all mimetypes in the same category as the current file
    • Improved error messages to be more user-friendly and informative
    • Better error handling with proper cleanup in finally block
  • Refactored mime type filtering: Simplified useDamAcceptedMimeTypes() to use the new getDamFileCategory() helper instead of individual type-checking functions

  • Added comprehensive tests: Created test suites for the new getDamFileCategory() function in both packages

Notable Implementation Details

  • The getDamFileCategory() function is duplicated between API and admin packages with a prominent comment warning about keeping them in sync
  • File URLs and usages remain unchanged during replacement; only the file extension is adjusted
  • The implementation properly handles cases where the original file had no image metadata but the replacement does

Screenshots

Original Replaced
1beforejpeg 2afterwebp
Files with same name/different format Error message after replacing
3folderwithnameconflict 4errorfilenamealreadyexists

Replacing a file required the exact same mimetype, so a JPEG could not be
replaced by a WebP even though both are pixel images. Editors have to
delete and re-upload the file instead, which breaks its URL and usages.

Replacement is now allowed within a category (pixel image, SVG image,
audio, video, document), with SVG images kept separate from pixel images.
The extension of the file's name is adjusted to match the new file so that
name and mimetype stay consistent; the URL is built from the name without
its extension and therefore stays unchanged. A name collision in the same
folder is rejected with a validation error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
@VPS-julia VPS-julia self-assigned this Jul 29, 2026
claude added 4 commits July 29, 2026 12:21
Replacing a PNG with a JPG renames the file to match the new extension. If
that name is already taken, the editor only saw "An error occurred while
replacing the file. Please try again later.", which neither explains the
cause nor how to resolve it.

The API now throws a dedicated CometFileNameAlreadyExistsException, which
the Admin recognizes by its class name — the same mechanism already used
for CometImageResolutionException during upload — and translates into a
message naming both file extensions and the ways to resolve the conflict.

Errors from the API were previously swallowed by replaceById, which parsed
error responses as successful ones, so the dialog only ever appeared for
network failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
Suggesting the editor pick a file of the original type contradicts their
intent, since they deliberately chose a different format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
The dialog fell back to the "Unknown error" error-type label, which says
nothing about what failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
@VPS-julia
VPS-julia marked this pull request as ready for review July 29, 2026 15:26
@github-actions
github-actions Bot requested a review from VPS-Obi July 29, 2026 15:26
@VPS-Obi
VPS-Obi requested a review from VPS-thodax July 30, 2026 06:32
Comment thread packages/admin/cms-admin/src/dam/config/damFileCategory.ts Outdated
Comment thread packages/api/cms-api/src/dam/files/files.service.ts
Comment thread packages/api/cms-api/src/dam/files/files.service.ts Outdated
Comment on lines +348 to +354
if (fileWithSameName !== null && fileWithSameName.id !== fileToReplace.id) {
throw new CometFileNameAlreadyExistsException(
`File cannot be replaced because a file named '${name}' already exists in ${
fileToReplace.folder ? `folder '${fileToReplace.folder.name}'` : "the root folder"
}`,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could add a "-2" suffix like we do in the page tree. @thomasdax98 what do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be easier and more user-friendly. We could show a snackbar in the admin to inform the user of the name-change. We also have to update the form data.

At first I thought this was a bad idea because the file/image url changes but it changes anyway because of the content hash in the URL. Also we need it to change as a cache buster. So the URL is no argument.

claude added 2 commits August 5, 2026 13:37
The fallback category matches every mimetype that is not an image, audio,
or video file, not only documents. Name it "other" to reflect that a
separate "document" category can be introduced later if a use case arises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
The stored file name is already slugified, so re-slugifying its base name
when only the extension changes is unnecessary; swapping the extension on
the existing base name is enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YLrviYawvdDwZrXLGpA7c
| `svgImage` | SVG |
| `audio` | MP3, OGG, WAV |
| `video` | MP4, WebM, QuickTime |
| `other` | PDF, DOCX, VTT, ZIP |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should allow replacing within the "other" category as usages could vary. For example VTT files are subtitles for videos that can be attached to a video, so it shouldn't be possible to replace it with, e.g., a PDF.

pixelImage: filterByCategory(allAcceptedMimeTypes, "pixelImage"),
audio: filterByCategory(allAcceptedMimeTypes, "audio"),
video: filterByCategory(allAcceptedMimeTypes, "video"),
other: filterByCategory(allAcceptedMimeTypes, "other"),

@VPS-thodax VPS-thodax Aug 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's somewhat strange that "other" would include "pdf" and "captions", although they have their own fields within this object. But I'm not quite sure how to handle it. Maybe include "pdf" and "captions" as categories within getDamFileCategory?

Also the rename from document to other breaks the public API, so we need to keep document (at least as an alias)

Comment on lines +348 to +354
if (fileWithSameName !== null && fileWithSameName.id !== fileToReplace.id) {
throw new CometFileNameAlreadyExistsException(
`File cannot be replaced because a file named '${name}' already exists in ${
fileToReplace.folder ? `folder '${fileToReplace.folder.name}'` : "the root folder"
}`,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be easier and more user-friendly. We could show a snackbar in the admin to inform the user of the name-change. We also have to update the form data.

At first I thought this was a bad idea because the file/image url changes but it changes anyway because of the content hash in the URL. Also we need it to change as a cache buster. So the URL is no argument.

Comment thread packages/api/cms-api/src/dam/files/files.service.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants