51 Commits

Author SHA1 Message Date
Andrey Antukh
0e388442a1
Add storage object status lifecycle and verified dedup (#11345)
* ♻️ Simplify storage GC delays and add skip-delay task params

The touched GC no longer applies an extra deletion-delay when marking
storage objects as deleted. By the time a storage object is touched, its
referencing domain row has already passed its own deletion delay, and the
reference scan is the only safety check needed. Touched objects are now
marked with deleted_at = now, so the deleted GC removes them on the next
run.

For the tempfile bucket, upload chunks now set touched-at in the future
(1h, aligned with the upload-session-gc TTL) instead of relying on a
special-case deletion delay.

Task handlers now read their task props:
- storage-gc-touched accepts :skip-delay to process all touched objects
  immediately, bypassing the min-age threshold.
- objects-gc accepts :chunk-size and :skip-delay to process recently
  deleted rows without waiting for the deletion delay.

This allows running the deletion cascade immediately from the REPL via
run-task! with the skip-delay option.

AI-assisted-by: deepseek-v4-flash

*  Add storage object status lifecycle, verified dedup, and deletion retry tracking

Storage object lifecycle hardening:

- Add status column ('valid' | 'pending') as write-ahead marker for
  object creation. put-object! inserts in 'pending' state, writes blob,
  then promotes to 'valid'. Failed writes remove the pending row.
- Add :storage-pending-gc task to reclaim orphaned pending rows (e.g.
  after crash between blob write and promotion).
- Verify blob existence on every dedup hit via exists-object? (fs stat /
  s3 headObject). Missing blobs mark the row as deleted and create fresh
  object.
- Add deletion_attempts column (migration 0154) to track physical blob
  deletion attempts. Restructure gc_deleted to use chunked processing
  with per-chunk transactions (short lock duration). Failed deletions
  are deferred to tomorrow (deleted_at = NOW() + 1 day) to prevent
  infinite loops. After 7 attempts, give up and accept orphan.
- Change del-objects-in-bulk contract to return #{fail-ids} for precise
  per-id tracking (fs and s3 backends updated).
- Use tmp/tempfile for fs atomic writes with cleanup queue registration
  (crashed-JVM temp files swept ~60min later). Document ATOMIC_MOVE
  POSIX-only assumption.
- Add linear backoff to s3 exists-object? retries (100ms/200ms/300ms).
- Wrap compensating delete in put-object! catch block to prevent
  masking original error when connection is aborted.
- Fix assert messages in pending_gc.clj and gc_deleted.clj (pool
  assertion said 'expected valid storage' instead of 'db pool').
- Add pending-objects-excluded-from-gc-deleted test. Use unique path in
  put-object-write-failure-leaves-no-row test to avoid collisions.

AI-assisted-by: qwen3.7-plus

* 🐛 Fix review comments on gc-deleted and storage

- Fix process-chunk! returning nil causing (+ acc nil) crash
- Add FOR UPDATE SKIP LOCKED to sql:get-deleted-chunk to prevent
  infinite loop when another worker holds locks
- Pass :cause to log messages in gc_deleted.clj and s3.clj
- Fix extra space in log hint string
- Remove unused ::blob-missing? reference from storage memory
- Rename test to match actual behavior (leaves pending row)
- Add test for gc-deleted giving up after max attempts

AI-assisted-by: qwen3.7-plus
2026-08-27 12:37:05 +02:00
Andrey Antukh
03cd3fa70f
♻️ Consolidate auto-link libraries with unified export-type and fix ref integrity (#9958)
*  Auto-link libraries during import based on slugified name

When a Penpot file is exported without bundled libraries and then
imported into a different environment, external library links are
broken because library UUIDs differ across environments.

This feature adds a heuristic to auto-relink libraries by matching
slugified library names against shared files in the target team:

- Export: embed external library metadata (id, name, slug, used-by)
  in the manifest when libraries are not included in the export.
- Import: resolve external libraries by slugifying shared file names
  in the destination team and matching against manifest slugs.
- Single match: auto-link silently (creates file-library-rel row).
- Multiple matches: emit SSE event so the frontend shows a selection
  dialog for the user to pick the correct library.
- No match: import continues without linking (current behavior).

Backend changes:
- Extended manifest schema with optional :external-libraries field
- Added slugify-name, get-files-names, get-shared-files-for-team,
  find-shared-files-by-slug helpers in app.binfile.common
- Threaded team-id into import cfg from RPC layer
- Added resolve-external-libraries and auto-link-libraries in v3
- Emit :library-candidates SSE event for multi-match cases

Frontend changes:
- Worker captures library-candidates SSE events and forwards them
- Import dialog shows auto-link notification and multi-match
  selection UI with select dropdowns
- Added link-files-to-library! RPC helper for user selections
- Added en/es translations for new UI strings

Closes #9263

Signed-off-by: Andrey Antukh <niwi@niwi.nz>

*  Add UI for the auto-link plumbing

* ♻️ Consolidate auto-link libraries with unified export-type and fix ref integrity

Consolidates the auto-link libraries feature into a single coherent implementation:

- Unify boolean flags (embed-assets, include-libraries, link-later)
  into single ::bfc/export-type parameter
- Fix critical reference-integrity bug: pre-resolution no longer remaps
  :component-file refs when no link is created (multi-match / no-permission)
- compute-link-decisions as single source of truth for auto-link logic
- 80+ backend tests covering round-trip, cross-team, permissions,
  edge cases, and reference integrity

AI-assisted-by: longcat-2.0

---------

Signed-off-by: Andrey Antukh <niwi@niwi.nz>
Co-authored-by: Eva Marco <evamarcod@gmail.com>
2026-08-27 10:09:52 +02:00
Andrey Antukh
81c3b3cd56
📎 Update copyright name on file header (#11346) 2026-08-25 11:55:10 +02:00
Andrey Antukh
47d599fe34
Persist binfile manifest and emit workspace audit events (#11106) (#11138)
Persist binfile manifest metadata in file_data on import so file
statistics are available at open-workspace time. Emit a new
open-workspace-file audit event enriched with file statistics:
page count, shape count, component count, linked libraries,
design tokens, and whether the file is a shared library.

Closes #11106

AI-assisted-by: mimo-v2.5-pro
2026-08-21 14:37:21 +02:00
Andrey Antukh
5906312dff
🐛 Normalize error response on duplicate file ID (#11050)
Capture unique constraint violation in insert-file! and return
generic :not-found error instead of propagating raw PostgreSQL
exception, preventing file existence oracle.

AI-assisted-by: mimo-v2.5-pro
2026-08-05 17:52:14 +02:00
Andrey Antukh
4a8fb5af53 Merge remote-tracking branch 'origin/staging' into develop 2026-06-01 13:15:57 +02:00
Andrey Antukh
c5de4c27b0 Merge remote-tracking branch 'origin/main' into staging 2026-06-01 12:57:39 +02:00
Yamila Moreno
ddba2ffa75
📎 Update Kaleidos Copyright (#9929) 2026-05-29 11:24:58 +02:00
Dexterity
56d8dc678c
🐛 Populate is-indirect flag on file libraries from relation graph (#9289)
Co-authored-by: Andrey Antukh <niwi@niwi.nz>
2026-05-27 09:23:48 +02:00
Andrey Antukh
52588412c7 Merge remote-tracking branch 'origin/staging' into develop 2026-05-14 11:12:01 +02:00
Pablo Alba
fffafdab93
🐛 Fix library updates reappear after file is reloaded (#9563)
* 🐛 Fix library updates reappear after file is reloaded

Summary
Migrate synced_at timestamps to a standalone file_library_sync table to ensure sync state is tracked for both direct and transitive libraries.

Problem
Transitive libraries (libraries imported by other libraries) are not stored as direct rows in file_library_rel. Because the system previously coupled synced_at directly to the file_library_rel schema, transitive libraries lacked a persistent location for their sync timestamps. This caused sync states to be lost or incorrectly reported for nested dependencies.

Changes
Schema Migration: Created file_library_sync and migrated existing synced_at values from file_library_rel.

Decoupling: Removed tight Foreign Key coupling to allow sync rows to exist independently of specific relationship records.

Persistent Writes: Added upsert-file-library-sync! helper. Updated all import, duplication, and RPC write paths (v1/v2/v3 importers, link-file-library) to ensure every write persists a sync row.

Unified Reads: Updated both direct and recursive/transitive library queries to fetch synced_at from the new table.

Testing: Added regression tests to verify that sync rows are correctly created/updated even when a transitive relation is absent in file_library_rel.

Impact
This fix ensures that the system accurately records and retrieves sync states for the entire library dependency tree, resolving the bug where nested libraries appeared out of sync.

*  MR review
2026-05-13 11:29:05 +02:00
Dexterity
3226660812
💄 Fix typos in comments and docstrings (#9362) 2026-05-06 17:43:09 +02:00
Andrey Antukh
8cb2f27de8 ♻️ Move file permissions to binfile common ns 2025-12-22 20:16:41 +01:00
Andrey Antukh
8307b699bf 🐛 Remove a race condition on file export
Caused when file is deleted in the middle of an exportation. The
current export process is not transactional, and on file deletion
several queries can start return not-found exception because of
concurrent file deletion.

With the changes on this PR we allow query deleted files internally
on the exportation process and make it resilent to possible
concurrent deletion.
2025-11-04 10:56:05 +01:00
Alejandro Alonso
36c986d8e8 🐛 Fix file doesn’t open after deleting the library used in it 2025-10-23 09:51:10 +02:00
Andrey Antukh
a4192ce835 🐛 Fix incorrect file data migration from db to legacy-db 2025-10-13 18:41:02 +02:00
Andrey Antukh
1eb6f33bdd Enable optional caching of results for file summary RPC methods 2025-10-13 12:32:29 +02:00
Andrey Antukh
12a4934c41 Allow pass :load-data? false to get-file 2025-10-13 12:32:29 +02:00
Andrey Antukh
5717708b56 ♻️ Refactor file storage
Make it more scallable and make it easily extensible
2025-10-13 12:24:05 +02:00
Andrey Antukh
c1058c7fdb ♻️ Add minor refactor for internal concurrency model
Replace general usage of virtual threads with platform threads
and use virtual threads for lightweight procs such that websocket
connections. This decision is made mainly because virtual threads
does not appear on thread dumps in an easy way so debugging issues
becomes very difficult.

The threads requirement of penpot for serving http requests
is not very big so having so this decision does not really affects
the resource usage.
2025-09-26 14:35:06 +02:00
Andrey Antukh
0b346e02ff 🐛 Fix incorrect options pass on decode-file 2025-09-22 11:30:42 +02:00
Andrey Antukh
c35bb6e09a 🎉 Add loadable weak map impl for libraries loading on validation and migration 2025-08-26 09:03:25 +02:00
Andrey Antukh
fa2b0bd67c Don't migrate libraries on accessing them on file data migrations
We don't migrate the libraries for avoid cascade migration; it is not ideal
but it reduces the total of the required memory needed for process a single
file migration that requires libraries to be loaded.
2025-08-26 08:15:45 +02:00
Andrey Antukh
283eb0419c ♻️ Refactor time related namespaces
Mainly removes the custom app.util.time namespace
from frontend and backend and normalize all to use
the app.common.time namespace
2025-08-01 11:20:01 +02:00
Andrey Antukh
37cec8891f 🎉 Add inplace binfile import support 2025-07-30 12:23:40 +02:00
Andrey Antukh
fa72bb4adf Add several improvements to admin pannel 2025-07-22 10:06:29 +02:00
Pablo Alba
be13704934
🐛 Fix access to libs on migration during an import (#6572) 2025-05-27 14:54:17 +02:00
Pablo Alba
443cabe94e
Add the ability to access libraries from file migrations 2025-05-27 13:12:34 +02:00
Andrey Antukh
b349d08155 🐛 Apply migrations in correct order for binfile-v1
The patch was already existed but only applied to binfile-v3,
with this commit, the fix is properly applied to all binfile
formats and for duplicate file operation.
2025-05-09 13:38:13 +02:00
Andrey Antukh
1fc0203c38 🎉 Add full integration with path data type feature 2025-05-06 13:39:10 +02:00
Andrey Antukh
15d09eb0d4
🐛 Fix incorrect data id assignation on creating a snapshot (#5934)
* 📎 Set proper name to relink-refs mechanism function

* 🐛 Fix incorrect id assignation on snapshot file resolution

* ♻️ Use uniform api for file retrieval on file snapshot code
2025-02-24 11:05:16 +01:00
Andrey Antukh
ca9b5b1b8a 📎 Use standard asserts on binfile common ns 2025-02-19 22:47:54 +01:00
Andrey Antukh
a391d71b60 🐛 Add correct feature handling on binfile import 2025-02-19 22:47:40 +01:00
Andrey Antukh
7d0c19fcc7 🐛 Add correct feature check on manifest reading
Instead on the file save operation so we can raise
exception if something does not match without processing
the whole file
2025-02-19 22:45:08 +01:00
Andrey Antukh
df7dd15705 🐛 Make the absorb library aware that file can be nil 2025-02-11 15:42:55 +01:00
Andrey Antukh
f871f88f30
♻️ Refactor file data migrations subsystem (#5692)
* ♻️ Refactor file data migrations subsystem

* 📎 Add backend scripts/run helper script
2025-01-31 13:37:41 +01:00
Andrey Antukh
541ed6282f Normalize file retrieving and persistence on binfile, file-gc and helpers 2025-01-27 12:38:10 +01:00
Andrey Antukh
74bdd72d2f Merge remote-tracking branch 'origin/staging' into develop 2025-01-27 12:12:46 +01:00
Andrey Antukh
471699960f
🐛 Update media references after instantiation of a component (#5652)
🐛 Update media references after instantiation of a component
2025-01-27 11:58:13 +01:00
Andrey Antukh
dae7b7cd74 Merge remote-tracking branch 'origin/staging' into develop 2025-01-23 12:53:03 +01:00
Andrey Antukh
efd4a11ae2 🐛 Add support for multiple formats on clone-template 2025-01-23 08:09:23 +01:00
Andrey Antukh
f62aecb383 🐛 Fix incompatibility of binfile exportation with offload feature 2025-01-16 14:37:55 +01:00
Andrey Antukh
47d28758d7 Clean frontend and backend features on exportation 2024-11-15 15:57:25 +01:00
Andrey Antukh
b7573c0b72 Change frontend-only features automatic team assignation rules
The frontend-only features are now ignored from files and from teams
and they do not autoassigns automatically to team and file on file
creation or update operations.
2024-11-15 15:57:25 +01:00
Andrey Antukh
8618cb950f 🎉 Add binfile-v3 export/import file format 2024-10-18 17:19:29 +02:00
Andrés Moya
29d0499725 🐛 Fix embed assets in unpublish and export 2024-07-09 11:51:52 +02:00
Andrey Antukh
7ebf8dd702 Skip invalid graphics on migration post binfile import 2024-04-08 17:29:58 +02:00
alonso.torres
28d6cf6f51 🐛 Fix import stroke attached library color 2024-03-07 17:54:49 +01:00
Andrey Antukh
b718a282e0 ♻️ Add minor refactor to file migrations
Relevant changes:

- Add the ability to create migration in both directions, defaulting
  to identity if not provided
- Move the version attribute to file table column for to make it more
  accessible (previously it was on data blob)
- Reduce db update operations on file-update rpc method
2024-02-19 09:20:47 +01:00
Andrey Antukh
afd68fa09d 🐛 Properly handle fdata features on file-gc task
It also adds a schema validation process after cleaning. If file
does not validates it will be skiped.
2024-02-13 19:36:10 +01:00