Files
kashilo/docs/KILLER-FEATURES.md

439 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Killer-Features — kashilo.com
Differenzierung gegenüber eBay Kleinanzeigen, Tutti, XMRBazaar.
Drei Features, die kein Konkurrent hat.
Status: **Planung** (noch nicht implementiert)
---
## 1. Blind Meeting Points
**Problem:** Bei lokalen Deals muss eine Seite ihre Adresse preisgeben. Privacy-bewusste User vermeiden das — oder treffen sich an zufälligen, unsicheren Orten.
**Lösung:** Beide Parteien geben einen ungefähren Radius an (z.B. "Zürich HB ± 1 km"). Die App berechnet einen **öffentlichen, neutralen Treffpunkt** im Überschneidungsbereich (Bahnhof, Einkaufszentrum, Café). Keine Seite gibt ihre echte Adresse preis.
### Datenquelle
- **OpenStreetMap Overpass API** (kostenlos, kein API-Key)
- Query: POIs mit Tags wie `amenity=cafe`, `shop=mall`, `railway=station` im Überschneidungskreis
- Fallback: Mittelpunkt der beiden Radien als generischer Treffpunkt
### Algorithmus
```
1. Seller setzt Punkt A + Radius rA (z.B. 1 km)
2. Buyer setzt Punkt B + Radius rB (z.B. 1.5 km)
3. Berechne Überschneidungsfläche der beiden Kreise
4. Falls keine Überschneidung: Mittelpunkt A↔B vorschlagen, Radien vergrößern
5. Query Overpass API für geeignete POIs in der Überschnittfläche
6. Filtere nach Kategorien (priorisiert):
a. Bahnhöfe / ÖV-Haltestellen (öffentlich, belebt)
b. Einkaufszentren (überdacht, sicher)
c. Cafés / Restaurants (neutral)
d. Parks / Plätze (Fallback)
7. Wähle den POI, der am nächsten zum Mittelpunkt liegt
8. Zeige Kartenausschnitt (Leaflet + OSM-Tiles) mit dem Treffpunkt
```
### UX-Flow
```
Chat-Widget:
┌─────────────────────────────────────┐
│ 💬 Chat mit Käufer │
│ │
│ ... Nachrichten ... │
│ │
│ ┌─────────────────────────────┐ │
│ │ 📍 Treffpunkt vorschlagen │ │
│ └─────────────────────────────┘ │
│ │
│ → Klick öffnet Modal: │
│ ┌─────────────────────────────┐ │
│ │ 🗺 Karte (Leaflet/OSM) │ │
│ │ │ │
│ │ [Pin auf Karte setzen] │ │
│ │ Radius: [====●===] 1 km │ │
│ │ │ │
│ │ [Treffpunkt berechnen] │ │
│ └─────────────────────────────┘ │
│ │
│ Ergebnis wird als Chat-Nachricht │
│ gesendet (E2E verschlüsselt): │
│ ┌─────────────────────────────┐ │
│ │ 📍 Vorschlag: Zürich HB │ │
│ │ Bahnhofstrasse 1 │ │
│ │ [Auf Karte anzeigen] │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Technische Umsetzung
| Komponente | Datei | Beschreibung |
|------------|-------|--------------|
| Service | `js/services/meeting-points.js` | Geo-Berechnung, Overpass-API-Query, POI-Filterung |
| Component | `js/components/meeting-point-modal.js` | Karte (Leaflet), Radius-Slider, Ergebnis-Anzeige |
| Integration | `js/components/chat-widget.js` | Button "Treffpunkt vorschlagen", Ergebnis als Nachricht |
| CSS | `css/components.css` | Karten-Styles, Modal-Styles |
| Vendor | `js/vendor/leaflet/` | Leaflet.js (self-hosted, ~40 KB gzip) |
### Overpass API Query (Beispiel)
```
[out:json][timeout:10];
(
node["railway"="station"](around:500,47.3769,8.5417);
node["amenity"="cafe"](around:500,47.3769,8.5417);
node["shop"="mall"](around:500,47.3769,8.5417);
);
out center 5;
```
### Privacy-Aspekte
- **Kein serverseitiger Standort**: Berechnung komplett im Browser (Client-Side)
- **Overpass-Query**: Geht direkt an OSM-Server (kein Proxy nötig, keine Logs bei uns)
- **Treffpunkt im Chat**: E2E verschlüsselt, Server sieht nur Ciphertext
- **Kein GPS nötig**: User setzt Pin manuell auf der Karte
- Seller-Standort ≠ Treffpunkt (Treffpunkt ist immer ein öffentlicher Ort)
### i18n Keys
```
meeting.suggest = "Treffpunkt vorschlagen"
meeting.setLocation = "Deinen ungefähren Standort wählen"
meeting.radius = "Radius"
meeting.calculate = "Treffpunkt berechnen"
meeting.result = "Vorgeschlagener Treffpunkt"
meeting.noOverlap = "Kein gemeinsamer Bereich — Mittelpunkt wird vorgeschlagen"
meeting.noPoi = "Kein öffentlicher Ort gefunden — Punkt auf der Karte"
meeting.send = "Im Chat senden"
meeting.showMap = "Auf Karte anzeigen"
meeting.types.station = "Bahnhof"
meeting.types.mall = "Einkaufszentrum"
meeting.types.cafe = "Café"
meeting.types.park = "Park / Platz"
```
### Aufwand
- **Geschätzt**: 23 Tage
- **Dependencies**: Leaflet.js (self-hosted), Overpass API (kostenlos)
- **Risiko**: Overpass API Rate-Limits (Fallback: Mittelpunkt ohne POI-Suche)
---
## 2. Verifiable Listings (Proof of Possession)
**Problem:** Auf anonymen Marktplätzen ist Scam ein Hauptproblem. Seller können Bilder aus dem Internet nehmen und Artikel listen, die sie nicht besitzen. Ohne KYC gibt es keine Konsequenzen.
**Lösung:** Seller können optional ein Foto hochladen, auf dem ein von der App generierter **Einmal-Verifizierungscode** sichtbar ist (auf Zettel, Display, etc.). Das beweist: "Dieser Artikel war zum Zeitpunkt X physisch vorhanden."
### Verfahren
```
1. Seller klickt "Besitz verifizieren" beim Listing erstellen/bearbeiten
2. App generiert einen 6-stelligen Code + QR-Code + Zeitstempel
- Code: kryptografisch zufällig (crypto.getRandomValues)
- Gültig: 10 Minuten (danach neuer Code)
3. Seller fotografiert den Artikel MIT dem Code (auf Zettel, Handy-Display daneben, etc.)
4. Foto wird hochgeladen als spezielles Verifikations-Bild
5. App speichert: Code + Zeitstempel + Foto-ID als Verifikationsnachweis
6. Listing zeigt Badge: "✓ Besitz verifiziert am [Datum]"
```
### Verifikations-Sicherheit
| Aspekt | Lösung |
|--------|--------|
| Fake-Code auf echtem Bild | Code ist nur 10 Min gültig, Zeitfenster wird angezeigt |
| Photoshop / Bildbearbeitung | EXIF-Metadaten prüfen (optional), Hash des Originalbilds speichern |
| Fremdes Foto mit Code | Nicht 100% verhinderbar, aber Aufwand steigt deutlich |
| Wiederverwendung | Code ist einmalig, an Listing-ID gebunden |
**Wichtig:** Das ist kein perfekter Beweis, sondern ein **Vertrauenssignal**. Es erhöht den Aufwand für Scammer signifikant.
### Datenmodell (Directus)
Neues Feld auf `listings`:
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `verification_code` | String (6) | Der generierte Code |
| `verification_image` | UUID, FK → directus_files | Das Verifikationsfoto |
| `verification_date` | DateTime | Zeitpunkt der Verifikation |
| `verified` | Boolean | `true` wenn Verifikation abgeschlossen |
### UX-Flow
```
Listing erstellen / bearbeiten:
┌─────────────────────────────────────┐
│ 📷 Bilder hochladen │
│ [Bild 1] [Bild 2] [+] │
│ │
│ ───────────────────────────── │
│ │
│ ✓ Besitz verifizieren (optional) │
│ │
│ → Klick öffnet Verifikation: │
│ ┌─────────────────────────────┐ │
│ │ │ │
│ │ Dein Code: 4 8 2 7 1 5 │ │
│ │ Gültig noch: 08:42 │ │
│ │ │ │
│ │ Schreibe diesen Code auf │ │
│ │ einen Zettel und fotografiere│ │
│ │ deinen Artikel zusammen │ │
│ │ mit dem Code. │ │
│ │ │ │
│ │ [📷 Verifikationsfoto │ │
│ │ hochladen] │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
│ Nach Upload: │
│ ✅ Besitz verifiziert │
└─────────────────────────────────────┘
Auf dem Listing (für Käufer):
┌──────────────────────┐
│ 📷 Bild │
│ Titel der Anzeige │
│ 50.00 EUR │
│ ✓ Besitz verifiziert│ ← Badge
│ 📍 Zürich 🟢 10+ │
└──────────────────────┘
```
### Technische Umsetzung
| Komponente | Datei | Beschreibung |
|------------|-------|--------------|
| Service | `js/services/verification.js` | Code-Generierung, Validierung, Directus CRUD |
| Component | `js/components/verification-widget.js` | Code-Anzeige, Countdown, Upload |
| Integration | `js/components/pages/page-create.js` | Einbettung in Listing-Formular |
| Integration | `js/components/pages/page-listing.js` | Badge-Anzeige für Käufer |
| CSS | `css/components.css` | Badge-Styles, Verifikations-Widget |
### Privacy-Aspekte
- **Freiwillig**: Keine Pflicht, reines Opt-in
- **Kein KYC**: Code beweist Besitz, nicht Identität
- **Foto-Metadaten**: EXIF wird beim Upload gestrippt (wie bei normalen Listing-Bildern)
- **Code serverseitig**: Nur der Hash des Codes wird gespeichert (nicht der Klartext)
### i18n Keys
```
verification.verify = "Besitz verifizieren"
verification.optional = "Optional — erhöht das Vertrauen"
verification.code = "Dein Code"
verification.validFor = "Gültig noch"
verification.instructions = "Schreibe diesen Code auf einen Zettel und fotografiere deinen Artikel zusammen mit dem Code."
verification.upload = "Verifikationsfoto hochladen"
verification.verified = "Besitz verifiziert"
verification.verifiedDate = "Verifiziert am {{date}}"
verification.expired = "Code abgelaufen — neuen generieren"
verification.badge = "✓ Verifiziert"
```
### Aufwand
- **Geschätzt**: 12 Tage
- **Dependencies**: Keine neuen (Directus File-Upload existiert bereits)
- **Risiko**: Gering — Feature ist optional und unabhängig
---
## 3. Selbstzerstörende Listings
**Problem:** Auf klassischen Plattformen bleiben Anzeigen und Nutzerspuren ewig gespeichert. Archivierte Listings sind oft noch über Suchmaschinen auffindbar. Für Privacy-bewusste User ist das ein Dealbreaker.
**Lösung:** Seller können ein Listing als **selbstzerstörend** markieren. Nach Ablauf oder Deal-Abschluss werden alle Daten (Bilder, Beschreibung, Verifikation) **unwiderruflich gelöscht** — nicht archiviert.
### Modi
| Modus | Trigger | Effekt |
|-------|---------|--------|
| **Zeitbasiert** | Seller wählt Lebensdauer: 24h, 48h, 7 Tage, 30 Tage | Listing + Bilder werden nach Ablauf gelöscht |
| **Deal-basiert** | Deal wird beidseitig bestätigt | Listing wird X Stunden nach Deal-Bestätigung gelöscht |
| **Manuell** | Seller klickt "Jetzt zerstören" | Sofortige Löschung |
**Standard-Listings** (ohne Selbstzerstörung) verhalten sich wie bisher: Archivierung nach 30 Tagen.
### Löschung — was wird gelöscht?
```
1. Listing-Datensatz (Directus: items.delete)
2. Alle verknüpften Bilder (Directus Files: files.delete)
3. Verifikationsfoto (falls vorhanden)
4. Junction-Table-Einträge (listings_files)
5. Conversations + Messages werden NICHT gelöscht
(die sind E2E verschlüsselt und gehören beiden Parteien)
```
### Datenmodell (Directus)
Neue Felder auf `listings`:
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `self_destruct` | Boolean | `true` wenn Selbstzerstörung aktiv |
| `destruct_mode` | String | `time`, `deal`, `manual` |
| `destruct_at` | DateTime | Zeitpunkt der geplanten Löschung |
| `destruct_after_deal_hours` | Integer | Stunden nach Deal-Bestätigung (Default: 24) |
### Implementierung: Löschung
**Option A: Directus Flow (empfohlen)**
- Neuer Scheduled Flow: alle 15 Minuten (wie Expired-Listings-Flow)
- Query: `self_destruct = true AND destruct_at <= NOW()`
- Action: Delete Listing + zugehörige Files
- Vorteil: Serverseitig, zuverlässig
**Option B: Frontend-Trigger (Fallback)**
- Bei App-Start prüfen: Gibt es eigene Listings mit `destruct_at` in der Vergangenheit?
- Falls ja: Delete-Request an Directus
- Nachteil: Funktioniert nur wenn Seller die App öffnet
**Empfehlung:** Option A als Primär, Option B als zusätzliche Absicherung.
### UX-Flow
```
Listing erstellen:
┌─────────────────────────────────────┐
│ Titel: [___________________] │
│ Preis: [___] Währung: [EUR ▼] │
│ ... │
│ │
│ ───────────────────────────── │
│ 🔥 Selbstzerstörung │
│ │
│ [●] Aus (Standard: Archivierung) │
│ [ ] Nach Zeitablauf │
│ → [24h ▼] / 48h / 7d / 30d │
│ [ ] Nach Deal-Abschluss │
│ → [24] Stunden danach │
│ │
│ ⚠ Achtung: Gelöschte Anzeigen │
│ können nicht wiederhergestellt │
│ werden. │
└─────────────────────────────────────┘
Auf dem Listing (für Käufer):
┌──────────────────────┐
│ 📷 Bild │
│ Titel der Anzeige │
│ 50.00 EUR │
│ 🔥 Läuft ab in 23h │ ← Countdown
│ 📍 Zürich │
└──────────────────────┘
Für Seller (eigenes Listing):
┌──────────────────────────────┐
│ 🔥 Selbstzerstörung aktiv │
│ Wird gelöscht am: 12.02.26 │
│ [Jetzt zerstören] │
└──────────────────────────────┘
```
### Technische Umsetzung
| Komponente | Datei | Beschreibung |
|------------|-------|--------------|
| Service | `js/services/self-destruct.js` | Countdown-Berechnung, Delete-Requests, Deal-Trigger |
| Directus Flow | (Server-Config) | Scheduled Flow: Listings mit `destruct_at <= NOW()` löschen |
| Integration | `js/components/pages/page-create.js` | Selbstzerstörungs-Optionen im Formular |
| Integration | `js/components/pages/page-listing.js` | Countdown-Anzeige, "Jetzt zerstören"-Button |
| Integration | `js/components/listing-card.js` | Countdown-Badge auf Karten |
| CSS | `css/components.css` | Countdown-Badge, Warnungs-Styles |
### Directus Flow: Auto-Delete
```
Trigger: Schedule (*/15 * * * *)
Bedingung: listings WHERE self_destruct = true AND destruct_at <= NOW()
Aktionen:
1. Read: Betroffene Listings + deren File-IDs laden
2. Delete: Files löschen (directus_files)
3. Delete: Junction-Einträge löschen (listings_files)
4. Delete: Listings löschen
```
### Deal-basierte Zerstörung
```
Wenn Deal status → confirmed:
1. Prüfe: Hat das Listing self_destruct = true UND destruct_mode = 'deal'?
2. Ja → Setze destruct_at = NOW() + destruct_after_deal_hours
3. Ab dann greift der Scheduled Flow
```
Integration in `js/services/reputation.js``confirmDeal()`:
```js
// Nach beidseitiger Bestätigung:
if (listing.self_destruct && listing.destruct_mode === 'deal') {
const destructAt = new Date()
destructAt.setHours(destructAt.getHours() + (listing.destruct_after_deal_hours || 24))
await directus.updateItem('listings', listing.id, { destruct_at: destructAt.toISOString() })
}
```
### Privacy-Aspekte
- **Echte Löschung**: Kein Soft-Delete, kein Archiv — Daten werden unwiderruflich entfernt
- **Bilder inklusive**: Files werden aus Directus Storage gelöscht (nicht nur DB-Einträge)
- **Keine Suchmaschinen-Spuren**: `noindex` Meta-Tag für Listings mit Selbstzerstörung
- **Countdown sichtbar**: Käufer sehen, dass das Listing zeitlich begrenzt ist (schafft Urgency)
### i18n Keys
```
destruct.title = "Selbstzerstörung"
destruct.off = "Aus (Standard)"
destruct.time = "Nach Zeitablauf"
destruct.deal = "Nach Deal-Abschluss"
destruct.manual = "Jetzt zerstören"
destruct.duration.24h = "24 Stunden"
destruct.duration.48h = "48 Stunden"
destruct.duration.7d = "7 Tage"
destruct.duration.30d = "30 Tage"
destruct.afterDeal = "{{hours}} Stunden nach Deal"
destruct.warning = "Gelöschte Anzeigen können nicht wiederhergestellt werden."
destruct.active = "Selbstzerstörung aktiv"
destruct.expiresIn = "Läuft ab in {{time}}"
destruct.deletedAt = "Wird gelöscht am {{date}}"
destruct.confirm = "Anzeige unwiderruflich löschen?"
destruct.destroyed = "Diese Anzeige wurde gelöscht"
```
### Aufwand
- **Geschätzt**: 1.52 Tage
- **Dependencies**: Directus Flow (analog zu bestehender Expired-Listings-Flow)
- **Risiko**: Directus File-Löschung muss getestet werden (Cascade)
---
## Zusammenfassung
| # | Feature | Aufwand | Einzigartigkeit | Privacy-Impact |
|---|---------|---------|-----------------|----------------|
| 1 | Blind Meeting Points | 23 Tage | Sehr hoch — kein Konkurrent hat das | Hoch |
| 2 | Verifiable Listings | 12 Tage | Hoch — innovative Lösung ohne KYC | Mittel |
| 3 | Selbstzerstörende Listings | 1.52 Tage | Hoch — starkes Privacy-Signal | Sehr hoch |
**Gesamt: ~57 Tage Implementierung**
### Empfohlene Reihenfolge
1. **Verifiable Listings** — geringster Aufwand, sofort sichtbarer Wert, keine neue Dependency
2. **Selbstzerstörende Listings** — baut auf bestehendem Flow auf, starkes Marketingmerkmal
3. **Blind Meeting Points** — höchster Aufwand, aber stärkstes Alleinstellungsmerkmal