Commit Graph

1690 Commits

Author SHA1 Message Date
Luca b0912c7427 feat(setup): validate setup token at step 1 before advancing
Previously "Continue" on the token step only checked the field was
non-empty; a wrong token wasn't caught until the final submit, after the
user had filled in email + password. Add a non-burning verify:

- backend: POST /setup/verify-token constant-time compares the token
  without consuming it (createInitialAdmin still claims it atomically on
  submit), gated on no-admin-exists and rate-limited like /setup/admin.
- frontend: step-1 "Continue" calls verifyToken and only advances on a
  valid token; a wrong token shows the invalidToken error on the field,
  429 -> too-many-attempts, 409 -> redirect to login.

Adds integration tests for accept-without-burn / reject / closed-once-set.
2026-07-02 15:18:22 +02:00
Luca 3e69c5df3f fix(setup): match first-run logo size to the login page default
The header logo used a hardcoded 64px frame; the login page renders a
medium (200x150) frame via resolveLoginLogoClasses. Reuse that helper
with the default size so /setup and /admin/login read identically.
2026-07-02 15:03:56 +02:00
Luca d9b0eb7232 feat(setup): brand first-run screen and split into two-step wizard
Address post-merge UI feedback on the first-run setup screen — the first
screen any new admin sees:

- Use the bundled PicPeak logo (same asset the login page falls back to)
  on the cream brand plate instead of the generic lucide Sparkles icon.
- Split the flow into two steps: step 1 takes only the one-time setup
  token, with the `docker compose logs backend | grep -i "setup token"`
  recovery command shown prominently (with a copy button) directly under
  the field, plus a docs link for when the logs have rotated away; step 2
  collects email + password. A rejected token bounces back to step 1.

en/de strings added; other locales fall back to en.
2026-07-02 14:56:14 +02:00
Luca 286975dc52 fix(setup): address PR #714 review — password UX, script token, race, nits
Blockers:
- SetupPage now mirrors the server password rule (>=8 with upper/lower/digit) so
  a green client isn't bounced by the server; server errors carry a `field`
  (routes/setup.js) that the client maps to a translated key instead of
  rendering raw English. New i18n: setup.invalidToken, setup.passwordRequirements.
- picpeak-setup.sh: the ADMIN_CREDENTIALS.txt block no longer dead-ends on the
  wizard path — when no legacy admin was seeded it prints the one-time setup
  token (from data/SETUP_TOKEN / docker compose logs) and points at /setup.

Concern:
- createInitialAdmin creates the admin + burns the token in ONE transaction,
  atomically claiming the token (null-if-present, expect 1 row) so a
  double-submit can't create two super_admins. Cross-DB (whereNotNull, trx-only
  writes). Added a concurrency test.

Nits:
- SetupPage redirects to /login when /setup/status errors (no form flash on a
  configured instance).
- Dropped the unused DATABASE_URL from docker-compose.yml.
- Documented why secrets are chmod 644 (three different reader users).
2026-07-02 13:21:55 +02:00
Luca 415bffa04c feat: zero-config first run — in-browser admin bootstrap + auto-generated secrets
Fresh installs need nothing in .env. See PR description for the full feature.
2026-07-01 14:49:18 +02:00
Paul Nothaft b8b33ae6d6 Merge pull request #713 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.77.0-beta.0
v3.77.0-beta.0
2026-07-01 10:00:29 +02:00
github-actions[bot] e1dae31e16 chore(main): release 3.77.0-beta.0 2026-07-01 07:55:55 +00:00
Paul Nothaft e873f7c98c Merge pull request #712 from PicPeak/chore/conventional-pr-title-guard
feat: admin photos list/grid toggle + upload failure report (#707, #708)
2026-07-01 09:55:37 +02:00
Paul Nothaft 6f95796b7c feat: admin photos list/grid toggle + upload failure report (#707, #708)
Records two features that merged with gitmoji commit subjects and were
therefore skipped by Release Please, so the next beta credits them:
- #707 grid/list layout toggle on the admin Photos tab
- #708 per-file upload failure report in the upload modal

No code change — the features are already on main; this commit only gives
Release Please a Conventional Commit to cut the release from.
2026-07-01 09:38:34 +02:00
Paul Nothaft 4881d2040a ci: validate PR titles against Conventional Commits
Release Please only recognizes Conventional Commit prefixes (feat:, fix:,
...). PRs merged with other conventions (gitmoji, free-form) are silently
skipped, shipping changes with no version bump or changelog entry (see
#707/#708). Fail such PRs early via amannn/action-semantic-pull-request.
2026-07-01 09:38:34 +02:00
Paul Nothaft b8665e1d86 Merge pull request #708 from andredlng/feat/upload-failure-details
Surface which files failed during photo upload
2026-07-01 09:29:19 +02:00
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 1be871cb9a 🐛 Address review: fix spinner hang + show processing failures
Blocker: the "every file rejected" reset never fired because the backend
returns `upload_id` unconditionally (with count 0), so `anyQueued` was
always true and the completion effect (gated on total > 0) never ran —
modal spun forever. Gate `anyQueued` on `count > 0` so a zero-photo
response takes the terminal reset path.

Concern 1: processing-stage failures were invisible — the modal
auto-closed on clean transfer before the worker reported them. Defer the
settle/close decision to the completion effect (combining transfer +
processing failures), and persist failed photos into `processingFailures`
state before `uploadIds` is cleared, so the rows don't vanish the instant
they appear.

Also: report card gets role="status"/aria-live (nit), and tests now cover
the whole-chunk transfer failure, the clean-settle path, and the
onUploadSettled contract from the real component.
2026-07-01 08:03:01 +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 cc26296c58 🐛 Keep upload modal open when some files fail
The failure report lives inside the upload modal, but the modal
auto-closed the instant the transfer finished (handleUploadComplete →
onClose), unmounting the report before the user could read it — so the
"which files failed" list never actually appeared.

Split the modal's completion callback in two:
- onUploadComplete: refresh the grid only (no close), as bytes land and
  again when processing finishes
- onUploadSettled({ hasFailures }): fired once the transfer settles; the
  modal auto-closes only on a clean upload and stays open (report
  visible) when any file failed

Also reset the transfer UI when nothing was queued (every file failed),
which previously left the modal spinning forever. Add a PhotoUploadModal
test covering close-on-clean vs stay-open-on-failure.
2026-06-30 21:22:49 +02:00
André Deuerling 1b0100cfad Surface which files failed during photo upload
A partial upload only told the admin "some files failed" with no way to
find out which ones — even though the data existed. The backend already
returns per-file rejections (response.errors: [{filename, error}]) and the
progress hook already exposes failedPhotos, but both were dropped.

Add a dismissible failure report to the upload modal listing every file
that didn't make it into the gallery, grouped by stage with its reason:
- rejected: per-file validation rejections from the upload response
  (previously discarded entirely)
- transfer: whole-chunk request failures (now captured with the error,
  not just the filename)
- processing: background-worker failures from useUploadProgress.failedPhotos

Replace the count-only "some files failed" toast with one that points at
the list. Add en/de keys under upload.failures.* and a component test
covering the rejected + processing rows and dismissal.
2026-06-30 21:08:02 +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