feat(commons): package layout sources/ masters/ derivatives/ under commons/assets/song/<id>/ - #152
Closed
jzongker wants to merge 7 commits into
Closed
feat(commons): package layout sources/ masters/ derivatives/ under commons/assets/song/<id>/#152jzongker wants to merge 7 commits into
jzongker wants to merge 7 commits into
Conversation
…mmons review
Reviewers can now send a pending submission back to the writer as a draft
with a note (POST /commons/admin/submissions/:id/request-changes), and
approve a proposal while declining some of its files with a reason each
(approve body gains declineFiles). Declined files are recorded on the
submission as filesChanged { action: "declined", reason } and listed in
the approval mail.
COMMONS_MUSIC_EDITORS (user ids or emails) names reviewers who are not
server admins: they see the queue and detail, approve, request changes
and reject, but an approval whose proposal changes rights (license,
licenseVersion, recordingOwned, proAnswer, a new demo/stems recording, or
a first publish) answers 403, and reports/visibility/feature/score routes
answer 403 too. GET /admin/status gains musicEditor.
The four submitter mails (received, approved, changes requested,
rejected) share one template so wording and footer stay consistent.
Tests: yarn jest src/modules/commons (192), yarn tsc --noEmit, eslint on
touched files.
Adds the package-model columns to songs (confidence tier, first line, rights and form maps, sing time, score source, listen-gate record), seeds them from the content repo packages (masters/song.json, derivatives/*, work inheritance), and recomputes them in the song publish hook. GET /commons/songs rows gain confidence/sundayReady/featured/has* flags and the sundayReady/confidence/ language/q filters; the detail adds rights, rightsMatrix, ccliReport, attribution, form, publishedKeys, contributors and the new fileUrls roles. New GET /commons/songs/:id/page bundles song + rating + history + family + similar, and POST /commons/admin/songs/:id/listen records the listen gate. Tests: RightsHelper table, seed reader against a fixture package repo, package shaping, /page shape, listen-gate transitions, publish hook fields.
Adds submissions.type (new | translation | arrangement | correction | additionalFile | removal) with per-type rights triage at submit, the score / scoreImage / lyrics upload roles, a ChordPro bracket lint that blocks submit, a sources inventory inside manifest.json, songs.contributors credits on approve, removal-request approval that unpublishes, triage-ordered queue and type-aware emails. Covered by the commons jest suite (177 tests); e2e runs in the batch integration pass.
# Conflicts: # src/modules/commons/controllers/CommonsAdminController.ts
Reconciled PublishHelper.approve so A3 declineFiles, A2 removal-type approval and A1 filesChanged-to-hooks all run; song hook writes both A2 contributors and A1 packageFields, manifest keeps A2 sources rows with A1 ContentLibraryHelper.role(). Reviewer checks stay on ReviewerHelper.canReview; controllers keep A1 query params and /page, A2 type in queue/detail/mine, A3 request-changes; mail merges A3 submitterMail footer with A2 type-aware wording. songs.contributors is now a real column, so SongPackageHelper.detail parses it from the loaded row and SongRepo.loadContributors (A1 fallback) is gone; models, SongRepo and test mocks are the union of all three branches.
…mmons/assets/song/<id>/ A song's live files now keep their package folder: assetFiles.name stores the package-relative name (sources/tune.mid, masters/lyrics.chordpro, derivatives/slides.json) and roles are decided by basename (helpers/PackageLayout.ts), so fileUrls keys are unchanged and only the URL path gains the folder. manifest.json stays at the package root. Pending uploads stay flat; packagePath() places them on approve (uploads to sources/, art to masters/, art-thumb.webp renamed onto derivatives/cover-thumb.webp) and a same-basename file in another folder is superseded. The song hook writes masters/song.json and masters/lyrics.chordpro. Non-song assets keep the flat layout. The seed builds the song row from masters/song.json + lyrics.chordpro (+ sources/hymnary.json, sources/video.json, derivatives/duration.json), with catalog.json only filling ids, paths, confidence and parentSongId; every package file is copied into the id-keyed folder keeping its folder, inherited work files land under the song. reset-commons no longer mirrors writers/ (portraits travel in copies); tools/manual/commons-prune-flat-mirror.ts lists and with --apply deletes commons/songs|works|writers/ except portraits authors.portraitUrl references. Tests: yarn jest src/modules/commons — 23 suites / 289 tests.
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
commons/assets/song/<id>/sources/<file>,masters/song.json,masters/lyrics.chordpro,masters/cover.webp(+masters/score.musicxml,masters/art.<ext>when present),derivatives/<file>;manifest.jsonstays at the package root.assetFiles.namestores the package-relative name; roles are decided by basename (newhelpers/PackageLayout.ts), sofileUrlskeys are unchanged for the site and B1Admin — only the URL path carries the folder. Non-song assets keep their flat layout.packagePath()places them on promotion (uploads →sources/, art →masters/art.<ext>, browserart-thumb.webprenamed ontoderivatives/cover-thumb.webp, generated files →derivatives/). A same-basename live file in another folder (a seededderivatives/score.musicxmlunder an uploadedsources/score.musicxml) is superseded. The song hook writesmasters/song.jsonandmasters/lyrics.chordpro;filesChangedand the manifest'sfiles[].name/sources[].fileare package-relative.tools/commons-seed/catalog.ts): the song row is built frommasters/song.json+masters/lyrics.chordpro(header stripped) +sources/hymnary.json+sources/video.json+derivatives/duration.json;catalog.jsononly fills ids, package paths,confidence,parentSongIdand writer bio/portrait — the reverse of before. Every package file is copied into the id-keyed folder keeping its folder; files inherited from a work land under the song's own package.commons-sync-catalog.tsuses the same reader (its S3 keys gain the subfolders).reset-commonsno longer mirrorswriters/(referenced portraits travel incopies). Newtools/manual/commons-prune-flat-mirror.tslists (dry run) and with--applydeletescommons/songs/,commons/works/,commons/writers/except the portraitsauthors.portraitUrlstill points at; selection is the puretools/commons-seed/prune.ts. Not run against S3 here.packagePath:ContentLibraryHelper.role/fileUrls/songJson/removePrefix(disk listing now walks subfolders),PublishHelper.approve/writeFile/readFile/fileSummary,publishHooks/song.ts(packageFields),publishHooks/index.ts(manifest),SubmissionHelper.recordFile/submit,SubmitValidation.resultingFileNames,CommonsAssetController(download by name accepts the package-relative name or the basename;files[].role),CommonsAdminController(declined-file live check,readText,score-missingroles),CommonsSongController.readText.Stacks on #151, #150 and #149 (branched from the local
integration/site-tasks= main + those three); merge after them.Test plan
yarn jest src/modules/commons— 23 suites / 289 tests green (SeedCatalog: copies land in subfolders, song row from song.json/lyrics.chordpro/hymnary.json/video.json, inherited work files under the song; ContentLibraryHelper: role by basename with folder prefix,packagePath,findByBase, fileUrls paths, recursive prefix removal; PublishHelper: destination folders, thumb rename, supersede across folders, manifest at the root, non-song assets stay flat; SongPublishHook: masters paths, basename reads; PruneFlatMirror: selection logic; SubmissionHelper: replace/remove against a foldered live file)yarn tsc --noEmitclean;yarn eslint --fix src/modules/commons— 0 errors (3 pre-existing max-len warnings)src/found no hard-codedassets/song/path)Cross-repo
Depends on / pairs with: #151, #150, #149 (stacked). Site/B1Admin: none (roles unchanged).
Migration: none (no schema change;
reset-commonsreseeds the new layout,commons-prune-flat-mirror.ts --applyon prod after cut-over)