Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Jan 27, 2026

Description of change

  • file-manager-api: FileService.deleteFile checks signature_containers and throws if the file is used; controller returns 409 with code: "FILE_HAS_SIGNATURES".
  • file-manager: Delete handler shows: "This file cannot be deleted because it has been used in a signing container." when the API returns 409 / FILE_HAS_SIGNATURES.
  • esigner-api: Webhook file update uses fileRepository.findOne, and only applies name, displayName, etc. when present in the payload so renames propagate.
  • file-manager: updateUrlFromState() updates the URL with view and folderId via goto(..., { replaceState: true }) from navigateToFolder and switchView; on load, existing logic still reads params from the URL.
  • file-manager-api: In the webhook, file.folderId is only set when local.data.folderId !== undefined, so eSigner payloads without folder don’t overwrite the folder.
  • esigner-api: getDocumentsWithStatus(userId, listMode) with listMode === 'containers' (default) returns only files that have at least one signee; GET /api/files?list=all returns all files.
  • esigner: New container page loads selectable files with GET /api/files?list=all into local state for the “Select Existing File” picker; main list still uses the default (containers only).
  • file-manager and esigner: Added getMimeTypeDisplayLabel() (XLSX, DOCX, PPTX, PDF + fallback) and use it for the “Type” field on file detail pages.

Issue Number

closes #633
closes #718
closes #719
closes #716
closes #714
closes #701
closes #700

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • User-friendly file type labels (e.g., DOCX, PDF) shown across previews and details.
    • Option to list/select existing files when creating new documents (shows all files).
    • URL state sync for bookmarkable views and improved back/forward behavior.
  • Bug Fixes

    • Returns conflict error when deleting files with signatures; clearer delete messages.
    • Prevents accidental overwrites of file metadata and accidental moves of nested files to root.
    • Soft-deleted files are hidden and treated as not found.
  • Chores

    • Faster file-update propagation for more responsive UI.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

Parses list query for document listing; adds soft-delete marker and prevents deletion if signing containers exist; webhook updates now guard undefined fields; adds mime-type display helper and integrates it into UIs; File Manager syncs URL state and improves delete error messaging; DB watcher debounces reloads per table.

Changes

Cohort / File(s) Summary
esigner — Listing & controller
platforms/esigner-api/src/controllers/FileController.ts, platforms/esigner-api/src/services/FileService.ts
FileController.getFiles reads optional list query and passes listMode ('all'
file-manager-api — Soft-delete & delete guard
platforms/file-manager-api/src/services/FileService.ts, platforms/file-manager-api/src/controllers/FileController.ts
Introduces SOFT_DELETED_FILE_NAME marker; retrieval/listing functions exclude soft-deleted entries. deleteFile checks signature count and either throws when signatures exist or performs a soft-delete (rename + remove accesses). Controller maps signing-related errors to HTTP 409 (FILE_HAS_SIGNATURES).
Webhook guarded updates
platforms/esigner-api/src/controllers/WebhookController.ts, platforms/file-manager-api/src/controllers/WebhookController.ts
Webhook handlers now only assign file fields (e.g., name, displayName, description, mimeType, size, md5Hash, folderId) when the incoming payload explicitly defines them, preventing unintended overwrites with undefined.
MIME display util + UI integration
platforms/esigner/src/lib/utils/mime-type.ts, platforms/file-manager/src/lib/utils/mime-type.ts, platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte, platforms/file-manager/src/routes/(protected)/files/[id]/+page.svelte
Adds getMimeTypeDisplayLabel(mimeType: string) and replaces raw MIME rendering with human-friendly labels in file detail views.
esigner — New container file selection UI
platforms/esigner/src/routes/(protected)/files/new/+page.svelte
Loads all files (list=all) into a local selectableFiles state so users can select existing files when creating a new signing container.
file-manager — Navigation & delete UX
platforms/file-manager/src/routes/(protected)/files/+page.svelte
Adds URL-sync via updateUrlFromState() for currentView/currentFolderId; calls it on folder navigation and view switches. Enhances delete error handling to show specialized messaging when API returns 409/FILE_HAS_SIGNATURES.
file-manager-api — DB watcher debounce
platforms/file-manager-api/src/web3adapter/watchers/subscriber.ts
Reworks post-update handling: queues/debounces entity reloads, uses per-table delays (files: 0ms, others: 3000ms), reloads/enriches entities after transaction commit, and dispatches adapter changes.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FileController
  participant FileService
  participant SignatureRepo
  participant FileRepo

  User->>FileController: DELETE /files/:id
  FileController->>FileService: deleteFile(fileId, userId)
  FileService->>SignatureRepo: countSignatures(fileId)
  alt signatures > 0
    SignatureRepo-->>FileService: count > 0
    FileService-->>FileController: throw Error("signing containers ...")
    FileController-->>User: HTTP 409 FILE_HAS_SIGNATURES
  else no signatures
    SignatureRepo-->>FileService: count = 0
    FileService->>FileRepo: removeFileAccess(fileId)
    FileRepo-->>FileService: access removed
    FileService->>FileRepo: soft-delete file (set name/displayName = SOFT_DELETED_FILE_NAME)
    FileRepo-->>FileService: file saved
    FileService-->>FileController: success
    FileController-->>User: HTTP 200 OK
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Feat/esigner #650: Overlaps changes to esigner-api file listing and the new getDocumentsWithStatus(userId, listMode) signature.

Suggested reviewers

  • sosweetham
  • ananyayaya129
  • xPathin

Poem

🐰 I nibble bytes and guard their names,
I hide the deleted from curious frames.
I label types so humans know,
I stop deletes where signatures grow.
🥕 A tiny hop — files tidy, steady, and tame.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Chore/file manager and esigner bug fixes' is vague and generic, using the term 'bug fixes' without specifying which bugs or what changes address them. Make the title more specific by highlighting the primary change, e.g., 'Fix file deletion blocking, soft-delete, and rename sync between services' or 'Add soft-delete and improve file management across services'.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All coding-related objectives from linked issues are met: file deletion prevention with proper error codes [#718], soft-delete to preserve access [#719], file rename sync via guarded updates [#716], user-friendly MIME type labels [#714], URL state persistence [#701], and filtering containers-only by default [#700].
Out of Scope Changes check ✅ Passed Changes in subscriber.ts refactor afterUpdate to defer entity reload post-transaction, preventing stale webhook data—directly enabling the linked issues' fixes by ensuring propagated changes use fresh entity state.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description covers all required template sections with comprehensive details about changes across multiple platforms and how they address specific issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…containers

- FileService.deleteFile: check signature_containers count before delete; throw
  if file is part of any signing container
- FileController.deleteFile: return 409 with code FILE_HAS_SIGNATURES when
  deletion is blocked so clients can show a clear message
…ed in signing)

- On delete error, check for 409 and FILE_HAS_SIGNATURES or 'signing container'
- Show specific toast: file cannot be deleted because it has been used in a
  signing container
…mes in File Manager appear in eSigner

- Update existing file from repo so entity is mutable
- Apply name, displayName, description, mimeType, size, md5Hash only when
  present in payload so renames and partial syncs propagate correctly
…rrent location

- Add updateUrlFromState() and call it from navigateToFolder and switchView
- Use goto(..., { replaceState: true }) with view and folderId query params
- Existing onMount already reads view and folderId from URL so refresh restores state
…no folderId

- When updating existing file, set file.folderId only if local.data.folderId
  is present in the payload
- Prevents eSigner webhooks (no folder concept) from overwriting folderId
  and moving deeply nested files to root when signed
…igner list, not all files from File Manager

- esigner-api: getDocumentsWithStatus(userId, listMode). list=containers (default):
  only files with at least one signee; list=all: all owner/invited files
- FileController.getFiles: read query param list=all and pass listMode
- esigner new container page: load selectable files via GET /api/files?list=all
  into local state so picker shows any file; main list unchanged (containers only)
…CX, XLSX, PDF) instead of raw MIME

- Add getMimeTypeDisplayLabel() in both apps: map long MIME types to short
  labels (XLSX, DOCX, PPTX, PDF) with fallback for unknown types
- File Manager files/[id]: use for Type in subtitle and Details
- eSigner files/[id]: use for Type in preview area and File Information
@coodos coodos force-pushed the chore/file-manager-and-esigner-bug-fixes branch from d982a49 to 39a0b13 Compare January 27, 2026 19:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platforms/esigner-api/src/services/FileService.ts`:
- Around line 55-56: The code currently treats SOFT_DELETED_FILE_NAME
("[[deleted]]") as a sentinel and silently hides files named that, but there is
no validation preventing uploads or renames to that name; update FileService to
explicitly reject any incoming filename equal to SOFT_DELETED_FILE_NAME during
create/upload and during rename operations by validating the provided name early
(e.g., in the upload/create and renameFile methods) and returning or throwing a
validation error with a clear message; reference SOFT_DELETED_FILE_NAME and the
FileService methods (upload/create and renameFile) so the checks are added in
the entry points that persist or change file.name.
🧹 Nitpick comments (1)
platforms/esigner-api/src/services/FileService.ts (1)

7-9: Consider centralizing the soft-delete marker.

This constant now exists in both file-manager-api and esigner-api. A shared constant (or config) would avoid drift if the marker changes later.

The afterUpdate subscriber was experiencing stale reads because findOne
ran inside the transaction before commit. This caused webhooks to send
the previous state instead of the current state, resulting in eSigner
always being one update behind File Manager.

Changes:
- Refactored afterUpdate to pass metadata (entityId, relations) instead
  of immediately loading the entity
- Created handleChangeWithReload that does the findOne INSIDE setTimeout
  after the transaction has committed
- Set 50ms delay for files (ensures commit) vs 3s for other tables
- This ensures the DB read happens post-commit with fresh data

Root cause: TypeORM afterUpdate fires before transaction commit. When
findOne uses a different connection from the pool, it sees the last
committed state, not the pending uncommitted changes.
Same root cause as file-manager-api: afterUpdate called findOne inside
the transaction before commit, causing stale reads. eReputation received
the previous wishlist state instead of the current one.

Changes:
- Refactored afterUpdate to pass metadata (entityId, relations) instead
  of immediately loading the entity
- Created handleChangeWithReload and executeReloadAndSend methods that
  do the findOne INSIDE setTimeout after transaction commit
- Added Wishlist to getRelationsForEntity with ["user"] relation
- Wishlists sync with 50ms delay (ensures commit), groups keep 3s debounce

This ensures wishlist updates sync to eReputation immediately with
fresh, post-commit data.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@platforms/file-manager-api/src/web3adapter/watchers/subscriber.ts`:
- Around line 186-201: afterUpdate (the UpdateEvent handler) can't get the
entity id for repository.update() calls because UpdateEvent.entity and
UpdateEvent.databaseEntity are not populated; update flows that call
repository.update(id, data) will skip syncing. Fix by changing update use-sites
or payloads: either modify callers (e.g., GroupService::the update call at
GroupService.ts line ~218) to load the entity and use repository.save(entity) so
afterUpdate receives an entity, or ensure callers include the primary key in the
partial passed to repository.update(...) (so the subscriber can read the id from
event.databaseEntity or the update payload); update afterUpdate only if you add
explicit id extraction from event.queryRunner or event.payload, but preferred
approach is to change repository.update usages to save() or include the PK in
update data so afterUpdate can determine the id.
🧹 Nitpick comments (1)
platforms/file-manager-api/src/web3adapter/watchers/subscriber.ts (1)

248-336: Fixed delays (50ms/3s) create race condition risk under slow transactions.

The current setTimeout-based reload may still encounter stale data if a transaction commits after the timeout. If entity modifications in this codebase use explicit transactions (via DataSource.transaction()), consider using TypeORM's afterTransactionCommit hook instead—it guarantees execution only after commit. However, this requires wrapping all relevant entity saves in explicit transactions; if most saves use auto-commit, this approach won't help. Verify transaction coverage in the codebase before refactoring.

Copy link
Collaborator

@Bekiboo Bekiboo left a comment

Choose a reason for hiding this comment

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

lgtm

@coodos coodos merged commit bd9c36d into main Jan 27, 2026
4 checks passed
@coodos coodos deleted the chore/file-manager-and-esigner-bug-fixes branch January 27, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment