Commit Graph

799 Commits

Author SHA1 Message Date
Drake 1801150ad4 make oidc check if email verification is required 2026-07-31 11:09:33 -05:00
Edouard Vanbelle 5a87999949 fix(integration test): fix drive test used bytes with grace period 2026-07-30 01:29:09 +02:00
Edouard Vanbelle 9fe47a53ea feat(jobs): add admin call to purge jubs result 2026-07-30 00:39:11 +02:00
Edouard Vanbelle 507bc2e98d feat(recoverable-job): add backend_consistency (storage) 2026-07-30 00:15:20 +02:00
Edouard Vanbelle 8de50ec404 feat(jobs/ui): blobs_consistency add b3sum check 2026-07-29 23:15:45 +02:00
Edouard Vanbelle e822714b50 feat(jobs/ui): reorder job names + fix(blobs_consistency) 2026-07-29 23:01:20 +02:00
Edouard Vanbelle 5527d09618 feat(recoverable-job): check if old blob (no cdc) still remains: notice only 2026-07-29 22:47:07 +02:00
Edouard Vanbelle 61b8571a48 feat(recoverable-job): add grace period for drives, files, folders consistency 2026-07-29 22:29:20 +02:00
Edouard Vanbelle 44356b14f2 feat(recoverable-job): add blobs_consistency job 2026-07-29 22:25:05 +02:00
Edouard Vanbelle 0f12399a48 feat(recoverable-job): fix files_consistency to check blob chunk consistency 2026-07-29 22:17:15 +02:00
Edouard Vanbelle 5881968f50 feat(recoverable-job): add progress view 2026-07-29 08:41:19 +02:00
Edouard Vanbelle 4336eca4d1 feat(recoverable-job): add admin page 2026-07-29 08:03:43 +02:00
Edouard Vanbelle e1556e3d36 feat(recoverable-job): add findings 2026-07-29 07:57:06 +02:00
Edouard Vanbelle 41d83b3053 feat(recoverable-job): add consistency_batch (runs all consistency check) 2026-07-29 01:44:52 +02:00
Edouard Vanbelle 782a5c99bd feat(recoverable-job): add folder_consistency 2026-07-29 01:39:58 +02:00
Edouard Vanbelle 394708c9a1 feat(recov. job): add drive consistency service 2026-07-29 01:25:15 +02:00
Edouard Vanbelle 8177b04786 feat(recoverable-job): wire API (cancel, view run, ...) 2026-07-28 23:25:37 +02:00
Edouard Vanbelle 302d2ff80f feat(recoverable-job): add core engine 2026-07-28 22:39:07 +02:00
Edouard Vanbelle 0e8b1fbbeb refactor(job-registry): simplify the job registering* 2026-07-28 21:13:09 +02:00
Edouard Vanbelle 24c514fe59 fix(job-registry): fix error handling* 2026-07-28 00:35:31 +02:00
Edouard Vanbelle f66f7fa31f feat(job-registry): remplace /api/admin/internal/trigger-*
remplace /api/admin/internal/trigger-* to /api/admin/jobs/{...}/trigger
remove OXICLOUD_ENABLE_ADMIN_INTERNAL_ENDPOINTS
2026-07-27 23:54:43 +02:00
Edouard Vanbelle dfedde54a4 feat(job-registry): wire /api/admin/jobs/* 2026-07-27 23:38:12 +02:00
Edouard Vanbelle 72b99f5b9a refactor: apply clippy 2026-07-27 23:06:02 +02:00
Edouard Vanbelle 1190dfef87 feat(job-registry): wire grant cleaner 2026-07-27 23:03:22 +02:00
Edouard Vanbelle f740de3c10 feat(job-registry): wire dedub_gc 2026-07-27 22:56:02 +02:00
Edouard Vanbelle f5f794fde5 feat(job-registry): handle job without periodicity but with trigger 2026-07-27 22:53:13 +02:00
Edouard Vanbelle e29f3ba067 feat(job-registry): wire storage usage update 2026-07-27 22:33:04 +02:00
Edouard Vanbelle d0f4e1ef5a feat(job-registry): wire engine + wire trash cleaner 2026-07-27 22:24:15 +02:00
Edouard Vanbelle 8bb62affc8 feat(job-registry): add the job engine 2026-07-27 22:05:08 +02:00
Edouard Vanbelle e487af6e7e feat(breadcrumb): show the granter (sharer) in the breadcrumb 2026-07-27 01:33:09 +02:00
Edouard Vanbelle 3b31b8911b feat(breadcrumb): build breadcrumb in 1 API call
add /api/folders/{id}/ancestors

    this API to iterate parent up to the drive root or the shared folder
    this will help UI to build the breadcrumb in 1 API call
    and to identify the root element (is it a drive users has access to or
    a shared folder ?)

    ui: now only 1 API call is now required to build the breadcrumb
2026-07-27 01:33:09 +02:00
Edouard Vanbelle 99e6de6fce feat(search): invalidate user's cache on share or favorite change 2026-07-26 18:18:15 +02:00
Edouard Vanbelle 87ddf8ffc8 refactor(search): simplify order by + wire reverse 2026-07-26 18:18:15 +02:00
Edouard Vanbelle c22741bc7f refactor(search): normalize answer to /resources format 2026-07-26 18:18:15 +02:00
Edouard Vanbelle 53077e0fa3 fix(merge): fix merge of mount + is_favorite feature
fix the merge of moun feature and is_favorite + is_shared feature
    together it was raising an impossible build due to missing schema
2026-07-22 08:30:06 +02:00
Dionisio Pozo ef3e3f45b3 Merge pull request #521 from BCNelson/feat/external-file-mounts
feat(mounts): external file mounts — pluggable provider, read-write, WebDAV/NextCloud, admin UI
2026-07-22 06:52:43 +02:00
Dionisio Pozo 86268049ff Merge pull request #641 from EdouardVanbelle/refactor/front-resource-list
feat(fileDto, folderDto): add is_favorite + is_shared
2026-07-22 06:51:28 +02:00
DioCrafts d66956824c Optimize storage, GC, and upload hot paths 2026-07-22 02:06:29 +02:00
Edouard Vanbelle d9987782c4 feat(fileDto, folderDto): add is_favorite + is_shared
- provide is_shared and is_favorite information in DTO, information propagated as badge/buttons per items
      regarding performances I try to be minimalis on SQL to prevent any perf regression

    - remove old set of sharedids and favoriteids (was not functionnal anymore)
    - fix date picker (no past selection) in grant
    - fix contextmenu close on /files section
2026-07-22 01:55:10 +02:00
Bradley Nelson 05ae217022 fix(mounts): post-merge fmt + audit + minimal lockfile
- rustfmt the integration-test FolderService::new callsites added during
  the upstream merge (long single-line args wrapped).
- Cargo.lock: rebuild from upstream/main's lock so only the
  testcontainers-modules dev-dep subtree and the memmap2 0.9.10→0.9.11
  security bump differ (avoids churning upstream's pins).
- .cargo/audit.toml: ignore the four astral-tokio-tar tar-extraction
  advisories — dev-only, transitive via testcontainers-modules
  (integration-test harness), never in the production binary.
2026-07-21 17:25:48 -06:00
Bradley Nelson 9f4bc54113 Merge upstream/main into feat/external-file-mounts
Resolve conflicts between the external-file-mounts feature and upstream's
D5/D7 refactor (per-file provenance, keyset pagination, cross-drive move
gates, resource-access hook, folder-cascade lifecycle hook).

Key resolutions:
- FolderService::new now takes (repo, authz, file_lifecycle, mount_router);
  all callers + DI updated.
- FileRetrievalService / FileManagementService keep both the mount_router
  and the new resource_access_hook / drive_repo / storage_usage wiring.
- list_files_batch_with_perms: adapt the mount branch from offset- to
  keyset (after_name) pagination, mirroring paginate_mount_entries.
- download_file_impl: keep upstream's &HeaderMap + `impl IntoResponse + use<>`
  signature, retain the mount-download branch.
- Mount DTOs: the retired `owner_id` field maps onto created_by/updated_by
  (the mount owner) — the fields the frontend now uses for owner display.
- admin/+page.svelte: keep upstream's user-delete modal + the 'mounts' tab.
- Bump memmap2 0.9.10 -> 0.9.11 (RUSTSEC critical advisory fix) and
  regenerate Cargo.lock against the merged Cargo.toml.
2026-07-21 17:09:36 -06:00
Dionisio Pozo 67fe944c2a Merge pull request #596 from swissiety/rfc-5789-http-patch 2026-07-22 00:32:27 +02:00
Claude 8e55caa8a9 perf(round29): cache-serve borrow-probe, NC REPORT href buffer, auth per-req allocs, DB over-fetch
Seven behaviour-preserving allocation / copy / bandwidth cuts, each behind a
counting-allocator BEFORE/AFTER gate that exit(1)s unless AFTER allocates
strictly fewer than BEFORE (benches/ROUND29.md, examples/bench_round29_micro.rs).

- [B] Content-cache serve fast path (optimized_inner Tier 1 +
  get_file_range_preloaded — the video-scrub hot path): probe the cache with a
  borrow first and build the owned get_or_load args (quoted-etag / key / id
  Strings) only on a miss, instead of allocating them before every probe and
  discarding them on a hit. 6 -> 0 allocs per cache hit. Splits get_or_load into
  get + load_and_cache so the miss path is not re-probed and the hit/miss stat
  counters stay byte-identical. Also drops the unconditional content_hash/name
  clones that ran for the >=10 MB streaming tier that used neither.
- [A] NextCloud REPORT emit loops: per-row href String (and format! per folder
  row) -> one reused href_buf via nc_href_into / nc_collection_href_into with the
  URL-encoded user computed once per page. 1497 fewer allocs on a 500-row page.
- [C] read_full: a single-frame blob is returned zero-copy instead of a second
  whole-payload memcpy into a fresh BytesMut; multi-frame path unchanged.
- [D] login-lockout key: to_lowercase()+format! -> one pre-sized ASCII buffer
  (non-ASCII keeps str::to_lowercase). 3 -> 1 alloc/req, byte-identical key.
- [E] NC composite-username parse: owned clone/to_string -> &str borrow of the
  already-owned raw_username. 1 -> 0 alloc on the common no-marker path.
- [F] get_contacts_in_group: stop SELECTing the discarded multi-KB vcard column
  (the live method ROUND25 §Q2 missed; ContactDto has no vcard field).
- [G] count_admin_users: add count_users_by_role -> scalar COUNT(*) instead of
  hydrating every admin's full row (incl. up-to-512 KiB avatar + ui_preferences
  JSONB) only to .len() it, on a bootstrap-polled status endpoint.

All seven gates pass; cargo fmt --check and cargo clippy --all-features
--all-targets -D warnings clean. §F/§G additionally validated against a live
PostgreSQL 16 with the full migration set (query validity, result equivalence,
600000 -> 8 byte wire delta on the admin count).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhpDZxSQTAGnAqCHUdtG5N
2026-07-21 10:25:36 +00:00
Claude 9333037fc7 perf(round28): extend the PROPFIND oc:id reused buffer (round27 H1) to the REPORT emit loops
report_handler's four REPORT emit loops (the filter-files favorites REPORT and the
search REPORT, each a file loop + a folder loop) shared the same per-row oc:id
String that ROUND27 §H1 replaced in the two PROPFIND page loops. Apply the
identical, already-validated transformation: hoist one oc_buf per handler (reused
across both its loops) and compute the id into it via format_oc_id_into instead of
a fresh format_oc_id String per child. 1 String/row -> 0 (amortized). The
write_{file,folder}_response fns already take Option<&str>, so their signatures are
unchanged and the emitted oc:id bytes are byte-identical.

Same transformation benchmarked in ROUND27 §H1 (bench_round27_micro: 998 -> 0
per-row allocs, 2.16x wall), so no new bench. Verified: cargo fmt clean, cargo
clippy --features bench -D warnings clean, cargo test --lib --features bench =
529 passed / 0 failed across 5 consecutive runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
2026-07-21 01:49:37 +00:00
Claude 8c936de50d perf(round27): NextCloud PROPFIND oc:id per-row buffer, contact JSONB write direct-serialize
Two behaviour-preserving allocation cuts (benches/ROUND27.md), each with a
counting-allocator BEFORE/AFTER gate that rolls back if AFTER does not allocate
fewer than BEFORE:

- H1 NextCloud PROPFIND: the streaming page loops built oc:id as a fresh String
  per child (format_oc_id -> format!("{:08}{}", id, instance)). Add
  format_oc_id_into(&mut buf, id, svc) and compute into one oc_buf reused across
  the page (next to the existing href buffer) — 1 String/row -> 0. 998->0
  per-row allocs on a 500-row page, 2.16x wall. The write fns still take
  Option<&str>, so no signature change; oc:id bytes identical. Scoped to the two
  PROPFIND page loops (the hot directory-listing path); REPORT/trashbin deferred.
- P2 contact create/update: bind sqlx::types::Json(&dtos) (Encode runs to_writer
  straight into the JSONB buffer) instead of serde_json::to_value(&dtos) + bind,
  which built a throwaway Value DOM per JSONB column. Write-side twin of ROUND23
  J1. 21->2 allocs, 4.68x wall for a 3-entry column. Behaviour-preserving:
  to_value sorts keys and direct serialize keeps struct order, but Postgres
  normalizes JSONB key order so the stored value is identical (verified via psql:
  '{...alpha...}'::jsonb = '{...struct...}'::jsonb -> t), and reads decode by
  field name; the etag comes from the domain entity, not the stored JSONB.

Adds bench_round27_micro. Verified: cargo fmt clean, cargo clippy --features
bench -D warnings clean (real exit), cargo test --lib --features bench = 529
passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
2026-07-21 01:30:53 +00:00
Claude 5b2bb8f883 perf(round26): drive-policy JSONB decode, CachedBlobBackend shard-dir pre-create, delta-upload foldhash
Three benchmark-gated optimizations from the ROUND25 backlog (benches/ROUND26.md),
each with a BEFORE/AFTER gate that rolls back if AFTER does not beat BEFORE:

- P1 drive_pg_repository policy reads: decode d.policies through
  sqlx::types::Json<DrivePolicies> (one from_slice over the raw JSONB bytes)
  instead of a throwaway serde_json::Value DOM + DrivePolicies::from_value —
  6 -> 0 allocs/read, 2.77x wall. A shared policies_from_row helper preserves
  the lenient unwrap_or_default fallback (malformed bag -> all-false).
- D1 CachedBlobBackend: pre-create the 256 {00..ff} shard dirs at initialize()
  (mirroring LocalBlobBackend, reusing HEX_PREFIXES) and drop the redundant
  per-write create_dir_all on already-existing shards — ~45us + a blocking-pool
  dispatch removed per cache write on cached-remote deployments.
- G1 delta-upload have/need hash sets (distinct_hashes, authorize_chunk_download):
  SipHash -> foldhash::quality::RandomState — a fast hasher that stays
  DoS-resistant via a per-instance random seed, the required property for the
  attacker-controlled 64-hex client hashes — 2.37x wall on a 40k-hash
  negotiation. foldhash was already in the lockfile transitively (hashbrown).

Tested and REVERTED (kept as-is): moving the moka eviction unlink off the reactor
via spawn_blocking. The benchmark refuted it — on the local cache dir the
spawn_blocking dispatch (~20us) costs more than the inline unlink (~7us) it would
replace. See ROUND26.md §D2.

Adds bench_round26_{micro,diskio,hasher} (counting allocator / async wall / wall).
Verified: cargo fmt clean, cargo clippy --features bench -D warnings clean,
cargo test --lib --features bench = 529 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
2026-07-21 01:05:12 +00:00
Claude e8e4ef4b15 perf(round25): in-place encrypted decrypt, dedup hash move, dead folder-Query, playlist N+1 fold, contact vcard over-fetch
Five benchmark-gated optimizations from a fresh six-way audit (benches/ROUND25.md),
each with a BEFORE/AFTER gate that rolls back if AFTER does not beat BEFORE:

- M1 EncryptedBlobBackend::decrypt_bytes: replace split_off (a fresh Vec + full
  ciphertext memcpy on every decrypted chunk, contradicting its own "in place"
  doc) with in-place detached decrypt + a zero-copy Bytes::slice past the nonce.
  Peak RAM per read drops from ~2x to ~1x the payload (-262KB/op at 256KiB;
  scales with blob size). Plaintext byte-identical; tamper/wrong-key tests pass.
- M2 delta commit: move-unzip the owned chunk list instead of a third
  per-occurrence hash clone (-4000 allocs on a 4000-chunk commit).
- M3 folder ZIP download: drop the dead Query<HashMap> extractor it never read
  (byte-identical response; 5->0 allocs/request).
- Q1 public-playlist listing: fold the per-playlist COUNT(*) N+1 into one
  LEFT JOIN ... GROUP BY via a new inherent repo method (101 -> 1 round-trips,
  36x wall on a 100-playlist page).
- Q2 contact REST listings (paginated/search/by-group): stop over-fetching the
  multi-KB vcard TEXT the ContactDto discards, via a shared lite row mapper and
  narrowed SELECTs (6.4x wall on 1000 contacts with 8KiB vcards). The whole-book
  vCard export and CardDAV sync paths keep the column.

Adds bench_round25_micro (counting allocator tracking count+bytes) and
bench_round25_queries (live Postgres), both with equivalence gates and a
rollback exit(1). Verified: cargo fmt clean, cargo clippy -D warnings clean,
cargo test --lib --features bench = 529 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
2026-07-21 00:07:44 +00:00
Dionisio Pozo 0d82632f92 Merge pull request #629 from EdouardVanbelle/refactor/front-resource-list
Refactor(front) restore legacy frontend features
2026-07-21 00:16:40 +02:00
Edouard Vanbelle 931e27d09c fix(resources): wire missing created_by and updated_by 2026-07-20 21:04:32 +02:00
Claude ffb536e0ae perf: round 24 — download_zip per-item authz+metadata N+1 → batch (validated authorization pass)
The ROUND23-deferred download_zip N+1, given its own validated pass. The
individually-selected files were authorized + fetched one at a time via
get_file_with_perms (require + get = 2 serial round-trips/file) before any
streaming — a 200-file selection was 400 serial round-trips. AFTER routes the
whole multi-select through the new
FileRetrievalService::get_files_by_ids_with_perms: one check_files_read_batch
(the PgAclEngine resolves every file's drive in ONE query and primes the
resource->drive cache) + one get_files_by_ids. 2N round-trips -> 2.

Authorization is unchanged and still enforced BEFORE any ZIP entry is written:
- add_file_entry_streamed writes the entry header (the filename) before it opens
  the authorized stream, so the pre-filter is load-bearing — a denied file must
  never reach it or its name leaks into the archive. AFTER a denied/missing id
  is absent from the authorized map and is skipped in the same input order,
  exactly as the old loop skipped a denied get_file_with_perms; it never reaches
  the entry write. The authz moved from a per-file require to one batch check
  EARLIER in the same function, not into or after the stream.
- The stream open keeps its own per-file Read check (now a primed-cache hit) +
  Recents recording; check_files_read_batch is documented + gated as identical
  to looping require.

Because the change is authorization-sensitive, the gate is the security property
itself. bench_round24_zip_authz drives the real PgAclEngine over a seeded,
interleaved mix of owned (granted drive) + denied (other drive) + missing ids
and asserts: the batch inclusion set AND input order are identical to the
per-file require loop; the included set is exactly the caller's owned files; no
denied or missing id is ever included (the authz-regression tripwire); and the
batch fetch returns exactly the owned files. Latency (cold, 600-item 1/3-owned
selection): 559 -> 267 ms (2.10x; the realistic all-owned selection is O(1) ->
a larger win). See benches/ROUND24.md.

The folder selections are left as-is (root counts are small and there is no
check_folders_read_batch primitive to batch through).

Verified: cargo clippy --features bench --all-targets -D warnings clean, cargo
fmt --all --check clean, cargo test --lib --features bench = 529 passed / 0
failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKyQ4AnYtgp1JtjzweyMeo
2026-07-20 17:12:38 +00:00