# SmileArtDrop — Technische Dokumentation

## 1. Stack-Übersicht
| Bereich   | Technologie                                  |
|-----------|-----------------------------------------------|
| Frontend  | React + Vite *oder* Next.js (dieses Paket liefert statisches HTML/CSS/JS als sofort lauffähigen Prototyp; Struktur ist 1:1 in Komponenten übertragbar) |
| Backend   | Node.js + Express + MongoDB (Mongoose)         |
| Karte     | Leaflet + OpenStreetMap-Tiles                  |
| Auth      | JWT (Access Token, 30 Tage gültig)             |
| QR-Codes  | qrcode.js (Client) / `qrcode` npm-Paket (Server, optional) |
| Payment   | Stripe (PaymentIntents + Webhook)              |
| PWA       | Web App Manifest + Service Worker (Cache- & Background-Sync-Strategien) |

---

## 2. Backend lokal starten

```bash
cd backend
npm install
cp .env.example .env   # und Werte anpassen (mind. JWT_SECRET)
```

**Mit echter MongoDB** (empfohlen für Produktion/eure Testumgebung, z. B. per Docker
`docker run -d -p 27017:27017 mongo:7` oder MongoDB Atlas):
```bash
npm start        # bzw. "npm run dev" mit nodemon
```

**Ohne installierte MongoDB** – lokaler In-Memory-Testmodus (nur zum Ausprobieren/Testen
der Routen, Daten gehen beim Beenden verloren):
```bash
npm run dev:mock   # bzw. "npm run start:mock" ohne nodemon
```
Alle Endpunkte (Registrierung, Login, Objekte, Logbuch, Karte, Rollen-Logik) wurden in diesem
Modus end-to-end getestet – inkl. Berechtigungsprüfungen (Rollen, Eigentümer/Finder, Admin-Bypass).

---

## 3. MongoDB-Schema (Zusammenfassung)

### `users`
| Feld              | Typ               | Beschreibung                          |
|-------------------|-------------------|----------------------------------------|
| nickname           | String, unique    | Öffentlicher Anzeigename               |
| email              | String, unique    | Login                                  |
| passwordHash       | String            | bcrypt-Hash                            |
| gpsOptIn           | Boolean           | Freiwillige Standortfreigabe           |
| homeLocation       | GeoJSON Point     | Nur gesetzt bei `gpsOptIn: true`        |
| pushSubscription   | Object            | Web-Push-Abo für Nähe-Benachrichtigungen |
| stats.objectsCreated / objectsFound | Number | Community-Statistik    |
| role               | Enum              | Nutzergruppe, siehe unten               |

**Nutzergruppen (`role`):**
| Gruppe | Wert       | Beschreibung                                              |
|--------|------------|-------------------------------------------------------------|
| 1      | `member`   | Sammelt und verteilt (Standard nach der Registrierung)       |
| 2      | `creator`  | Sammelt, verteilt **und** hat eigene Objekte – automatisches Upgrade beim ersten selbst veröffentlichten Objekt |
| 3      | `admin`    | Vollzugriff auf alle Objekte (Moderation), manuell vergeben   |

### `artobjects`
| Feld              | Typ               | Beschreibung                          |
|-------------------|-------------------|----------------------------------------|
| title              | String            | Objektname                             |
| qrSlug             | String, unique    | Teil der Logbuch-URL & des QR-Codes    |
| owner              | ObjectId → User   | Erstellende Person                     |
| status             | Enum              | `ausgesetzt` · `eingesammelt` · `pausiert` |
| collectedAt/collectedBy | Date / ObjectId | Gesetzt beim Mitnehmen – Frist: max. 3 Tage bis zum erneuten Aussetzen |
| supportedBy/supportedAt | ObjectId / Date | Reserviert für eine spätere, optionale Kauf-/Unterstützungsfunktion (siehe unten) |
| coverPhotoUrl      | String            | Titelbild                              |
| backText           | String            | Automatisch generierter Rückseiten-Text |
| currentLocation    | GeoJSON Point     | Aktueller Standort (für Karten-Pin)     |
| logbook[]          | Array             | Jede Station: Ort, Geschichte, Foto, Datum, Finder-ID |

**Status-Modell:**
| Status         | Bedeutung                                                              |
|----------------|--------------------------------------------------------------------------|
| `ausgesetzt`   | Objekt ist im öffentlichen Raum auffindbar                                |
| `eingesammelt` | Ein Finder hat das Objekt mitgenommen (zuhause/Büro) – max. 3 Tage Frist, dann Schritt „Erfreuen“ (Logbuch-Eintrag) und erneutes Aussetzen/Weitergeben |
| `pausiert`     | Vorübergehend aus dem Umlauf genommen (z. B. Urlaub der aktuellen Person) |

> **Hinweis zum Kauf-Feature:** Das dauerhafte „Behalten“ eines Objekts gegen Bezahlung ist in
> dieser Version **optional und noch nicht in den Kern-Workflow eingebunden**. Die Stripe-Routen
> existieren als vorbereitete Erweiterung, verändern aber aktuell nicht den Status eines Objekts.

Beide Collections nutzen `2dsphere`-Indizes für performante Umkreis- und Kartenabfragen.

---

## 4. API-Endpunkte (Auswahl)

### Nutzer
| Methode | Pfad                          | Beschreibung                             |
|---------|-------------------------------|-------------------------------------------|
| POST    | `/api/users/register`         | Registrierung (Nickname, E-Mail, Passwort, optional GPS) |
| POST    | `/api/users/login`            | Login, gibt JWT zurück                    |
| GET     | `/api/users/me`                | Eigenes Profil (JWT erforderlich)          |
| POST    | `/api/users/me/push-subscription` | Web-Push-Abo speichern                 |

### Objekte
| Methode | Pfad                             | Beschreibung                                        |
|---------|-----------------------------------|---------------------------------------------------------|
| GET     | `/api/objects?owner=me&status=ausgesetzt` | Eigene/alle Objekte filtern (`owner=all` nur für Gruppe 3/Admin) |
| GET     | `/api/objects/:slug`              | Einzelnes Objekt inkl. vollständigem Logbuch          |
| POST    | `/api/objects`                    | **Nur angemeldet** – neues Objekt anlegen ("Kunst in Umlauf bringen"); stuft Gruppe 1 automatisch auf Gruppe 2 hoch |
| POST    | `/api/objects/:slug/logbook`      | Schritt „Erfreuen“: Fund eintragen → Status wird `eingesammelt` (Frist 3 Tage) |
| PATCH   | `/api/objects/:slug/status`       | Status ändern (`ausgesetzt`/`eingesammelt`/`pausiert`) – nur Ersteller, aktuelle Finder-Person oder Admin |

### Karte
| Methode | Pfad                                             | Beschreibung                        |
|---------|---------------------------------------------------|---------------------------------------|
| GET     | `/api/map/objects?city=&district=&since=`         | Objekte für die Kartenansicht, mit Filtern |
| GET     | `/api/map/nearby?lat=&lng=&radiusKm=3`             | Objekte im Umkreis (Basis für Push-Trigger) |

### Payment (optional, noch nicht Teil des Kern-Workflows)
| Methode | Pfad                              | Beschreibung                             |
|---------|------------------------------------|---------------------------------------------|
| POST    | `/api/payments/create-intent`      | Stripe-PaymentIntent für eine freiwillige Unterstützung (9,90 €) |
| POST    | `/api/payments/webhook`            | Speichert `supportedBy`/`supportedAt` – ändert **nicht** den Objekt-Status |


---

## 5. SEO- & OpenGraph-Vorschlag

```html
<title>SmileArtDrop – Kunst in Bewegung. Finde, teile, verschenke.</title>
<meta name="description" content="SmileArtDrop dokumentiert Kunstobjekte im öffentlichen Raum und zeigt ihre Reise auf einer interaktiven Karte.">
<meta name="theme-color" content="#FF9800">

<meta property="og:type" content="website">
<meta property="og:site_name" content="SmileArtDrop">
<meta property="og:title" content="SmileArtDrop – Kunst in Bewegung">
<meta property="og:description" content="Finde, teile und verschenke Kunstobjekte im öffentlichen Raum. Verfolge ihre Reise live auf der Karte.">
<meta property="og:image" content="/icons/icon-512.png">
<meta property="og:url" content="https://smileartdrop.app/">
<meta property="og:locale" content="de_DE">

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="SmileArtDrop – Kunst in Bewegung">
<meta name="twitter:description" content="Finde, teile und verschenke Kunstobjekte im öffentlichen Raum.">
<meta name="twitter:image" content="/icons/icon-512.png">
```

Jede Unterseite (`map.html`, `account.html`, `generator.html`, `payment.html`, `community.html`) trägt bereits eine eigene, seitenspezifische `<meta name="description">`.

---

## 6. Beispiel-Textinhalte

**Claim:** „Kunst in Bewegung – Finde, teile, verschenke.“

**Projektbeschreibung (Kurzform):**
„SmileArtDrop macht Straßenkunst sichtbar: Menschen setzen kleine Kunstobjekte im öffentlichen Raum aus, dokumentieren ihre Geschichte – und jede und jeder kann die Reise dieser Objekte quer durch die Stadt live mitverfolgen.“

**Drei Schritte (aus Sicht der Finderin/des Finders):**
1. *Mitnehmen* – Ein ausgesetztes Objekt finden und mitnehmen, nach Hause oder ins Büro, zum Aufhängen oder Hinstellen.
2. *Erfreuen* – QR-Code scannen und ein Foto ins Logbuch eintragen; Standort und eine kleine Geschichte sind optional.
3. *Weitergeben* – Spätestens nach 3 Tagen das Objekt wieder aussetzen oder direkt weitergeben – die Reise geht weiter.

**Automatisch generierter Rückseiten-Text (Beispiel):**
„Ein kleines Kunststück auf Reisen: „Sonnenblume aus Draht“, ausgesetzt von StadtfuchsMoni in Berlin. Es erzählt an jedem Ort, an dem es landet, eine neue Geschichte. Scanne den QR-Code, lies die bisherige Reise und trag deinen Fund ein!“

**Community-Regeln (Kurzfassung):** Sicherheit zuerst · Respekt vor Ort und Mensch · Nachhaltig gestalten.

---

## 7. Zugriffslogik (wer sieht/darf was)

| Bereich                                   | Anonym | Gruppe 1 (member) | Gruppe 2 (creator) | Gruppe 3 (admin) |
|--------------------------------------------|:------:|:------------------:|:-------------------:|:-----------------:|
| Karte ansehen, Foto & Angaben im Popup      | ✅     | ✅                  | ✅                   | ✅                 |
| „Gesamte Reise ansehen“ statt Registrieren-Hinweis | ❌ | ✅            | ✅                   | ✅                 |
| Objekt scannen & im Logbuch eintragen (Erfreuen) | 👁 nur ansehen | ✅ | ✅                   | ✅                 |
| Objekt-Generator / eigenes Objekt aussetzen | ❌ (Nav-Link ausgeblendet) | ✅ | ✅ | ✅ |
| Alle Objekte verwalten (Moderation)         | ❌     | ❌                  | ❌                   | ✅                 |

Hinweis: Jede Person startet als Gruppe 1 und wird beim ersten selbst veröffentlichten Objekt
automatisch zu Gruppe 2 hochgestuft (siehe `POST /api/objects`).

---

## 8. PWA-Troubleshooting (Testumgebung/Unterverzeichnis)

Wenn die PWA sich nicht installieren lässt oder der Service Worker nicht registriert:

1. **HTTPS ist Pflicht** (außer auf `localhost`) – Service Worker registrieren grundsätzlich
   nicht über einfaches HTTP. Das ist die häufigste Ursache.
2. Alle Pfade in `manifest.json`, `sw.js` und `js/app.js` sind bewusst **relativ** (`./…`)
   gehalten, damit die App auch unterhalb eines Unterverzeichnisses wie `/smileartdrop/public/`
   funktioniert.
3. Beim Aufruf immer den **abschließenden Schrägstrich** verwenden
   (`https://domain.tld/smileartdrop/public/` statt `.../public`), da sich sonst relative Pfade
   auf das falsche Verzeichnis beziehen können.
4. `js/app.js` entfernt beim Laden automatisch alte Service-Worker-Registrierungen mit
   abweichendem Scope (z. B. Reste aus früheren Tests) und registriert `sw.js` neu.
5. Zum gezielten Debuggen: Chrome/Edge DevTools → Tab „Application“ → „Service Workers“ /
   „Manifest“ zeigt die genaue Fehlermeldung des Browsers an.

---

## 9. SQL-Variante für IONOS-Hosting (MySQL statt MongoDB)

Für Hosting-Umgebungen wie IONOS (`*.hosting-data.io`), die MySQL + PHP statt Node.js/MongoDB
bereitstellen, liegt zusätzlich ein vollständiges PHP-Auth-System bei:

```
database/schema.sql          # users, art_objects, logbook_entries, email_verification_tokens
backend-php/
├── config/
│   ├── config.ini.example    # Vorlage – ins Repo, OHNE echtes Passwort
│   ├── config.ini            # echte Zugangsdaten – NICHT ins Repo (siehe .gitignore)
│   ├── db.php                 # lädt config.ini, liefert eine mysqli-Verbindung
│   └── .htaccess              # blockt Web-Zugriff auf *.ini-Dateien
├── lib/
│   ├── bootstrap.php          # fängt JEDEN PHP-Fehler ab -> immer JSON statt HTML-Fehlerseite
│   ├── security.php           # Session-Härtung, Passwort-/Nickname-Validierung, CSRF-Token
│   └── mailer.php             # Bestätigungs-E-Mail (PHP mail())
└── test-connection.php        # einfacher Verbindungstest

public/api/
├── register.php    # POST – legt Konto an, NICHT sofort angemeldet, sendet Bestätigungsmail
├── verify.php      # GET  – Bestätigungslink aus der E-Mail, aktiviert das Konto
├── login.php       # POST – Login nur nach Bestätigung, mit Brute-Force-Sperre
├── logout.php      # POST – zerstört die Session
├── me.php          # GET  – aktueller Session-Status (für Header/Nav)
└── health.php      # GET  – Diagnose: PHP-Version, Erweiterungen, config.ini, DB-Verbindung
```

**Einrichtung:**
```bash
cd backend-php
cp config/config.ini.example config/config.ini
# config.ini mit den echten IONOS-Zugangsdaten füllen, debug=false lassen (Produktion)
```
Datenbank anlegen (z. B. per phpMyAdmin bei IONOS oder `mysql -u ... -p ... < database/schema.sql`).

**Sicherheitshinweis:** `config.ini` am besten eine Verzeichnisebene **oberhalb** des Webroots
(z. B. neben `htdocs/`) ablegen – dann ist sie über den Browser grundsätzlich nicht erreichbar.
`db.php` sucht automatisch zuerst dort, danach im `config/`-Ordner selbst. Das mitgelieferte
`.htaccess` blockt zusätzlich den direkten Aufruf von `*.ini`-Dateien, falls sie doch im
Webroot liegt.

**Sicherheitsmerkmale des Auth-Systems:**
- Passwörter mit `password_hash()`/`password_verify()` (bcrypt), nie im Klartext gespeichert
- E-Mail-Bestätigung Pflicht: Registrierung meldet NICHT automatisch an; Login wird ohne
  bestätigte E-Mail mit 403 abgelehnt
- Bestätigungs-Token: nur der SHA-256-Hash landet in der DB, Token läuft nach 24h ab, ist nach
  einmaliger Nutzung ungültig
- Brute-Force-Schutz: Konto wird nach 5 Fehlversuchen für 15 Minuten gesperrt
- Generische Login-Fehlermeldung (kein Unterschied zwischen "E-Mail unbekannt" und "Passwort falsch")
- Session-Cookies: `HttpOnly`, `SameSite=Lax`, `Secure` bei HTTPS; `session_regenerate_id()` nach Login
- Alle DB-Zugriffe über Prepared Statements (kein SQL-Injection-Risiko)

**Getestet:** Kompletter Ablauf end-to-end gegen einen echten MySQL-8.0-Server + PHP-Built-in-Server
durchgespielt: Registrierung → kein Auto-Login → Bestätigungslink → Login → Session → Logout,
Brute-Force-Sperre, doppelte Registrierung, falsches Passwort, abgelaufene/bereits genutzte Tokens.

**Hinweis zur Architektur:** Das Node.js/Express/MongoDB-Backend (`backend/`) bleibt als
Referenz-Implementierung bestehen, läuft aber nicht auf klassischem PHP/MySQL-Shared-Hosting.
Für den Objekt-Upload ("Meine Objekte") gibt es aktuell nur die PHP-Auth-Endpunkte – das
Veröffentlichen eigener Objekte ist im Frontend noch eine Demo-Meldung ohne echte DB-Anbindung.

---

## 10. Fehlerbehandlung & Troubleshooting (API)

**"Server nicht erreichbar" im Frontend, obwohl der Request ankommt:** Das passiert, wenn die
API-Antwort kein gültiges JSON ist (z. B. eine PHP-Fatal-Error-HTML-Seite) – `await res.json()`
wirft dann einen Fehler, der im Frontend pauschal als "nicht erreichbar" landet. `bootstrap.php`
wird deshalb als Allererstes in jedem `api/*.php` eingebunden und fängt **jeden** PHP-Fehler ab
(fehlende `config.ini`, falsche DB-Zugangsdaten, fehlende Erweiterung, Syntaxfehler, ...) und
liefert garantiert JSON statt einer HTML-Fehlerseite zurück.

**Schnelldiagnose:** `GET /api/health.php` aufrufen (z. B. direkt im Browser). Zeigt an:
- PHP-Version & ob `mysqli`/`mbstring` geladen sind
- ob `config.ini` gefunden wurde (und wo genau gesucht wurde)
- ob die Datenbankverbindung klappt und die `users`-Tabelle existiert

Nach erfolgreicher Einrichtung kann/sollte `health.php` gelöscht oder per `.htaccess` gesperrt
werden (verrät sonst dauerhaft, dass eine Datenbank existiert).

**`debug`-Flag in `config.ini` (Abschnitt `[app]`):** Bei `debug=false` (Produktions-Standard)
zeigen Fehlerantworten nur eine generische Meldung – die echten Details landen ausschließlich im
Server-Error-Log. Bei `debug=true` werden die Details zusätzlich in der JSON-Antwort ausgegeben
(nur zum Einrichten/Debuggen nutzen, danach wieder auf `false` stellen).

---

## 11. Projektstruktur

```
smileartdrop/
├── public/                  # PWA-Frontend (statischer Prototyp)
│   ├── index.html            # Landingpage
│   ├── account.html          # Registrierung, Upload (nur Gruppe 2+), eigene Objekte
│   ├── map.html               # Leaflet-Kartenansicht (Zwei-Finger-Pan)
│   ├── scan.html              # QR-Code scannen (Schritt 2: Erfreuen)
│   ├── logbook.html           # Logbuch-Eintrag nach dem Scannen
│   ├── generator.html        # QR-Code- & Text-Generator (nur Gruppe 2+)
│   ├── payment.html          # Stripe – optionale Unterstützung (nicht Kern-Workflow)
│   ├── community.html        # Community-Regeln
│   ├── manifest.json         # PWA-Manifest (relative Pfade)
│   ├── sw.js                  # Service Worker (relative Pfade)
│   ├── css/style.css
│   ├── js/{app,sample-data,map,generator,scan,logbook}.js
│   ├── img/logo.png
│   ├── icons/{icon-192,icon-512}.png
│   └── api/{register,verify,login,logout,me,health}.php   # echtes PHP-Auth-Backend
├── backend/                  # Node.js/Express/MongoDB-Referenzimplementierung
│   ├── server.js
│   ├── models/{index,User,ArtObject}.js
│   ├── routes/{users,objects,map,payments}.js
│   ├── middleware/{auth,optionalAuth}.js
│   ├── testing/{store,User.mock,ArtObject.mock}.js   # In-Memory-Testmodus (USE_MOCK_DB=true)
│   ├── utils/textGenerator.js
│   └── package.json / .env.example
├── database/
│   └── schema.sql            # MySQL-Schema für IONOS/Shared-Hosting
├── backend-php/              # PHP + MySQL für IONOS-Hosting (config.ini-basiert)
│   ├── config/{config.ini.example,db.php,.htaccess}
│   ├── lib/{bootstrap,security,mailer}.php
│   └── test-connection.php
└── docs/API.md               # dieses Dokument
```
