# Architecture

## 1. Project Purpose

Kulibri is a **poll/survey/scheduling SaaS** — a "who can attend"-style tool where an organizer creates a poll (with date/text/multi-choice options), invites participants via email or public link, collects answers/comments, and manages results. It also includes:
- Paid **membership/subscription** billing (via Mollie)
- Contact/newsletter management
- Admin tooling and statistics

It's a monolith combining a JSON API backend with an SPA frontend, plus a legacy WordPress marketing site (`wordpress/`) that fronts the public-facing pages.

## 2. Architecture Overview

```mermaid
graph TD
    A[Vue 2 SPA - frontend/] -->|axios / JSON API| B[Laravel 13 API - routes/api.php]
    W[WordPress marketing site] -->|links into| A
    B --> C[(MySQL)]
    B --> D[Mollie Payments]
    B --> E[Mail: Mailgun/Postmark]
    B --> F[Excel Import/Export - Maatwebsite/PhpSpreadsheet]
    B --> G[Sanctum session auth]
    B -->|hashids middleware| C
```

- **Backend**: Laravel 13 (PHP 8.3), session-based Sanctum auth for the SPA, a separate token-based `Api\AuthController` for a mobile/Flutter client.
- **ID obfuscation layer**: nearly every route is wrapped in a custom `hashids` middleware (`app/Kulibri/HashIds/`) that decodes hashid strings to DB integer PKs on the way in and re-encodes them on the way out.
- **Public, unauthenticated poll routes**: poll/participant/invitation endpoints reachable by anyone holding the right hashid (polls are answered without login) — the most security-sensitive surface in the app.
- **Frontend**: Vue 2 + Vuex + vue-router SPA under `frontend/`, built independently and published straight into Laravel's `public/`.
- **Billing**: Mollie webhooks (`/payment/webhook`, `/subscription/webhook`) drive `Membership`/`Subscription`/`Invoice` state.
- **Legacy**: `wordpress/` folder hosts the marketing site; locale is synced from its `wp-wpml_current_language` cookie for logged-out SPA users.

## 3. Main Technologies

| Layer | Tech |
|---|---|
| Backend framework | Laravel 13, PHP ^8.3 |
| Auth | Laravel Sanctum (SPA session + mobile token), Socialite (Google) |
| DB | MySQL |
| Payments | Mollie API |
| Mail | Mailgun/Postmark (via Symfony mailers), PHPMailer |
| Excel | Maatwebsite/Excel v4, PhpOffice/PhpSpreadsheet v5 |
| Images | Intervention Image v3 |
| Frontend | Vue 2, Vuex, vue-router, Bootstrap 4, Laravel Mix / vue-cli (webpack) |
| Testing | PHPUnit 11 (Feature/Unit), Jest (frontend, mostly unused), Nightwatch e2e (configured, unused) |
| Infra | Ansible (cron jobs), custom deploy scripts (`deploy-app.js`, `deploy-laravel.js`, `deploy-vue.js`) |

## 4. Build and Deployment Process

**Backend:**
```
composer install
php artisan migrate:fresh --seed
php artisan serve   # dev, check /api/version
php artisan test    # PHPUnit
```

**Frontend** (root `package.json` wraps `frontend/`):
```
npm install                # postinstall also npm-installs frontend/
npm run build              # version bump + Mix prod build + `vue-cli-service build --no-clean` -> published into ../public
npm run build:dev|:qa|:vps # environment-specific variants
npm run lint / test:unit / test:e2e   # frontend/'s eslint, Jest, Nightwatch
```
Builds use `--no-clean`, and the root scripts manually `rm -rf public/{js,css,img,sass}` first, because Vue's build output is published directly into the Laravel `public/` folder — there is no separate frontend server in production.

Deployment/hosting is custom-scripted (`deploy-app.js`, `deploy-laravel.js`, `deploy-vue.js`) rather than CI/CD-based, with server setup documented in `Hosting.md`:
- Requires a `public/export/` folder (temp Excel export storage) and `php artisan storage:link` (or `GET /storage_link` shortcut) per environment.
- User-uploaded images (`storage/app/public`) must be manually rsync'd when migrating servers — not covered by any automated deploy step.
- DB backups/cleanup run via Laravel's scheduler (`Kernel.php`), moved out of server crontab.

## 5. Important Folders and Responsibilities

| Folder | Responsibility |
|---|---|
| `app/Http/Controllers/` | One controller per resource; `Admin/` and `Api/` subfolders for admin-only and mobile-API endpoints |
| `app/Models/` | Domain entities: `Poll`, `Choice`, `Participant`, `ParticipantValue/Attribute`, `Invitation`, `Comment`, `Task`, `Resource`, `Design`, `Membership`, `Subscription`, `Invoice`, `User`, `Contact` |
| `app/Services/` | Cross-cutting logic: `PollExportService`, `PollAccessResolver`/`PollAccess`, `NewsletterService`, `SignalFlow`, `UserNotificationService`, `HealthCheckService` |
| `app/Kulibri/HashIds/` | The public-ID obfuscation middleware/converter system |
| `app/Repositories/` | Only `SubscriptionRepository` so far — not a project-wide pattern |
| `app/Exports/`, `app/Imports/` | Maatwebsite Excel import/export (poll data, contacts) |
| `routes/api.php` | Main API surface, `hashids`+`logger` (+`auth:sanctum`,`verified`) middleware groups |
| `frontend/src/` | `store/` (Vuex per-domain modules), `services/` (`signalflow.js`), `views/`+`components/`, `locales/` (vue-i18n) |
| `tests/Feature/`, `tests/Unit/` | Real PHPUnit coverage (post Laravel 9->13 migration), tracked against `TESTING-BACKLOG.md` |
| `Design.md` | Living design-questions doc (German) on public/personal poll links, admin vs poll key, invitation `visited`, temp-participant cleanup |
| `MIGRATION-LOG.md` | Hop-by-hop record of the Laravel 9->13 upgrade, with verified findings per hop |
| `ROADMAP.md` | Non-test engineering backlog, notably the auth backdoor stopgap and DB backup credentials |

## 6. Coding Conventions Discovered

- **Controllers** use constructor DI for services (`PollExportService`, `PollAccessResolver`), fat with many `use` imports; not consistently thin — business logic often lives directly in controller methods.
- IDs are **always hashid strings on the wire, ints internally** — never assume a request/response body ID is the raw PK.
- **Route grouping by middleware** (`auth:sanctum, verified, hashids, logger` vs. public `hashids, logger`-only groups) is the primary way access control is expressed — not per-method policy checks.
- Recently extracted shared-logic pattern: pulling duplicated admin/sub-admin resolution into a dedicated service (`PollAccessResolver`) to avoid "drifting copies" — a convention worth following for future duplication.
- **Carbon date-math bugs** (`diffIn*` sign flips) have been a recurring real bug class across the codebase.
- Tests favor **real HTTP round-trips** (`Tests\Feature`) over heavy mocking, with `Http::fake()`/`Mail::fake()` only where a real external call is required (Mollie, mail, SignalFlow).
- `TESTING-BACKLOG.md` is treated as a **living status board** — updated in the same turn a test is added, not written once as a plan.

## 7. Key Business Workflows

1. **Poll creation & invitation** — organizer creates a `Poll` with `Choice` options and a `Design` (theming); `Invitation`s track `visited` (opened-at-least-once, display-only, not an access gate).
2. **Public poll answering** — anyone with a poll/participant/invitation hashid can open the poll link (no login), optionally becoming a "temp" `Participant`; answers land in `ParticipantValue`. Biggest security/ambiguity surface (see `Design.md`).
3. **Comments & hidden polls** — participants can comment; `hiddenPoll` option redacts other participants' answers (recently fixed to be enforced server-side, not just frontend).
4. **Result export** — `PollExportService` streams a poll's answer matrix to `.xlsx` via raw PhpSpreadsheet into `public/export/`.
5. **Billing** — `Membership`/`Subscription`/`Invoice`, driven by Mollie webhooks, grants premium roles on successful payment.
6. **Reminders/notifications** — `ReminderController`/`AutomatedReminder`, `UserNotificationService`, threshold logic based on time since last answer.
7. **Health monitoring** — `/api/health` performs a real login -> fetch polls -> logout round trip; alerting via `health:check --alert`.
8. **Admin support login** — a former hardcoded-password account-takeover backdoor, now stopgapped behind an env secret + admin-role check + audit log (real fix still pending, see `ROADMAP.md`).

## 8. Suggested Areas Requiring Documentation

- **Public vs. personal poll link model** — `Design.md` explicitly flags this as unresolved (temp-participant cleanup/merge, admin-key vs. poll-key). Highest-value gap given it's also the top security surface.
- **`hashids` middleware internals** — `Checker`'s field-matching/blacklist rules, currently only inferable from code; no dedicated round-trip unit test for `RequestDecoder`/`ResponseEncoder`.
- **Frontend public-link scheme vs. backend hashids** — `${VUE_APP_FRONTEND_URI}/pr/{pollId}/{publicKey}` uses a separate ad-hoc scheme from backend hashids; worth an explicit doc note.
- **Subscription/Membership/Invoice state machine** — no doc or test coverage of upgrade/downgrade/cancel transitions.
- **`SignalFlow` service** — cross-cutting, undocumented, no test coverage; unclear responsibility from name alone.
- **Two known live security items**: `/api/version` leaking mail-credential-adjacent env values, and `/api/email` gated on a hardcoded literal — flagged inline in code/`CLAUDE.md` but not tracked with remediation steps.
- **Deployment/runbook** — `deploy-*.js` scripts and Ansible cron config have no README describing invocation, environments, or rollback.
- **Frontend Vuex module contracts** — no documentation of store module shapes/actions consumed by views.
