Skip to content

feat: add custom collection poster support#2784

Merged
enoch85 merged 7 commits into
developmentfrom
feat/collection-poster
Apr 28, 2026
Merged

feat: add custom collection poster support#2784
enoch85 merged 7 commits into
developmentfrom
feat/collection-poster

Conversation

@enoch85

@enoch85 enoch85 commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add custom poster upload support for Maintainerr-managed collections.

What Changed

  • add collection poster upload, fetch, and clear endpoints plus local JPEG storage
  • push collection posters through the media-server abstraction for Plex and Jellyfin
  • restore stored posters when collections are recreated and clean them up on deletion or full media-server wipes
  • add the rule-group modal poster picker UI, shared upload response contract, and focused tests/docs

Closes #2782

@enoch85

enoch85 commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2784 🚀

enoch85 added 3 commits April 27, 2026 17:56
…s at 500 KB

- Push stored poster when addToCollectionInternal first creates the
  media-server collection, so brand-new rules apply the user's poster
- Lower upload limit from 10 MB to 500 KB via shared contracts constants
- Style the Clear button as buttonType=danger so it reads as a button
@enoch85

enoch85 commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@enoch85

enoch85 commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator Author
  1. Poster on a brand-new rule
    Create a new rule, save it.
    Re-open it, upload a poster, close the modal.
    Trigger the rule so the collection is created in Plex.
    Expected: poster shows up in Plex automatically.

  2. File size limit
    Try uploading a poster larger than 500 KB.
    Expected: inline error, no upload, no server crash.
    Try one under 500 KB → uploads fine.

  3. Clear button
    Upload a poster, then look at the picker.
    Expected: "Clear" is now a red button, not plain text.

  4. EPIPE crash
    Repeat your earlier 2.4 MB upload test.
    Expected: rejected client-side at 500 KB; no server crash.

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2784 🚀

enoch85 added 2 commits April 28, 2026 17:29
…er right

- DELETE /poster now also calls refreshItemMetadata via the media-server
  abstraction; response carries refreshRequested so callers can adapt
- Picker shows a softened best-effort message when a refresh is requested
- Move the poster section into the right column of the rule-group modal
  to balance whitespace
- Docs and Swagger describe the new contract and the no-guarantee semantics
@enoch85 enoch85 merged commit 52adf1c into development Apr 28, 2026
13 checks passed
@enoch85 enoch85 deleted the feat/collection-poster branch April 28, 2026 15:52
@enoch85 enoch85 added this to the 3.9.0 milestone Apr 28, 2026 — with GitHub Codespaces
maintainerr-automation Bot added a commit that referenced this pull request Apr 28, 2026
* build(deps): bump the nestjs group with 2 updates (#2785)

Bumps the nestjs group with 2 updates: [@nestjs/event-emitter](https://github.com/nestjs/event-emitter) and [@nestjs/swagger](https://github.com/nestjs/swagger).


Updates `@nestjs/event-emitter` from 3.0.1 to 3.1.0
- [Commits](nestjs/event-emitter@3.0.1...3.1.0)

Updates `@nestjs/swagger` from 11.4.1 to 11.4.2
- [Release notes](https://github.com/nestjs/swagger/releases)
- [Commits](nestjs/swagger@11.4.1...11.4.2)

---
updated-dependencies:
- dependency-name: "@nestjs/event-emitter"
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nestjs
- dependency-name: "@nestjs/swagger"
  dependency-version: 11.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump nodemailer from 8.0.6 to 8.0.7 (#2786)

Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.6 to 8.0.7.
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](nodemailer/nodemailer@v8.0.6...v8.0.7)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @typescript-eslint/parser from 8.58.2 to 8.59.1 (#2787)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.58.2 to 8.59.1.
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump prettier-plugin-tailwindcss from 0.7.3 to 0.8.0 (#2790)

Bumps [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) from 0.7.3 to 0.8.0.
- [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md)
- [Commits](tailwindlabs/prettier-plugin-tailwindcss@v0.7.3...v0.8.0)

---
updated-dependencies:
- dependency-name: prettier-plugin-tailwindcss
  dependency-version: 0.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump react-hook-form from 7.72.1 to 7.74.0 (#2793)

Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.72.1 to 7.74.0.
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](react-hook-form/react-hook-form@v7.72.1...v7.74.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.74.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump jsdom from 29.0.2 to 29.1.0 (#2794)

Bumps [jsdom](https://github.com/jsdom/jsdom) from 29.0.2 to 29.1.0.
- [Commits](jsdom/jsdom@v29.0.2...v29.1.0)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump typescript-eslint from 8.59.0 to 8.59.1 (#2791)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.59.0 to 8.59.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.59.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.59.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @tanstack/eslint-plugin-query (#2795)

Bumps [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) from 5.99.0 to 5.100.5.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/eslint-plugin-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/eslint-plugin-query@5.100.5/packages/eslint-plugin-query)

---
updated-dependencies:
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.100.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump actions/github-script from 8 to 9 (#2788)

Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Commits](actions/github-script@v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>

* build(deps): bump peter-evans/find-comment from 3 to 4 (#2789)

Bumps [peter-evans/find-comment](https://github.com/peter-evans/find-comment) from 3 to 4.
- [Commits](peter-evans/find-comment@v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/find-comment
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>

* feat: add custom collection poster support (#2784)

* feat: add custom collection poster support

* style: format collection poster picker

* fix: tolerate poster cleanup failures on collection delete

* fix(collections): push poster on first rule-driven create, cap uploads at 500 KB

- Push stored poster when addToCollectionInternal first creates the
  media-server collection, so brand-new rules apply the user's poster
- Lower upload limit from 10 MB to 500 KB via shared contracts constants
- Style the Clear button as buttonType=danger so it reads as a button

* feat(collections): refresh server metadata on poster clear, move picker right

- DELETE /poster now also calls refreshItemMetadata via the media-server
  abstraction; response carries refreshRequested so callers can adapt
- Picker shows a softened best-effort message when a refresh is requested
- Move the poster section into the right column of the rule-group modal
  to balance whitespace
- Docs and Swagger describe the new contract and the no-guarantee semantics

* fix(rules,tasks): unstick rules-collections lock and clarify sw_watchers labels (#2801)

- ExecutionLockService.acquire() stored the chained promise instead of `current`,
  so the release callback's `locks.get(key) === current` check never matched
  and the map entry leaked. tryAcquire then returned null forever after the
  first scheduled run, breaking manual Trigger Now until restart. Store
  `current` directly; FIFO chaining is preserved by `await prior`.
- rule-executor-job-manager.executeJob now runs emitStatusUpdate inside the
  inner try/finally that owns release(), and emitStatusUpdate itself swallows
  listener throws at debug level so a misbehaving SSE client can't poison
  the executor.
- Sharpen sw_watchers humanName to "Users that watched at least one episode"
  and sw_allEpisodesSeenBy to "Users that watched every episode" across all
  three servers; add semantic comments in the getters pointing at the
  alternative property. No behaviour change for the watchers data.

Fixes #2798
Fixes #2799

* feat: cache jellyfin collections (#2800)

* Cache jellyfin collections to avoid excessive repeat queries

* Invalidate jellyfin collection caches on mutation

Drops cached entries when collections are created, deleted, updated,
or when items are added/removed, so reads within the TTL window can't
serve pre-mutation state. Also skips caching empty results to avoid
sticking a transient zero-collection response.

---------

Co-authored-by: enoch85 <mailto@danielhansson.nu>

* fix(logs): block path traversal in log file download endpoint

The safeLogFileRegex was unanchored, allowing any string containing a
maintainerr-YYYY-MM-DD.log substring to pass validation. Combined with
path.join, an attacker could read arbitrary files via URL-encoded
traversal segments (e.g. maintainerr-2026-01-01.log%2F..%2F..%2Fetc%2Fpasswd).

Anchor the regex and add a defense-in-depth canonical-path check that
rejects symlinks and verifies the resolved path stays inside the logs
directory.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>
Co-authored-by: Nathan Spencer <natekspencer@gmail.com>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.9.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow setting a custom collection poster image

1 participant