Commit Graph

1676 Commits

Author SHA1 Message Date
Paul Nothaft f4a1db8b6a Merge pull request #707 from andredlng/feat/photos-list-view
Add grid/list layout toggle to admin Photos tab
2026-07-01 09:00:31 +02:00
André Deuerling 2a81d992f1 🎨 Address review: persist-on-click + radiogroup toggle
- Persist the layout choice in the toggle click handlers instead of a
  useEffect, so simply opening the Photos tab no longer re-writes the
  value it just read from localStorage (review concern 1).
- Give the Grid/List toggle radiogroup/radio + aria-checked semantics
  so a screen reader announces them as one mutually-exclusive set
  (review concern 2).
- Add a test that mount performs no localStorage write.
2026-07-01 07:55:49 +02:00
Paul Nothaft 22b20e8314 Merge pull request #711 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.76.2-beta.0
v3.76.2-beta.0
2026-06-30 22:57:16 +02:00
github-actions[bot] 79924e10d9 chore(main): release 3.76.2-beta.0 2026-06-30 20:52:33 +00:00
Paul Nothaft 3feed0fae6 Merge pull request #710 from PicPeak/fix/whatsnew-workflow-no-checkout
fix(ci): whatsnew highlights — set GH_REPO so gh runs without a checkout
2026-06-30 22:52:08 +02:00
Paul Nothaft 2a5f0a8601 fix(ci): whatsnew highlights — set GH_REPO so gh runs without a checkout
Release Please Beta on v3.76.1-beta.0 hard-failed at the very first
`gh release view "$TAG"` call:

  failed to run git: fatal: not a git repository
  (or any of the parent directories): .git

The reusable `whatsnew-highlights.yml` (PR #703) doesn't run
actions/checkout — so when `gh` tried to infer the target repo from
the runner's empty workspace it errored out. The first time it ran
against an actual release (#709 → 3.76.1-beta.0), the whole job died
before the deterministic-fallback path could save it.

Two changes, both single-line:

1. `env.GH_REPO: ${{ github.repository }}` at job scope. `gh` honours
   this and won't fall back to parsing `.git/config`, so no checkout
   is needed (the workflow only calls the GitHub API, never reads
   repo files).

2. `continue-on-error: true` on the "Extract Features" step. The
   file's comments say "never let highlights break a release", but
   the original wiring only soft-failed the AI + inject steps. A
   transient API hiccup at extract still hard-failed the whole job —
   defeating the design intent. Match the comment.

Why not just add actions/checkout? It would work, but pulls the whole
repo over the wire on every release just for `gh` to read its own
config. GH_REPO is the lighter idiom.

Net impact today: v3.76.1-beta.0 shipped without the `<!-- whatsnew -->`
block; the app's parseWhatsNew() already falls back to the raw Features
list so the admin "What's New" banner still works. The next beta release
will pick up the polished version.
2026-06-30 22:47:10 +02:00
Paul Nothaft 152952877f Merge pull request #709 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.76.1-beta.0
v3.76.1-beta.0
2026-06-30 22:42:09 +02:00
github-actions[bot] 753e680b1c chore(main): release 3.76.1-beta.0 2026-06-30 20:33:22 +00:00
Paul Nothaft 0b74b51f40 Merge pull request #703 from Luca-Timo/ci/whatsnew-highlights-workflow
ci(whatsnew): generate release highlights via GitHub Models
2026-06-30 22:33:00 +02:00
André Deuerling 46ce59d82e Add grid/list layout toggle to admin Photos tab
The event detail Photos tab (AdminPhotoGrid) only offered a thumbnail
grid. Add a Grid/List toggle in the action bar so admins can scan
photos in a compact, metadata-oriented list.

- New utils/photoViewPrefs.ts persists the choice per admin via
  localStorage (mirrors utils/calendarPrefs.ts), defaulting to grid
- List view is a compact <table> following the established admin
  list pattern (EventsListPage), with responsive column hiding:
  Photo (thumbnail + filename + original + Video/Hidden badges),
  Category (lg+), Uploaded date (md+, via useLocalizedDate),
  Engagement views/downloads/likes (xl+), Feedback rating/comments
  (sm+), Size, and hover Actions (download, delete)
- Rows reuse the existing selection, download, delete and category
  handlers; row click opens the photo viewer
- Toggle buttons use LayoutGrid / List icons with aria-pressed state
- Add en.json + de.json keys under admin.photos (viewMode, gridView,
  listView, columns.*)
- Tests for the persistence util and the toggle's render + persistence
2026-06-30 19:17:26 +02:00
Luca 5582644dc4 fix(whatsnew): decode HTML entities and trim em-dash detail in fallback bullets
The Features-fallback showed raw changelog text, so a commit subject like
'branded URL shortener — /s/<slug> with OG injection' surfaced two problems
in the admin banner:
- release-please escapes <slug> to &lt;slug&gt;; React renders the literal
  entity, so the banner read '/s/&lt;slug&gt;'. Decode the entities
  (&lt; &gt; &amp; &quot; &#39;), &amp; last to avoid double-decoding.
- the technical tail leaked into a user-facing highlight. Drop a trailing
  '— detail' clause (em dash only, so 'mark-paid' is untouched) so the bullet
  reads as the headline 'branded URL shortener'.

Only affects the deterministic fallback; curated <!-- whatsnew --> blocks are
unchanged.
2026-06-30 18:39:21 +02:00
Luca d25178d2e5 ci(whatsnew): let the Models step fail soft so the fallback runs without Models
If GitHub Models is disabled for the org the ai-inference step errors;
without continue-on-error the job would go red and skip the inject+fallback.
Mark it continue-on-error so an unavailable Models cleanly degrades to the
deterministic bullets — the feature now works with Models off, not just on.
2026-06-30 17:28:31 +02:00
Paul Nothaft 627c655a4d Merge pull request #704 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.76.0-beta.0
v3.76.0-beta.0
2026-06-30 17:12:31 +02:00
github-actions[bot] 6439cf2e42 chore(main): release 3.76.0-beta.0 2026-06-30 15:02:34 +00:00
Paul Nothaft a0f7033ffc Merge pull request #702 from PicPeak/feat/branded-short-urls-699
feat(gallery): branded URL shortener — /s/<slug> with OG injection (#699)
2026-06-30 17:01:16 +02:00
Paul Nothaft 3ac88370d6 Merge pull request #701 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.75.1-beta.0
v3.75.1-beta.0
2026-06-30 17:00:10 +02:00
Luca 5aeb6905ac ci(whatsnew): generate release highlights via GitHub Models
Activate the What's New highlights step that condenses each release's
Features into <=8 short bullets and injects a <!-- whatsnew --> block the
app reads (utils/whatsNew.parseWhatsNew), with a deterministic fallback.

Runs as a needs: job inside the release-please workflows rather than on a
standalone release: published trigger, because release-please creates the
release with GITHUB_TOKEN and GitHub never starts new workflow runs from
token-generated events -- a standalone trigger would never fire. Shared as
a reusable workflow_call so the stable and beta channels stay in sync.

Best-effort: continue-on-error + fallback mean it can never break a release.
Requires GitHub Models enabled for the org; until then the fallback is used.
2026-06-30 16:43:54 +02:00
Paul Nothaft 56c2386c90 feat(gallery): branded URL shortener — /s/<slug> with OG injection (#699)
Issue 3 from #699 (@alexvaltchev's report): expose a custom-named short
URL per event that bots scrape for OG previews and browsers redirect to
the underlying gallery. WhatsApp / iMessage / Facebook cache the OG
metadata by the URL they crawl, so the SHORT URL becomes the cache key
— admins can rotate or split-test underlying gallery URLs without
re-pushing a fresh link to clients.

Additive feature; no existing route, table, or column is modified.

## Backend

- `gallery_short_urls` table (migration 150): id, short_slug UNIQUE,
  event_id FK CASCADE, target_path TEXT, created_by/at, hit_count,
  last_hit_at, deleted_at/by. hasTable-guarded so the migration is
  idempotent on re-run.

- `src/services/galleryShortUrlService.js` — validator + CRUD +
  resolver. Slug rules: `/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/`,
  reserved blocklist (admin, api, auth, gallery, og, s, login, ...).
  target_path snapshots at create-time from the event + global
  short-URL toggle, so a later flip of the toggle does NOT silently
  change where existing short URLs resolve.

- `src/routes/adminShortUrls.js` — `GET/POST
  /api/admin/events/:eventId/short-urls`, `DELETE
  /api/admin/short-urls/:id`. Structured errors: 400 INVALID_SLUG,
  409 SLUG_TAKEN (with `suggested`), 404 EVENT_NOT_FOUND. Gated by
  events.view / events.edit + requireEventOwnership.

- `server.js` /s/:shortSlug public route. Bot UA → server-render the
  same OG metadata the existing /og/gallery/<slug> handler produces,
  then override og:url to point at /s/<shortSlug> itself (cache-key
  invariant — social platforms key by the URL they scrape).
  Browser UA → 302 to target_path. Soft-deleted slug → 410 Gone
  (intentional-delete signal, distinct from 404 unknown slug).
  Hit accounting is fire-and-forget.

## Frontend

- `services/shortUrls.service.ts` — list/create/remove.
- `components/admin/ShortUrlsCard.tsx` — per-event card on the
  EventDetailsPage. Form for custom or auto-generated slug, list with
  copy-to-clipboard + soft-delete. SLUG_TAKEN error surfaces the
  service's `suggested` slug with a "use suggested" button.
- i18n: events.shortUrls.* added to EN + DE.

## Tests

78 new tests, all passing:

- `__tests__/utils/galleryShortUrlValidation.test.js` (48) — pure-
  function tests for validateSlug: accepts/rejects, reserved-slug
  blocklist, path-traversal + URL-injection vectors.
- `__tests__/integration/galleryShortUrls.test.js` (19) — service
  layer against a real SQLite DB. Covers custom + auto-generated
  slugs, collision + SLUG_TAKEN + suggested, target_path
  snapshotting (backward-compat invariant), soft-delete + slug
  rotation, hit counting.
- `__tests__/integration/galleryShortUrlRoute.test.js` (11) —
  HTTP-level: 302 redirect for browser UA, 200 + OG HTML for bot UA,
  og:url canonical points at /s/<slug>, 410 for soft-deleted +
  orphaned events, 404 unknown + malformed.

Regression sweep: 47 existing migration-chain integration tests still
pass; migration 150 is additive only.

## Backward compatibility

- Existing `/gallery/<slug>`, `/gallery/<32-hex-share-token>`,
  `/gallery/<slug>/show/<token>`, `/og/gallery/<slug>`,
  `/og/gallery/<slug>/cover` routes are untouched.
- The `/s/` namespace is new; no existing route lives there.
- Migration 150 only ADDs the new table — no ALTERs on existing
  schema, no destructive changes.
- target_path is snapshotted at create-time so flipping the global
  "Use short gallery URLs" setting after a short URL exists does NOT
  change where that short URL resolves.
2026-06-30 16:30:13 +02:00
github-actions[bot] 541b3d32ef chore(main): release 3.75.1-beta.0 2026-06-30 14:09:58 +00:00
Paul Nothaft 25bf7bb523 Merge pull request #700 from PicPeak/fix/og-injection-share-token-and-slideshow-699
fix(og): rich social previews for share-token + slideshow URLs (#699)
2026-06-30 16:09:31 +02:00
Paul Nothaft 1b8747dc82 fix(og): rich social previews for share-token + slideshow URLs (#699)
Two SSR-OG injection bugs reported by @alexvaltchev. Both made his link
previews fall back to the brand logo + site-wide tagline instead of the
event-specific name/photo, even though the bot UA was hitting our
already-existing OG handler. He compensated with a Cloudflare Worker as
SSR middleware — which then created bug 3 below (og:image at the
auth-gated /api/.../hero/ path, not the public /og/.../cover one), so
Instagram never rendered the image either.

## Bug A — slideshow URLs miss the OG handler entirely

`/gallery/<slug>/show/<token>` has 3 segments after `/gallery/`. The OG
route was wired only at `/gallery/:slug/:token?` (1-2 segments), so
slideshow links fell through to the SPA-catchall `/gallery/*` and never
invoked the OG handler at all. Added a second route handler for the
3-segment slideshow shape, sharing the same intercept middleware so a
recognised social crawler still gets the rich preview.

## Bug B — share-token-only URLs resolve to nothing

`/gallery/<32-char-share-token>` (the form produced when migration 525's
short-URLs option strips the event slug) routes to the OG handler with
`slug=<token>`. resolveSlug then queries `events.slug = <token>`, which
never matches because the token is in a separate `share_token` column.
Result: falls through to the "no event found" branch and serves the
generic site-wide OG.

Fix: when the slug shape matches a 32-char hex AND the slug lookup
missed AND no redirect rule applies, try `events.share_token = slug` as
a final fallback. Real slugs are kebab/dot/underscore mixes, never pure
32-hex, so the extra DB roundtrip is gated to only fire for the
token-shaped URL.

## Tests

3 new tests in galleryOgService.shareImage.test.js using non-entropy
32-hex fixtures (deliberately zero-padded to avoid tripping
GitGuardian's Generic High Entropy Secret detector while still
matching the route's /^[a-f0-9]{32}$/i shape check):
- share-token slug resolves via the share_token column (alex's case)
- malformed/expired 32-hex token returns the site-wide fallback (no leak)
- non-hex slugs skip the share_token query entirely (hot-path cost guarded)

All 14 tests in the file pass.

## Out of scope here (separate follow-up)

- Issue 2 (Instagram og:image) — alex-side CF Worker bug pointing
  og:image at /api/gallery/<slug>/hero/<id>, which requires gallery
  auth. PicPeak already has the right unauthenticated path
  (/og/gallery/<slug>/cover) gated by events.og_image_share_enabled
  per-event opt-in (#474). Documented in the issue reply.
- Issue 3 (URL shortener with custom names) — real feature request,
  meaningfully different from the existing #525 short-URLs option that
  just strips the slug. Designing separately.
2026-06-30 16:03:49 +02:00
Luca de789faec5 Merge pull request #698 from Luca-Timo/docs/comparison-pixieset 2026-06-30 12:35:29 +02:00
Paul Nothaft 52dfe2723d Merge pull request #697 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.74.1-beta.0
v3.75.0-beta.0
2026-06-30 11:44:10 +02:00
github-actions[bot] 5925bed761 chore(main): release 3.75.0-beta.0 2026-06-30 09:44:00 +00:00
Paul Nothaft a1a73bf75f Merge pull request #694 from Luca-Timo/feat/whatsnew-notifications
feat(updates): "What's New" highlights after update + pre-update teaser
2026-06-30 11:43:39 +02:00
Luca 3528f6b8b7 Merge pull request #696 from Luca-Timo/docs/contributor-luap
docs(readme): credit @the-luap as creator/lead maintainer

Docs only no change in Codebase
2026-06-30 11:23:02 +02:00
Luca b0439638bd docs(readme): clarify comparison footnotes — $0 cost caveat + Pixieset video cap
- you still bring your own server (own hardware or VPS) and optional domain.
- Pixieset "unlimited" storage is photos only — video is capped per plan (~0–10 h depending on tier).
- Renumber the PicPeak storage footnote (* → **) so the three markers don't collide.
2026-06-30 11:20:37 +02:00
Luca 721f440fa6 docs(readme): add Pixieset to comparison + customer-accounts/CRM/accounting rows 2026-06-30 11:07:58 +02:00
Luca 748238e8ca docs(readme): credit @the-luap as creator/lead maintainer 2026-06-30 11:06:04 +02:00
Paul Nothaft 0191b283d7 Merge pull request #692 from PicPeak/chore/security-bumps-aug-2026-batch
chore(security): close all 27 code-scanning alerts via dep + base-image bumps
2026-06-30 10:58:09 +02:00
Paul Nothaft e48b81fb8d Merge pull request #693 from PicPeak/chore/workflow-required-checks-always-fire
ci: required-check workflows fire on every PR (drop paths filter + post-rename branch list)
2026-06-30 10:53:25 +02:00
Luca 500cf8522e feat(updates): "What's New" highlights after update + pre-update teaser
Surfaces release highlights to admins, sourced from the GitHub release notes
(no AI at runtime). Bullets are written once per release in CI via GitHub Models
(see docs/ci/whatsnew-highlights.yml) into a <!-- whatsnew --> block; the app
reads that block and falls back to the changelog's "### Features" for releases
without it — so it works against today's releases immediately.

- backend utils/whatsNew.parseWhatsNew(body): curated block else Features
  section, strips scope/PR-links, de-dups, caps at 8 (tested).
- GET  /admin/system/updates/whatsnew: highlights for every version moved
  through since the per-instance marker (whatsnew_last_seen_version); fresh
  installs self-anchor silently. Best-effort, never errors.
- POST /admin/system/updates/whatsnew/seen: advance the marker (per-instance).
- /admin/system/updates also returns latestHighlights for the teaser.
- Frontend: WhatsNewBanner (green bar -> modal with "Full changelog" link) on
  the dashboard via adminService; UpdateNotification shows a "New features
  include:" teaser. i18n de/en. No migration (uses app_settings).
2026-06-30 02:31:13 +02:00
Paul Nothaft a40ab6a9b1 ci: required-check workflows now fire on every PR (no paths filter)
Branch protection on `main` + `stable` lists `upgrade-from-bootstrap`
and `fresh-install` as REQUIRED checks. The producing workflows had
`paths:` filters in their `pull_request` triggers, so they correctly
skipped on PRs that didn't touch migrations / package.json. But a
skipped workflow doesn't satisfy a required check — it leaves the
status "missing", which blocks merge on every unrelated PR.

Concretely surfaced on PR #692 (security bumps): all 12 visible checks
were green, but the merge button was blocked because the two
path-filtered workflows skipped and their required-check names never
reported.

This PR drops the `paths:` filter from both workflows so they always
fire on PRs against `main` + `stable`. Costs:
- `schema-drift` (`upgrade-from-bootstrap`): ~75 s per PR (Postgres
  service boot + migrate:safe run + schema assertion).
- `install-smoke` (`fresh-install`): ~2 min per PR (full Docker
  Compose boot + login).

Both are buying unconditional safety nets on the install + migration
paths, which is what the required-check gate is supposed to model.

Also fixes the trigger branch list while in the file: `[main, beta]`
→ `[main, stable]`, completing the post-#669 rename for these two
workflows that were missed in PR #686.

## What this does NOT fix

`GitGuardian Security Checks` is the third required check that's
currently missing on PRs — but that's a separate problem. The
GitGuardian GitHub App was installed at the user-account level
(`the-luap`) before the org transfer and didn't move with the repo.
Re-installing it on the org via the GitHub Marketplace is a UI step
the maintainer needs to do; can't be done via API.
2026-06-30 00:07:44 +02:00
Paul Nothaft 7546f104a3 chore(security): close 27 code-scanning alerts via dep + base-image bumps
Single PR closing every open code-scanning alert at
https://github.com/PicPeak/picpeak/security/code-scanning. Both repos go
from 27 open alerts → 0 across direct deps, transitive deps, and build-
time bundled deps.

## Backend (`backend/package.json` + overrides)

Direct dep bumps:
- axios          1.15.2   → 1.16.0       (closes 9 alerts: 7 high + 1 med + 1 low)
- nodemailer     8.0.10   → ^9.0.1       (closes 1 high — SSRF + file-read via raw option)
- multer         2.1.1    → 2.2.0        (closes 2 alerts: 1 high + 1 med)
- form-data      4.0.5    → 4.0.6        (closes 1 high)
- tar            ≥7.5.13  → ≥7.5.16      (closes 1 med)
- postcss        8.5.6    → 8.5.10       (closes 1 med)
- i18next-http-backend  3.0.2  → 3.0.5   (closes 1 med — backend lagged frontend)
- js-yaml        4.1.1    → ^4.2.0       (closes 1 med)
- joi            17.13.3  → ^17.13.4     (closes 1 med)

Overrides updated to match deps (npm rejected the install otherwise) +
nodemailer ^9.0.1 added as override so imapflow + mailparser transitive
bundling of older nodemailer is also fixed. Babel devDep auto-bumped via
`npm audit fix` (low-severity arbitrary file read).

Backend npm audit: 0 vulnerabilities.

## Frontend (`frontend/package.json`)

Direct dep bumps:
- axios                 1.15.2  → 1.16.0
- postcss               8.5.6   → 8.5.10
- i18next-http-backend  3.0.5   → 3.0.5  (already current — kept for parity)

`npm audit fix` swept up 12 transitive issues at the same time:
- vitest (1 critical — file read on UI server)
- vite (2 high — fs.deny bypass, NTLM hash via launch-editor)
- ws (2 high — uninitialized memory + DoS)
- dompurify (8 mod — multiple IN_PLACE / hook-pollution XSS vectors)
- react-router-dom + react-router (1 mod transitive)
- esbuild (1 mod — dev server file read)
- @babel/core (1 low)

Frontend npm audit: 0 vulnerabilities.

## Frontend Dockerfile

- Build stage: `node:20-alpine` → `node:22-alpine`

Closes the npm-bundled CVE class (picomatch, ip-address, brace-expansion,
@sigstore/core, tar) that came from Node 20's older bundled npm. Matches
the backend Dockerfile base. The nginx serving stage stays at
`nginx:1.28-alpine` — that tag is rolling, so the next build picks up
the fixed 1.28.3-r4 layer that closes the 4 nginx CVEs.

## Verification

- Backend: `npm audit` → 0 vulnerabilities 
- Frontend: `npm audit` → 0 vulnerabilities 
- Backend Jest (workflow engine, rounding, WhatsApp): 47/47 pass 
- Frontend Vitest: 84/84 pass 
- `frontend npm run build`: succeeds 
- nodemailer 9 sanity check: our usage is `createTransport({host,port,secure,auth})`
  + `sendMail({from,to,subject,html,text})` — we don't touch the `raw`
  option that 9.x tightened, so the major bump is API-compatible.
2026-06-29 23:26:32 +02:00
Paul Nothaft a24821de55 Merge pull request #691 from PicPeak/chore/bypass-size-gate
ci: bypass size gate — cap self-merge PR size for bypass users
2026-06-29 23:15:45 +02:00
Paul Nothaft 806b1ac921 ci: bypass size gate — cap self-merge PR size for review-bypass users
@Luca-Timo is on main's review-bypass list so he can self-merge small
bugfixes without waiting for a maintainer review. The bypass list alone
is binary (he can merge anything), so this adds a complementary required
status check that fails when a bypass user's PR exceeds a configured
line-count threshold — blocking merge for genuine features while leaving
small bugfixes flowing.

How it works:
- Trigger: pull_request_target (so the workflow runs in the base repo's
  context with permissions to write a check status — script never
  executes PR code, so fork-PR-attack-safe).
- For PRs authored by a bypass user (default: @Luca-Timo):
    - linesChanged = additions + deletions
    - If ≤ LINE_LIMIT (300): check = success → bypass works → self-merge OK
    - If > LINE_LIMIT: check = failure → required-check gate blocks merge
      regardless of bypass; needs a maintainer review.
- For everyone else: check = success ("not applicable"). They go through
  the normal review path and are unaffected.

Both constants (LINE_LIMIT, BYPASS_USERS) are at the top of the workflow
for easy tuning.

After this lands on main, a separate API step adds 'bypass-size-gate' to
the main branch's required_status_checks list so the gate is actually
enforced. Until that's in place the check runs but doesn't block.
2026-06-29 23:13:09 +02:00
Paul Nothaft beae46e408 Merge pull request #689 from PicPeak/release-please--branches--main
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(main): release 3.74.0-beta.0
v3.74.0-beta.0
2026-06-29 22:44:39 +02:00
github-actions[bot] 5ba45753c1 chore(main): release 3.74.0-beta.0 2026-06-29 20:39:22 +00:00
Paul Nothaft 14bd3e1a6c Merge pull request #688 from PicPeak/chore/readme-migration-banner
docs: prominent migration banner at the top of README
2026-06-29 22:29:09 +02:00
Paul Nothaft 5839bba72a docs: prominent migration banner at the top of README (#669)
GitHub-flavored `> [!IMPORTANT]` callout right below the title, before
the badges/hero block, so it's the first thing a visitor or repo browser
sees in the rendered README. Mirrors the in-app banner (#687) so an
operator gets the same message whether they're browsing the repo or
logged into the admin dashboard.

Body covers:
- Image-path change with the literal new path
- Branch rename (beta → main, main → stable) with auto-redirect note
- Link to docs/migration-to-org.md for the exact compose-file edit

Remove (or downgrade to a regular note) after the migration window
settles, same lifecycle as the in-app banner constant.
2026-06-29 22:24:56 +02:00
Paul Nothaft 02133478bd Merge pull request #687 from PicPeak/chore/in-app-migration-banner
feat(admin): in-app migration banner for the org rename
2026-06-29 22:22:52 +02:00
Paul Nothaft 297c2d3df0 Merge pull request #686 from PicPeak/chore/post-rename-workflow-triggers
chore: workflows + RELEASING.md for the post-rename branch model
2026-06-29 22:21:56 +02:00
Paul Nothaft 166ef47611 Merge pull request #685 from PicPeak/chore/post-org-move-docs-and-contributing
docs: branch model + migration-to-org guide + PR template
2026-06-29 22:21:24 +02:00
Paul Nothaft 2a4bf3b868 feat(admin): in-app migration banner for the org rename (#669)
One-time banner shown at the top of the admin layout to surface the org
rename + GHCR registry change for operators who haven't read the release
notes. Sits right below the existing maintenance banner — same pattern.

## What it looks like

Blue, dismissible banner with a short body:

> PicPeak's image registry has moved
> Update your docker-compose.yml to pull from
> ghcr.io/picpeak/picpeak/{backend,frontend} — the old path is no longer
> being updated. [See migration notes]

The link goes to `docs/migration-to-org.md` on the new org repo.

## Design choices

- **No backend feature flag.** A hard-coded `MIGRATION_BANNER_ENABLED`
  constant in the component file (1 line) gates global display. After
  ~1 quarter, flip it to false (or drop the mount in `AdminLayout.tsx`)
  in a small follow-up PR. A backend `app_settings` row + Settings UI
  toggle would be overkill for a one-time migration event.

- **Per-admin dismissal via localStorage.** Key is `picpeak:migration-banner:v1`
  (versioned so a future "we've moved AGAIN" banner can show without
  inheriting earlier dismissal). Wrapped in try/catch so private-mode
  browsers + storage-quota-exceeded errors don't crash the layout.

- **EN + DE strings** under a new top-level `migrationBanner` namespace.
  Other locales (fr, nl, pt, ru) fall through to EN — `migrationBanner.*`
  keys aren't translated there yet, deliberate (per #669 the ops
  banner is operator-facing and admins reading EN/DE is the majority).

- **Reuses `common.dismiss`** for the close-button aria-label.

## Test plan

- [ ] Frontend `npm run build:check` passes (TS + build)
- [ ] Open admin dashboard in EN → banner shows at top, below header,
      above main content
- [ ] Switch to DE → banner shows German strings
- [ ] Click dismiss → banner hides, doesn't re-appear on hard refresh
- [ ] Clear localStorage `picpeak:migration-banner:v1` → banner returns
- [ ] Flip `MIGRATION_BANNER_ENABLED` to false → banner doesn't render
      for anyone, regardless of dismissal state

Refs #669.
2026-06-29 22:19:22 +02:00
Paul Nothaft 3ca6378bd7 chore: workflows + RELEASING.md for the post-rename branch model
After the org move + branch rename (#669):
  beta  → main   (active development)
  main  → stable (curated release channel)

This PR rewires the workflows that referenced the old branch names so
release-please and the Docker build target the right channels.

## Workflow changes

### `.github/workflows/docker-build.yml`

- **Push triggers**: `[main, beta]` → `[main, stable]` (both `push.branches`
  and `pull_request.branches`). `beta` no longer exists; `stable` is the
  curated channel that should also produce builds.
- **`is_prerelease` detection**: pre-release context was decided by
  `refs/heads/beta`; now decided by `refs/heads/main` (active dev →
  prerelease, `-beta.N` version suffix unchanged).
- **`:latest` + `:stable` tagging**: were gated on `{{is_default_branch}}`
  (which used to be `main` = stable channel). Default branch is now `main`
  = active dev, so the implicit gate would have aliased `:latest` to dev.
  Both tags now explicitly gate on `refs/heads/stable` OR a non-prerelease
  release tag.
- **`:beta` tag**: REMOVED. Active-dev pulls are `:main` (auto-generated
  by `type=ref,event=branch`). The pre-rename `:beta` tag remains frozen
  at its last build under Option B / #669 — operators are expected to
  update to `:main` or pin to a versioned tag.

### `.github/workflows/release-please.yml`

- `branches: [main]` → `branches: [stable]`. This is the **stable**
  release-please workflow (uses `release-please-config.json`); after the
  rename, the stable channel lives on the `stable` branch.

### `.github/workflows/release-please-beta.yml`

- `branches: [beta]` → `branches: [main]`.
- `target-branch: beta` → `target-branch: main`.
- This is the **pre-release** release-please workflow (uses
  `release-please-config-beta.json`, `prerelease: true`); after the rename,
  pre-releases are cut from the new `main` (active dev). The version-suffix
  scheme stays `-beta.N` so existing operator pins keep working.

## RELEASING.md

Rewrote the TL;DR, "How a stable release is cut", and hotfix path to
reference the new branch names. Added a one-line "branch model background"
note pointing at #669 so future maintainers know why `main` means active
dev (the opposite of what some projects use). Filename conventions:
`release/X.Y.Z-merge-from-main` (was `…-from-beta`); promotion PR title
`promote main → stable as vX.Y.Z` (was `promote beta → main`).

## Why combined with PR A's content as a single PR

Originally planned as two PRs (B = workflow triggers, C = release-please
reconfigure). Splitting wasn't worth it: the configs are branch-agnostic
(`release-please-config.json` and `release-please-config-beta.json` don't
mention branch names internally), and not bundling them meant a window
where the stable release-please workflow would fire on pushes to the new
`main` (active dev) — exactly the wrong place. Single PR closes that gap.

## Versioning scheme — kept

No version-scheme decision needed. The `-beta.N` suffix on pre-release
versions is preserved (existing operator pins like `v3.71.3-beta.0` keep
working). If a `v4.0.0-pre.N`-style reset is desired later, that's a
separate PR with explicit operator-comms attached.
2026-06-29 22:15:31 +02:00
Paul Nothaft d606fcd5a4 docs: branch model + migration-to-org guide + PR-template target hint
Operator + contributor docs for the post-org-move world. None of these
files reference the legacy branch names (`beta` / old `main` meaning) —
they describe the new shape (`main` = active dev, `stable` = curated
release channel), so they're correct from the moment the rename happens.

Three additions/edits:

1. `docs/migration-to-org.md` (new) — operator-facing one-pager that the
   in-app migration banner + the OLD GHCR package URLs (now 404) can
   point at. Walks through the single `docker-compose.yml` edit needed.

2. `CONTRIBUTING.md` — new "Branch model" section explaining which
   branch to target (`main` for features + most fixes; `stable` only
   for small, surgical bugfix backports). Updates the "fork from beta"
   step to "fork from main". Updates the release-process paragraph to
   describe the two-channel model instead of the old beta→main promote.

3. `.github/PULL_REQUEST_TEMPLATE.md` — adds a target-branch hint at
   the top of the template (HTML comment so it shows during PR
   composition but doesn't render in the merged PR body).
2026-06-29 22:05:56 +02:00
Paul Nothaft 448da95020 Merge pull request #684 from PicPeak/chore/migrate-image-registry-to-picpeak-org
chore: migrate Docker registry + GitHub URLs to PicPeak org
2026-06-29 20:25:15 +02:00
Paul Nothaft 0205c7dcce chore: migrate Docker registry + GitHub URLs to PicPeak org
Repo transferred from the-luap/picpeak → PicPeak/picpeak. Docker images
publish to ghcr.io/picpeak/picpeak/{backend,frontend} (lowercase, per the
GHCR canonical form computed by docker-build.yml's `${GITHUB_REPOSITORY,,}`).

Sweep covers:
- docker-compose.production.yml + Dockerfiles → new image registry path
- README, CONTRIBUTING, SECURITY, SIMPLE_SETUP, scripts/picpeak-setup.sh
  → new GitHub URLs
- Update-check / release-notes services (updateCheckService,
  environmentService, updateNotificationService, adminSystem,
  UpdateNotification, githubReleaseUrl) → GitHub API + tag URLs use the
  canonical PicPeak/picpeak path
- Issue templates + README-DOCKER + workflow README → updated package URLs
- One commit-context comment in migrations/090 + customerAccountsService

CHANGELOG.md is intentionally untouched (historical release entries are
immutable; GitHub auto-redirects the old URLs indefinitely).
CLAUDE.md keeps the bare `(the-luap)` reference — that's the maintainer's
personal handle, not a repo URL.

22 files, 48/48 line swaps (every change is a 1:1 URL replacement).
2026-06-29 20:20:13 +02:00
Paul Nothaft 5ebe126970 Merge pull request #683 from the-luap/release-please--branches--beta
Build and Push Docker Images / build-backend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-backend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-backend (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/amd64, ubuntu-latest) (push) Has been cancelled
Build and Push Docker Images / build-frontend (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build and Push Docker Images / merge-frontend (push) Has been cancelled
Build and Push Docker Images / summary (push) Has been cancelled
chore(beta): release 3.73.0-beta.0
v3.73.0-beta.0
2026-06-29 20:00:07 +02:00
github-actions[bot] 578a73f352 chore(beta): release 3.73.0-beta.0 2026-06-29 17:57:55 +00:00