<p align="center">
  <img src="frontend/public/og-image.png" alt="fete" width="100%" />
</p>

<p align="center">
  <strong>Privacy-focused, self-hostable event announcements and RSVPs.</strong><br>
  An alternative to Facebook Events or Telegram groups — reduced to the essentials.
</p>

<p align="center">
  <img src="docs/screenshots/01-create-event.png" alt="Create Event" width="230" />
  &nbsp;&nbsp;&nbsp;
  <img src="docs/screenshots/02-event-detail.png" alt="Event Detail" width="230" />
  &nbsp;&nbsp;&nbsp;
  <img src="docs/screenshots/03-rsvp.png" alt="RSVP" width="230" />
</p>

<p align="center">
  <sub>Create events &middot; Share with guests &middot; Collect RSVPs</sub>
</p>

---

## What it does

- **Create events** with title, description, date, time, location, and an expiry date
- **Share a single link** — guests open it, see all details, and RSVP
- **No accounts, no login** — the organizer gets a secret token stored in the browser; guests just enter a name
- **Automatic cleanup** — all event data is permanently deleted after the expiry date
- **Works offline-first** — installable as a PWA, local event list stored entirely in the browser

### For organizers

- Edit event details, post update messages, manage the guest list
- Cancel or delete events at any time
- Generate a QR code for posters and flyers
- Optionally pick a color theme or header image (via self-hosted Unsplash proxy)

### For guests

- RSVP with just a name — no personal data required
- Bookmark events locally across devices
- Download `.ics` or subscribe via `webcal://` for calendar integration
- See highlighted changes and new update indicators on revisit

### Privacy by design

- No analytics, no telemetry, no external requests from the client
- No CDNs, no Google Fonts, no tracking-capable dependencies
- Server never logs PII or IP addresses
- Data retention is bounded by a mandatory expiry date

## Tech stack

| Layer        | Technology                                 |
|--------------|--------------------------------------------|
| Backend      | Java (latest LTS), Spring Boot, Maven      |
| Frontend     | Vue 3, Vite, TypeScript                    |
| Database     | PostgreSQL (external, not bundled)         |
| Architecture | SPA + RESTful API, hexagonal/onion backend |
| Deployment   | Single Docker container                    |

## Development

### Prerequisites

- Java (latest LTS) + Maven wrapper (`./mvnw`, included)
- Node.js (latest LTS) + npm
- Docker (for running backend tests via Testcontainers)

### Project structure

```
fete/
├── backend/      # Spring Boot application (Maven)
├── frontend/     # Vue 3 SPA (Vite, TypeScript)
├── spec/         # User stories, personas, implementation phases
└── CLAUDE.md     # Project statutes and AI agent instructions
```

### Running tests

```bash
# Backend
cd backend && ./mvnw test

# Frontend unit tests
cd frontend && npm run test:unit

# Frontend E2E tests (requires Chromium: npx playwright install chromium)
cd frontend && npm run test:e2e
```

### Running the backend locally

**Option A: Without external PostgreSQL (Testcontainers)**

```bash
cd backend && ./mvnw spring-boot:test-run
```

Starts the app with a Testcontainers-managed PostgreSQL that is created and destroyed automatically. Requires Docker.

**Option B: With external PostgreSQL**

```bash
cd backend
cp src/main/resources/application-local.properties.example \
   src/main/resources/application-local.properties
# Edit application-local.properties if your PostgreSQL uses different credentials
./mvnw spring-boot:run -Dspring-boot.run.profiles=local
```

### Building

```bash
# Backend
cd backend && ./mvnw package

# Frontend
cd frontend && npm run build
```

## Code quality

Automated quality gates run as Claude Code hooks during AI-assisted development. They provide immediate feedback after every file edit and block the agent from finishing when tests fail.

### Backend (Java / Maven)

**After editing a `*.java` file** (PostToolUse hook):

| What                | Command          | Fails on                         |
|---------------------|------------------|----------------------------------|
| Checkstyle          | `./mvnw compile` | Style violations (Google Style)  |
| Java compiler       | `./mvnw compile` | Compile errors                   |

Checkstyle enforces Google Style (2-space indent, import order, Javadoc on public types) and is bound to the `validate` phase, so it runs automatically as part of every `compile`. Covers both `src/main` and `src/test`.

**When the agent finishes** (Stop hook — only if `backend/src/` has uncommitted changes):

| What                | Command        | Fails on                              |
|---------------------|----------------|---------------------------------------|
| JUnit 5             | `./mvnw test`  | Test failures (fail-fast, stops at 1) |
| ArchUnit (9 rules)  | `./mvnw test`  | Hexagonal architecture violations     |

ArchUnit enforces hexagonal boundaries: domain must not depend on adapters, application, config, or Spring; ports must be interfaces; web and persistence adapters must not cross-depend.

**Not hooked** (run manually):

| What                | Command          | Fails on                                          |
|---------------------|------------------|----------------------------------------------------|
| SpotBugs            | `./mvnw verify`  | Potential bugs, null dereferences, resource leaks  |

### Frontend (TypeScript / Vue)

**After editing a `*.ts` or `*.vue` file** (PostToolUse hook):

| What                | Command            | Fails on        |
|---------------------|--------------------|-----------------|
| TypeScript (strict) | `vue-tsc --noEmit` | Type errors     |
| oxlint + ESLint     | `oxlint`, `eslint` | Lint violations |

**When the agent finishes** (Stop hook — only if `frontend/src/` or `frontend/e2e/` has uncommitted changes):

| What                | Command                      | Fails on                              |
|---------------------|------------------------------|---------------------------------------|
| Vitest              | `npm run test:unit -- --run` | Test failures (fail-fast, stops at 1) |
| Playwright          | `npm run test:e2e`           | E2E test failures                     |

**Not hooked** (run manually or via editor):

| What                | Command          | Fails on          |
|---------------------|------------------|-------------------|
| Prettier            | `npm run format` | Formatting issues |

### OpenAPI Spec (YAML)

**After editing an `openapi/*.yaml` file** (PostToolUse hook):

| What                | Command                  | Fails on                          |
|---------------------|--------------------------|-----------------------------------|
| Redocly CLI         | `redocly lint api.yaml`  | Structural and ruleset violations |

Validates the OpenAPI 3.1 spec against the Redocly `recommended` ruleset (with `security-defined` disabled, since endpoints are intentionally public). Runs via `npx @redocly/cli@latest`.

## Deployment

### Docker Compose

The app ships as a single Docker image. It requires an external PostgreSQL database.

```yaml
services:
  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: fete
      POSTGRES_USER: fete
      POSTGRES_PASSWORD: changeme
    volumes:
      - fete-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U fete"]
      interval: 5s
      timeout: 3s
      retries: 5

  app:
    image: git.bahamut.nitrix.one/nitrix/fete:latest
    ports:
      - "8080:8080"
    environment:
      DATABASE_URL: jdbc:postgresql://db:5432/fete
      DATABASE_USERNAME: fete
      DATABASE_PASSWORD: changeme
    depends_on:
      db:
        condition: service_healthy

volumes:
  fete-db:
```

### Environment variables

| Variable            | Required | Default | Description                       |
|---------------------|----------|---------|-----------------------------------|
| `DATABASE_URL`      | Yes      | —       | JDBC connection string            |
| `DATABASE_USERNAME` | Yes      | —       | Database username                 |
| `DATABASE_PASSWORD` | Yes      | —       | Database password                 |

## License

GPL — see [LICENSE](LICENSE) for details.
