Skip to content

feat(artwork): shared object-store cache for cover art (phase 1.h.1)#28

Merged
InstaZDLL merged 3 commits into
mainfrom
feat/1-h-1-artwork-storage
Jun 6, 2026
Merged

feat(artwork): shared object-store cache for cover art (phase 1.h.1)#28
InstaZDLL merged 3 commits into
mainfrom
feat/1-h-1-artwork-storage

Conversation

@InstaZDLL
Copy link
Copy Markdown
Owner

@InstaZDLL InstaZDLL commented Jun 6, 2026

Summary

Sprint 1 / Phase 1.h.1 — foundation of the server-side artwork pipeline. Lays the storage abstraction + the metadata_artwork table + the two CRUD endpoints, all behind the WAVEFLOW_ARTWORK_LOCAL_DIR opt-in flag.

  • POST /api/v1/artwork (JWT-authed): raw image bytes, Content-Type ∈ {image/jpeg, image/png, image/webp}, 4 MiB cap. Hashes server-side with BLAKE3, skips the storage write when the row already exists — idempotent. Returns { hash, byte_size, mime, url }.
  • GET /api/v1/artwork/{hash} (public — the 64-hex hash is the credential, same shape as the share token): serves bytes with the recorded Content-Type, Cache-Control: public, max-age=31536000, immutable, ETag: "<hash>". Boundary validator rejects anything that isn't 64 lowercase hex chars before touching object_store keys.

Storage layer: Apache's object_store crate with the LocalFileSystem backend in this PR. S3 (object_store feature aws) lands in 1.h.2 with no caller change — same ObjectStore trait, same put / get / exists. Image resize (3 sizes) + the async pipeline follow in 1.h.3 + 1.i.1.

Both routes 503 when storage is unconfigured — same opt-in shape as streaming.

Surface added

  • migrations/20260606000000_metadata_artwork.sql — Postgres table, BLAKE3 hex PK, MIME + byte_size + created_at. CHECK constraints mirror the application-layer validators so a SQL-only client can't pollute the table.
  • src/storage.rsArtworkStorage (Arc-cloneable), ArtworkConfig::from_env, StorageError, is_well_shaped_hash.
  • src/api/artwork.rs — both handlers + the local ArtworkErrorIntoResponse mapping.
  • src/db.rs::artworkinsert_if_absent (race-safe via ON CONFLICT DO NOTHING) + fetch_meta.
  • tests/artwork.rs — 9 integration tests covering round-trip, idempotency, 503-when-disabled, MIME + empty-body rejections, bearer requirement on POST, hash validation on GET, charset-parameter-tolerant Content-Type parsing.

Test plan

  • cargo fmt --all --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --lib (24/24 unit tests pass locally, including 6 new in storage::tests)
  • DATABASE_URL=postgres://... cargo test --test artwork (integration suite — needs a reachable Postgres)
  • cargo test end-to-end (every existing test file got an artwork: None field on its AppState / Config literals; no behaviour change for non-artwork tests)

Follow-up tracked

  • 1.h.2 — S3 backend behind object_store's aws feature. MinIO-compat via custom endpoint.
  • 1.h.3 — image-crate resize pipeline (3 sizes: thumbnail / preview / full) with BLAKE3 per variant.
  • 1.i.1apalis Postgres-backed job queue, first job = artwork pipeline async.

Part of the post-1.g sprint plan validated 2026-06-05.

Summary by CodeRabbit

  • Nouvelles Fonctionnalités

    • Cache d’artwork avec déduplication par hash (BLAKE3), POST authentifié et GET public par hash; réponses avec ETag et cache longue durée.
  • Configuration

    • Activation optionnelle via variable d’environnement pour stockage local; routes d’artwork renvoient 503 si non configurées.
  • Données

    • Nouvelle table de métadonnées persistante (hash, mime, taille, created_at).
  • Validation

    • MIME limités (JPEG/PNG/WebP) et limite d’upload 4 MiB; uploads idempotents.
  • Documentation

    • Ajout de guide expliquant comportement, endpoints et future extension S3.
  • Tests

    • Tests d’intégration couvrant succès, idempotence et erreurs (401/400/413/503/404).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4792ad5e-8d26-4a70-92ac-8e336cb004ba

📥 Commits

Reviewing files that changed from the base of the PR and between f91fb3f and fe18d4d.

📒 Files selected for processing (1)
  • src/api/artwork.rs

📝 Walkthrough

Walkthrough

Cette PR ajoute un cache d’artwork indexé par BLAKE3 avec stockage local (object_store), table Postgres metadata_artwork, endpoints POST (authentifié) et GET (public) et tests d’intégration vérifiant validation, idempotence et erreurs.

Changes

Artwork Cache & Storage

Layer / File(s) Summary
Configuration et dépendances
Cargo.toml, .env.example, src/config.rs, CLAUDE.md
Ajout des dépendances runtime object_store, bytes, blake3. Documentation de WAVEFLOW_ARTWORK_LOCAL_DIR. Config.artwork: Option<ArtworkConfig> chargé depuis l'env.
Couche stockage d'artefacts
src/lib.rs, src/storage.rs
Ajout de storage exporté, ArtworkStorage (Arc), ArtworkConfig::from_env, StorageError. put/get/exists utilisent clés artwork/<hash> et tests unitaires/tokio pour round‑trips.
Schéma et accesseurs database
migrations/20260606000000_metadata_artwork.sql, src/db.rs
Création de metadata_artwork (hash PK 64‑hex, mime whitelist, byte_size>0, created_at). db::artwork::insert_if_absent et fetch_meta.
Handlers API et réponses HTTP
src/api/artwork.rs
POST /api/v1/artwork (JWT, 4 MiB max, validate Content‑Type, calcule BLAKE3 hex, idempotence DB/storage) et GET /api/v1/artwork/{hash} (public, valide hash, lit méta+bytes, sets Content-Type/Content-Length/Cache‑Control: immutable/ETag). ArtworkError mappe codes HTTP.
Bootstrap et wiring API
src/main.rs, src/api/mod.rs, src/lib.rs
Initialisation conditionnelle d’un ArtworkStorage::local si Config.artwork présent, injection dans AppState.artwork. Montage séparé d’un router auth et d’un router public pour /api/v1/artwork, fusionnés dans OpenApiRouter.
Tests d'intégration et support
tests/artwork.rs, tests/support.rs
Tests couvrent upload→GET round‑trip, idempotence (une entrée DB), disactivation (503), validation MIME/body/bearer/hash, et acceptation de Content‑Type avec paramètre. Helper spawn_app_with_jwt_and_artwork ajouté.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant JWT_Middleware
  participant UploadHandler as upload_artwork
  participant GetHandler as get_artwork
  participant DB as Database
  participant Storage as ArtworkStorage

  rect rgba(200, 220, 255, 0.5)
  Note over Client,Storage: POST /api/v1/artwork (Authentifié)
  Client->>JWT_Middleware: Bearer token + octets
  JWT_Middleware->>UploadHandler: request
  UploadHandler->>UploadHandler: validate Content-Type and size
  UploadHandler->>UploadHandler: compute BLAKE3(body) -> hash
  UploadHandler->>DB: fetch_meta(hash)
  alt meta exists
    DB-->>UploadHandler: Some(ArtworkMeta)
    UploadHandler-->>Client: 200 UploadResponse (short-circuit)
  else meta absent
    UploadHandler->>Storage: put(artwork/<hash>, bytes)
    Storage-->>UploadHandler: OK
    UploadHandler->>DB: insert_if_absent(hash, mime, size)
    DB-->>UploadHandler: OK
    UploadHandler-->>Client: 200 UploadResponse
  end
  end

  rect rgba(200, 255, 200, 0.5)
  Note over Client,Storage: GET /api/v1/artwork/{hash} (Public)
  Client->>GetHandler: GET /api/v1/artwork/{hash}
  GetHandler->>GetHandler: validate hash format
  GetHandler->>DB: fetch_meta(hash)
  alt found
    DB-->>GetHandler: Some(mime)
    GetHandler->>Storage: get(artwork/<hash>)
    alt retrieved
      Storage-->>GetHandler: Bytes
      GetHandler-->>Client: 200 + Cache-Control:public,max-age=31536000,immutable + ETag
    else not found
      Storage-->>GetHandler: StorageError::NotFound
      GetHandler-->>Client: 500 Internal Error
    end
  else not found
    GetHandler-->>Client: 404 Not Found
  end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🎨 Un hash chante, BLAKE3 fidèle,
Les octets trouvent leur nid local,
La DB note, l'API révèle,
Auth protège l'envoi initial,
Public admire, cache immuable.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Le titre décrit précisément la fonctionnalité principale ajoutée : un cache partagé basé sur object-store pour les artwork (phase 1.h.1), ce qui correspond directement aux changements du PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1-h-1-artwork-storage

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

@InstaZDLL InstaZDLL self-assigned this Jun 6, 2026
Lays the foundation of the server-side artwork pipeline: a BLAKE3-
keyed object_store wrapper, a `metadata_artwork` table that mirrors
the desktop's on-disk cache, and two endpoints that round-trip raw
image bytes.

POST /api/v1/artwork is JWT-authed, accepts image/jpeg | image/png |
image/webp up to 4 MiB, hashes server-side and skips the storage
write when the metadata row already exists — idempotent re-uploads
of the same content are free. GET /api/v1/artwork/{hash} is public
(the 64-hex hash is the credential, same shape as the share token),
short-circuits on a 64-char lowercase-hex validator before touching
object_store keys, and serves the recorded Content-Type with
Cache-Control: immutable + ETag = "<hash>".

object_store's LocalFileSystem backend ships here; the S3 backend
lands in 1.h.2 with no caller change (same trait, same put/get/exists).
Image resize (3 sizes) + the async pipeline follow in 1.h.3 + 1.i.1.

Tests cover round-trip, idempotency, 503-when-disabled, MIME +
empty-body rejections, bearer requirement on POST, hash validation
on GET, and the charset-parameter-tolerant Content-Type parse.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL InstaZDLL force-pushed the feat/1-h-1-artwork-storage branch from 5bdeb81 to a8eb339 Compare June 6, 2026 17:34
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/artwork.rs`:
- Around line 1-16: The coverage matrix mentions rejecting oversize uploads but
no test covers it; add a test named upload_rejects_oversized_body that
constructs a request body with length > MAX_UPLOAD_BYTES, posts to the artwork
upload endpoint (same helper/path used by other tests in tests/artwork.rs), and
asserts the response status is 413 PAYLOAD_TOO_LARGE (and optionally checks the
error body); use the same Content-Type and auth setup as existing upload tests
so it fits into the test harness and references the MAX_UPLOAD_BYTES constant to
generate the oversized payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 849339b6-ddcf-4a0f-8372-230c42957d4c

📥 Commits

Reviewing files that changed from the base of the PR and between 06c7485 and 5bdeb81.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock, !*.lock
📒 Files selected for processing (13)
  • .env.example
  • CLAUDE.md
  • Cargo.toml
  • migrations/20260606000000_metadata_artwork.sql
  • src/api/artwork.rs
  • src/api/mod.rs
  • src/config.rs
  • src/db.rs
  • src/lib.rs
  • src/main.rs
  • src/storage.rs
  • tests/artwork.rs
  • tests/support.rs

Comment thread tests/artwork.rs
CodeRabbit noted the coverage matrix promised a 413 test but no test
asserted it. Add `upload_rejects_oversized_body` (4 MiB + 1 byte
post → 413) and widen the auth router's `DefaultBodyLimit` to
`MAX_UPLOAD_BYTES + 1024` so the handler's `body.len() >
MAX_UPLOAD_BYTES` check is what observes the overflow — axum's
default 2 MiB limit would otherwise pre-empt our error variant and
let the test pass for the wrong reason.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL
Copy link
Copy Markdown
Owner Author

@coderabbitai bien vu — j'avais oublié de tester le cap 4 MiB. Ajouté upload_rejects_oversized_body (4 MiB + 1 byte → 413).

Subtilité : axum 0.8 ship un DefaultBodyLimit de 2 MiB qui aurait intercepté la requête avant notre handler. J'ai donc layered DefaultBodyLimit::max(MAX_UPLOAD_BYTES + 1024) sur le auth_router artwork pour que notre check body.len() > MAX_UPLOAD_BYTES soit le vrai gardien — sinon le test passerait pour la mauvaise raison (axum 413, pas le notre).

Push: f91fb3f.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the relevant changes in the commit f91fb3f
git show f91fb3f --stat
echo "---"
git show f91fb3f -- src/api/artwork.rs | head -80
echo "---"
git show f91fb3f -- tests/artwork.rs | head -60

Length of output: 5196



@InstaZDLL : logique impeccable. Le +1 KiB de marge est juste suffisant pour que le payload de 4 MiB + 1 byte passe le filtre axum et atterrisse dans le handler — c'est exactement là que le contrôle doit vivre. Le cas limite exact (exactement 4 MiB) est couvert implicitement par les tests de round-trip existants, donc pas de trou dans la matrice.

Tout est bon. ✓

(ノ◕ヮ◕)ノ*:・゚✧


🧠 Learnings used
Learnt from: InstaZDLL
Repo: InstaZDLL/waveflow-server PR: 11
File: src/api/users.rs:0-0
Timestamp: 2026-05-30T17:08:15.408Z
Learning: Dans utoipa-axum 0.2 (crate `utoipa-axum`), l'attribut `required = false` n'est PAS supporté dans `request_body(...)` — les seuls attributs acceptés sont `content`, `description`, `content_type`, `example`, `examples`, `extensions`. Pour documenter un corps de requête optionnel (`requestBody.required: false`), il faut utiliser le wrap type-level : `request_body = Option<TheType>`. Repository : InstaZDLL/waveflow-server, fichier : src/api/users.rs.

Learnt from: CR
Repo: InstaZDLL/waveflow-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-05-31T13:47:30.071Z
Learning: Implement the streaming feature (`Phase 1.e`) with `POST /api/v1/profiles/{p}/libraries/{l}/tracks/{t}/stream-url` (JWT-authed) to sign short-lived (≤ 60 s) URLs via `stream_token::mint`, and `GET /api/v1/stream/{token}` mounted outside the JWT layer. Canonicalize paths via `std::fs::canonicalize` and refuse anything outside `WAVEFLOW_MUSIC_ROOT`. Handle Range requests: `Accept-Ranges: bytes`, 206 + `Content-Range` on partial, 416 on unsatisfiable. Both endpoints answer 503 when streaming is disabled

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
tests/artwork.rs (1)

1-16: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Incohérence entre la matrice de couverture et les tests implémentés.

La matrice de couverture (ligne 12) annonce « 400 / 413 boundary cases (wrong MIME, empty body, oversize) », mais aucun test ne valide le rejet d'un corps dépassant 4 MiB (413 PAYLOAD_TOO_LARGE). Les tests couvrent MIME invalide (ligne 165) et corps vide (ligne 181), mais pas le cas body.len() > MAX_UPLOAD_BYTES.

Soit ajouter un test upload_rejects_oversized_body, soit retirer « oversize » de la matrice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/artwork.rs` around lines 1 - 16, The coverage matrix claims an
"oversize" case but no test implements it; add a test named
upload_rejects_oversized_body that posts a payload with length >
MAX_UPLOAD_BYTES to the POST /api/v1/artwork endpoint and asserts a 413
PAYLOAD_TOO_LARGE response (and does not create a storage row), using the same
test harness/helpers as the other upload tests and referencing the
MAX_UPLOAD_BYTES constant to construct the oversize body; alternatively, if you
prefer not to add a test, remove the word "oversize" from the coverage matrix
line to keep doc and tests consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/artwork.rs`:
- Around line 1-16: The coverage matrix claims an "oversize" case but no test
implements it; add a test named upload_rejects_oversized_body that posts a
payload with length > MAX_UPLOAD_BYTES to the POST /api/v1/artwork endpoint and
asserts a 413 PAYLOAD_TOO_LARGE response (and does not create a storage row),
using the same test harness/helpers as the other upload tests and referencing
the MAX_UPLOAD_BYTES constant to construct the oversize body; alternatively, if
you prefer not to add a test, remove the word "oversize" from the coverage
matrix line to keep doc and tests consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 10daa358-c60a-497c-b607-49c3c8d73b31

📥 Commits

Reviewing files that changed from the base of the PR and between 5bdeb81 and a8eb339.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock, !*.lock
📒 Files selected for processing (13)
  • .env.example
  • CLAUDE.md
  • Cargo.toml
  • migrations/20260606000000_metadata_artwork.sql
  • src/api/artwork.rs
  • src/api/mod.rs
  • src/config.rs
  • src/db.rs
  • src/lib.rs
  • src/main.rs
  • src/storage.rs
  • tests/artwork.rs
  • tests/support.rs

Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/artwork.rs (1)

185-199: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Incohérence mineure dans la réponse idempotente.

Quand le contenu existe déjà (ligne 185), la réponse utilise byte_size et mime de la requête courante au lieu des valeurs stockées (_existing). En pratique c'est identique (même hash = mêmes bytes = même taille), mais récupérer _existing.byte_size et _existing.mime serait plus cohérent et documenterait l'état réel en base.

♻️ Suggestion optionnelle
-    if let Some(_existing) = crate::db::artwork::fetch_meta(&state.db, &hash)
+    if let Some(existing) = crate::db::artwork::fetch_meta(&state.db, &hash)
         .await
         .map_err(ArtworkError::Db)?
     {
         return Ok((
             StatusCode::OK,
             Json(UploadResponse {
                 hash: hash.clone(),
-                byte_size,
-                mime,
+                byte_size: existing.byte_size,
+                mime: existing.mime,
                 url: format!("/api/v1/artwork/{hash}"),
             }),
         )
             .into_response());
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/artwork.rs` around lines 185 - 199, The idempotent upload branch
returns the request's byte_size and mime instead of the stored values; update
the code that handles the Some(_existing) case (the result of
crate::db::artwork::fetch_meta) to read and return _existing.byte_size and
_existing.mime in the UploadResponse (keeping hash and url as-is) so the
response reflects the database state; ensure you still
map_err(ArtworkError::Db)? and return the same StatusCode::OK Json(...) response
shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/api/artwork.rs`:
- Around line 185-199: The idempotent upload branch returns the request's
byte_size and mime instead of the stored values; update the code that handles
the Some(_existing) case (the result of crate::db::artwork::fetch_meta) to read
and return _existing.byte_size and _existing.mime in the UploadResponse (keeping
hash and url as-is) so the response reflects the database state; ensure you
still map_err(ArtworkError::Db)? and return the same StatusCode::OK Json(...)
response shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0de72ed6-91aa-4f15-a815-167acdb8369a

📥 Commits

Reviewing files that changed from the base of the PR and between a8eb339 and f91fb3f.

📒 Files selected for processing (2)
  • src/api/artwork.rs
  • tests/artwork.rs

CodeRabbit flagged that the idempotent branch was echoing the
request's `byte_size` + `mime` instead of the stored row's values.
Today they're identical (BLAKE3 collision-resistance), but
`UploadResponse::mime` already documents the contract "what the
server stored" — so a future canonicalisation (e.g. mapping
`image/jpg` → `image/jpeg`) would silently break that promise.
Read through `existing` so the documented behaviour holds
unconditionally.

Signed-off-by: InstaZDLL <github.105mh@8shield.net>
@InstaZDLL
Copy link
Copy Markdown
Owner Author

@coderabbitai bien vu — le DTO documente déjà mime comme "what the server stored" (UploadResponse::mime doc), donc retourner les valeurs de la requête trahit le contrat dès qu'une normalisation MIME atterrit (image/jpg → image/jpeg, par exemple). Lu via existing.byte_size + existing.mime.

Push: fe18d4d.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Tip

For best results, initiate chat on the files or code changes.

@InstaZDLL : parfait — retourner existing.mime / existing.byte_size depuis la DB est le seul comportement correct, surtout si la normalisation MIME évolue plus tard (ou si une future pipeline resize modifie byte_size). Le DTO reflète maintenant exactement ce que le serveur a persisté. ✓


🧠 Learnings used
Learnt from: InstaZDLL
Repo: InstaZDLL/waveflow-server PR: 11
File: src/api/users.rs:0-0
Timestamp: 2026-05-30T17:08:15.408Z
Learning: Dans utoipa-axum 0.2 (crate `utoipa-axum`), l'attribut `required = false` n'est PAS supporté dans `request_body(...)` — les seuls attributs acceptés sont `content`, `description`, `content_type`, `example`, `examples`, `extensions`. Pour documenter un corps de requête optionnel (`requestBody.required: false`), il faut utiliser le wrap type-level : `request_body = Option<TheType>`. Repository : InstaZDLL/waveflow-server, fichier : src/api/users.rs.

Learnt from: CR
Repo: InstaZDLL/waveflow-server PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-05-31T13:47:30.071Z
Learning: Implement the streaming feature (`Phase 1.e`) with `POST /api/v1/profiles/{p}/libraries/{l}/tracks/{t}/stream-url` (JWT-authed) to sign short-lived (≤ 60 s) URLs via `stream_token::mint`, and `GET /api/v1/stream/{token}` mounted outside the JWT layer. Canonicalize paths via `std::fs::canonicalize` and refuse anything outside `WAVEFLOW_MUSIC_ROOT`. Handle Range requests: `Accept-Ranges: bytes`, 206 + `Content-Range` on partial, 416 on unsatisfiable. Both endpoints answer 503 when streaming is disabled

@InstaZDLL InstaZDLL merged commit 5a2c68d into main Jun 6, 2026
8 checks passed
@InstaZDLL InstaZDLL deleted the feat/1-h-1-artwork-storage branch June 6, 2026 18:03
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.

1 participant