Commit Graph
15 Commits
Author SHA1 Message Date
Luca cf424efb4a feat(workflows): wire booking document actions (prepare_invoice/contract + send_document)
Implements the draft-seam booking cutover so the booking_invoice_only flow
becomes enableable. The booking flows trigger on quote.accepted, so the run
entity is the quote:

- prepare_invoice: convertToInvoiceOnly({draft:true}) creates the invoice(s)
  on HOLD (scheduled_send_at NULL, status stays 'scheduled') so the scheduler
  never auto-sends before the review gate; crash-recovery recovers drafts by
  the quote's deal_uuid. Stores ids in ctx.vars.preparedInvoiceIds.
- prepare_contract: createFromQuote (idempotent via converted_contract_id).
- send_document: dispatches the prepared draft (invoice -> sendInvoice each id,
  contract -> sendContract).
- resolveActor: quote creator -> workflow creator -> first admin.
- prepare_contract/prepare_invoice/send_document removed from the enable-guard
  list; prepare_event/prepare_quote/prepare_gallery/reserve_date still guarded,
  so booking_full/booking_simple stay blocked until the event-path increment.

Fixes a latent single-connection SQLite deadlock these unattended paths would
hit: getAppSetting/logActivity/adminActor read or write the global db, which
deadlocks when issued inside an open knex transaction. Thread the active trx
through getAppSetting, logActivity, nextInvoiceNumber, nextContractNumber, the
spawnInstallmentInvoices audit log, and hoist adminActor before createFromQuote's
transaction. convertToInvoiceOnly now logs after commit and returns invoiceIds.

Adds bookingCutover integration test (hold-mode null send-at, normal scheduled
contrast, contract path no-deadlock) and a route test that the now-implemented
booking invoice actions can be enabled.
2026-06-26 23:48:12 +02:00
Luca 989b42c2f1 fix(restore-wizard): warn on backups without a database dump
Closes the loop on the original 2026-05-29 data-loss class: Ralf had
four "Run Backup Now" manifests sitting on disk with
database.backup_file = null because Stage A wasn't yet in place.
The restore wizard would have happily restored any of those four,
bringing back files (photos, PDFs) but leaving the database empty —
silently re-creating the exact data loss the rest of this branch
prevents going forward.

The /restore/list-backups endpoint now returns `database_included`
per row (parsed from the manifest at discovery time). The wizard
uses it to:

  - Per-row badge: red "No DB" pill next to any backup where
    database_included === false. Tooltip explains the consequence
    in plain English: "restoring this will NOT recover the database".
  - Selected-card callout: full red banner under the chosen row
    when database_included is false, restating the warning + giving
    the admin a clear path: "pick a different backup if you have
    one with a database dump, or proceed only if files-only is
    what you want."

The wizard does NOT block the restore — the admin may genuinely want
a files-only restore (e.g. recovering a deleted photo while keeping
current DB state). The warnings make sure that choice is informed.
2026-05-31 22:44:37 +02:00
Paul Nothaft 87834a7fff fix(install): defer events.hero_photo_id FK to break circular reference (#484)
Real root cause behind MrGabri's fresh-Postgres install crash, surfaced
by his second log dump after #488 silenced the FATAL noise:

  Initial setup failed: error: alter table "events" add constraint
  "events_hero_photo_id_foreign" foreign key ("hero_photo_id")
  references "photos" ("id") on delete SET NULL
  - relation "photos" does not exist

initializeDatabase() in src/database/db.js declared the FK inline at
events createTable (line 89), but the photos table is created later
in the same function (line 203). On Postgres this is a hard error —
the referenced table must exist at FK-declaration time. SQLite
silently tolerated it because its FK enforcement is lazy and the
inline declaration just became a column with no FK metadata.

Why no existing Postgres install hit it: initializeDatabase only
runs the createTable block on `if (!hasEventsTable)`. Once a
deployment has the events table from any prior run, the path is
skipped. So the bug only ever fires on a truly fresh Postgres
install — which is exactly MrGabri's scenario, and which our smoke
suite never exercises (it runs against a long-lived dev stack).

Fix:

- events createTable: drop the inline FK; column declared as a plain
  integer with an explainer comment.
- After both tables exist (post photos createTable): db.schema
  .alterTable('events').foreign('hero_photo_id').references...
  Wrapped in a try/catch that swallows "already exists" so re-runs
  on installs that previously got into a half-state don't fail boot.

Verified by docker compose down -v + up against the dev stack — no
FK error, all migrations apply, FK present in pg_constraint with
the expected definition.
2026-05-14 21:21:23 +02:00
LucaandClaude Opus 4.6 087ef45942 feat(customers): customer portal (#354) on top of feature-flags reorg
Implements the recurring-customer login surface from
the-luap/picpeak#354 plugged into the maintainer's
new feature-flag infrastructure (PR #443) instead of
a parallel toggle.

* New `customerPortal` feature flag (foundation flag for the
  not-yet-built calendar/quotes/bills/messaging customer
  surfaces). Defaults FALSE on fresh installs, TRUE on existing
  installs (events > 0) via migration 095 so live customer
  accounts don't disappear mid-deployment.
* Foundation schema: customer_accounts, customer_invitations,
  event_customer_assignments, customer_password_resets, plus
  RBAC permissions customers.view / .create / .delete granted
  to super_admin + admin system roles.
* Backend: /api/admin/customers (invite, list, search, assign,
  deactivate, reset password) + /api/customer/auth/* +
  /api/customer/* (login, dashboard, accept-invite, reset).
  Customer JWT bypass minted via
  /api/customer/events/:slug/access-token so existing gallery
  middleware stays untouched.
* Frontend: /customer/* route tree gated by RequireFeature flag
  customerPortal, with login / dashboard / accept-invite /
  reset pages and a customer-side sidebar layout.
  /admin/customers and /admin/customers/:id gated identically.
* Settings → Features grows a "Customers" section with a
  Customer portal card. The maintainer's Features tab stays the
  single source of truth — no parallel Advanced features tab.
* CustomerAccountPicker on event create/edit forms hides itself
  when the flag is off; backend ignores customer_account_ids in
  that case instead of erroring the whole event save.

Translations: en + de hand-translated. nl/pt/ru fall through to
en — flagged here as needing native review.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-05-11 00:05:20 +02:00
Luca 66423bb65e feat(cms): add per-page external URL override — backend 2026-05-03 23:05:18 +02:00
Paul Nothaft 4f77905b87 feat: customisable 404 + gallery-not-found pages via CMS (#324)
The 404 catch-all and the "gallery not found" branches in GalleryPage
were hard-coded English strings on a default-themed background — the
one place where a white-labelled deployment leaked the PicPeak default
look. Pluggable now via the existing CMS Pages mechanism.

Backend:
- Seed two new default CMS pages: `not-found` and `gallery-not-found`,
  with sensible English/German copy admins can edit in /admin/cms.
- Add `cms_pages.logo_url` (nullable) for per-page logo override; online
  migration on existing deployments. Null falls back to the global
  branding logo.
- New per-page logo upload (POST /api/admin/cms/pages/:slug/logo) +
  clear endpoint (DELETE …/logo). Reuses the existing /uploads/logos
  storage location with a `cms-<slug>-` filename prefix.
- adminCMS PUT now accepts logo_url; publicCMS GET returns it.

Frontend:
- New <CMSContentBlock slug fallback> component renders the CMS page in
  the standard branded shell (logo precedence: page → branding → bundled
  default), with DOMPurified content and footer/legal links.
- App.tsx: `path="*"` catch-all routes through CMSContentBlock("not-found").
- GalleryPage: collapses the two "gallery not found" branches (invalid
  identifier + infoError archived/missing) into a single
  CMSContentBlock("gallery-not-found"), so admins can edit one source
  of truth.
- Admin CMS Page editor gains an "Upload Logo / Use site default"
  control per page; falls back to the page's own English title in the
  page list when no `legal.<slug>` translation is registered.
2026-04-27 22:38:00 +02:00
Paul Nothaft 31fd64c83c Add short gallery URL toggle and token support (#38)
continuous-integration/drone/push Build is passing
Test and Lint / backend-test (push) Successful in 1m55s
Test and Lint / frontend-test (push) Successful in 1m54s
2025-10-15 07:21:09 +02:00
Paul Nothaft 775c5159ea Add customer contact fields and admin API docs (refs #41) 2025-10-14 18:29:21 +02:00
paul 5d6c061f1c feat: support per-gallery password toggle 2025-10-01 16:00:37 +02:00
paul 71e7179145 Harden auth cookies and fix native schema for event creation
Mirror to GitHub / mirror (push) Successful in 45s
Test and Lint / backend-test (push) Successful in 1m37s
Test and Lint / frontend-test (push) Successful in 2m8s
Version and Release / version-bump (push) Successful in 1m0s
Version and Release / trigger-drone (push) Successful in 3s
2025-09-18 15:59:26 +02:00
paul 097ce2c205 Fix native install schema gaps (closes #20)
Version and Release / trigger-drone (push) Has been skipped
Mirror to GitHub / mirror (push) Successful in 1m38s
Test and Lint / backend-test (push) Successful in 1m44s
Test and Lint / frontend-test (push) Successful in 2m12s
Version and Release / version-bump (push) Failing after 1m20s
2025-09-18 12:42:05 +02:00
paul 7f9cb33a40 chore(native): ensure SQLite data dir exists in setup and at runtime; keep native paths consistent under /opt/picpeak/app
Test and Lint / frontend-test (push) Successful in 2m3s
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m38s
Version and Release / version-bump (push) Successful in 59s
Version and Release / trigger-drone (push) Successful in 3s
2025-09-09 15:25:04 +02:00
paulandClaude 1b4b497fdf chore: clean up codebase for production readiness
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Has been cancelled
Version and Release / version-bump (push) Has been cancelled
Version and Release / trigger-drone (push) Has been cancelled
- Remove all console.log/debug statements from production code
- Add NODE_ENV checks for development-only logging
- Remove test scripts (test-feedback, test-image-security, test-backup-*, test-restore)
- Remove one-time fix scripts (fix-temp-photos, fix-migration-state, mark-migration-applied)
- Remove sensitive files (.env.backup, ADMIN_CREDENTIALS.txt)
- Update package.json to remove references to deleted scripts
- Replace console statements with logger utility in backend
- Secure error boundaries to not expose stack traces in production

This makes the codebase production-ready with no debug output or test scripts.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-08-24 23:19:30 +02:00
paulandClaude 8588133a4e fix: critical database connection pool exhaustion issues
- Disabled duplicate email service (emailService.js) that was creating redundant connections
- Increased connection pool size from 10 to 25 for production environment
- Extended session timeout cache from 5 to 30 minutes to reduce DB queries
- Added connection retry logic with exponential backoff for transient failures
- Fixed password validation to use retry wrapper and correct setting key
- Updated public settings and gallery middleware to handle connection failures gracefully

These changes address the "Connection terminated unexpectedly" errors in production by:
1. Reducing unnecessary database connections
2. Increasing available connection pool capacity
3. Implementing automatic retry for transient connection failures
4. Caching frequently accessed data for longer periods

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-07-24 16:57:07 +02:00
paulandClaude 1773ed5f95 Initial commit - Project start (July 17, 2025)
continuous-integration/drone/push Build is passing
Mirror to GitHub / mirror (push) Successful in 26s
Test and Lint / backend-test (push) Successful in 1m11s
Test and Lint / frontend-test (push) Successful in 2m28s
Version and Release / version-bump (push) Successful in 32s
Version and Release / trigger-drone (push) Has been skipped
Original: feat: enhance security logging and ensure rate limit blocks are properly tracked

- Add comprehensive logging for rate limit blocks with full request details
  - IP address (with proper proxy detection), user agent, headers, timestamps
  - Rate limit info (current count, limit, remaining, reset time)
  - Separate tracking for auth vs general endpoints

- Enhance authentication failure logging
  - JWT validation failures with detailed error info
  - Admin auth attempts without token
  - Failed token validation with user context
  - All events include IP, path, method, user agent

- Improve Winston logger configuration for production
  - Add automatic log rotation (10MB errors, 50MB combined)
  - Create separate security.log for auth/rate limit events
  - Ensure logs directory exists automatically
  - Add structured JSON format for log aggregation
  - Support container logging with LOG_TO_CONSOLE env var

- Create comprehensive documentation
  - Security logging guide with examples
  - Monitoring recommendations
  - Configuration reference

- Add test script to verify logging functionality

All rate limit settings remain configurable via admin panel:
- Window duration, max requests, auth limits
- Skip authenticated requests option
- Public endpoints only option

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-07-24 16:57:07 +02:00