Files
kashilo/docs/REPUTATION.md

381 lines
12 KiB
Markdown
Raw Permalink 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.
# Reputation-System — kashilo.com
## Ziel
Vertrauen zwischen anonymen Nutzern aufbauen, ohne KYC, ohne Escrow.
Trust durch nachweisbares Verhalten statt durch Identität.
---
## Stufensystem
| Level | Bedingung | Badge | Effekt |
|-------|-----------|-------|--------|
| **Neu** | 0 bestätigte Deals | ⚪ | Hinweis "Neuer Account" auf Anzeigen |
| **Aktiv** | 3+ Deals, Account 30+ Tage | 🔵 | Kein Hinweis |
| **Vertrauenswürdig** | 10+ Deals, ⌀ 4.0+ Sterne | 🟢 | Badge auf Anzeigen |
| **Power Seller** | 30+ Deals, ⌀ 4.5+ Sterne | 🟣 | Badge + bevorzugte Platzierung |
---
## Datenmodell
### Directus: Collection `deals`
Bestätigte Transaktionen zwischen zwei Nutzern.
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `id` | UUID | Primary Key |
| `listing_id` | UUID, FK → listings | Bezug zur Anzeige |
| `conversation_id` | UUID, FK → conversations | Bezug zur Konversation |
| `seller_hash` | string(64) | SHA-256 Hash des Verkäufers |
| `buyer_hash` | string(64) | SHA-256 Hash des Käufers |
| `seller_confirmed` | boolean, default false | Verkäufer hat Deal bestätigt |
| `buyer_confirmed` | boolean, default false | Käufer hat Deal bestätigt |
| `status` | string | `pending`, `confirmed`, `disputed` |
| `date_created` | datetime | Erstellungsdatum |
| `date_confirmed` | datetime, nullable | Zeitpunkt der beidseitigen Bestätigung |
Ein Deal gilt als `confirmed`, wenn **beide Seiten** bestätigt haben.
### Directus: Collection `ratings`
Bewertungen nach bestätigtem Deal.
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `id` | UUID | Primary Key |
| `deal_id` | UUID, FK → deals | Bezug zum Deal |
| `rater_hash` | string(64) | Wer bewertet |
| `rated_hash` | string(64) | Wer bewertet wird |
| `score` | integer | 15 Sterne |
| `date_created` | datetime | Zeitpunkt |
Pro Deal kann jeder Teilnehmer **einmal** bewerten (nach Bestätigung).
Kein Freitext-Kommentar — nur Sterne (Privacy).
### Directus: Permissions
| Collection | Public Read | Public Create | Public Update |
|------------|:-----------:|:------------:|:------------:|
| `deals` | Ja (Filter: eigener Hash) | Ja | Ja (nur eigene `*_confirmed`) |
| `ratings` | Ja (Filter: `rated_hash`) | Ja (nur nach bestätigtem Deal) | Nein |
### Computed Fields (Frontend)
Diese Werte werden im Frontend aus `deals` + `ratings` berechnet:
```
reputation = {
deals_completed: count(deals WHERE status = 'confirmed' AND user = seller OR buyer),
avg_rating: avg(ratings WHERE rated_hash = user),
account_age_days: now() - user.date_created,
level: computed from above
}
```
---
## User Flow
### Deal bestätigen (im Chat)
```
1. Käufer und Verkäufer handeln im Chat
2. Eine Seite klickt "Deal abschliessen" → Deal wird erstellt (status: pending)
3. Andere Seite sieht Bestätigungsanfrage im Chat
4. Andere Seite klickt "Deal bestätigen" → status: confirmed
5. Beide Seiten können danach bewerten (15 Sterne)
```
### UI im Chat-Widget
```
┌─────────────────────────────────────┐
│ 💬 Chat mit Verkäufer │
│ │
│ ... Nachrichten ... │
│ │
│ ┌─────────────────────────────┐ │
│ │ 🤝 Deal abschliessen │ │
│ └─────────────────────────────┘ │
│ │
│ Nach beidseitiger Bestätigung: │
│ ┌─────────────────────────────┐ │
│ │ ⭐⭐⭐⭐☆ Bewertung abgeben │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
```
### Badge auf Listing-Card
```
┌──────────────────────┐
│ 📷 Bild │
│ Titel der Anzeige │
│ 50.00 EUR │
│ 📍 Zürich 🟢 10+ │ ← Badge + Deal-Count
└──────────────────────┘
```
### Seller-Card auf Listing-Seite
```
┌──────────────────────────┐
│ ? Anonymer Anbieter │
│ Mitglied seit 2025 │
│ 🟢 Vertrauenswürdig │
│ 12 Deals · ⌀ 4.3 ⭐ │
└──────────────────────────┘
```
---
## Scam-Prävention
| Massnahme | Beschreibung |
|-----------|--------------|
| **Neuer-Account-Warnung** | "Neuer Account — starte mit kleinen Beträgen" |
| **Preis-Limit** | Max. Anzeigenpreis für Level "Neu": 100 EUR/USD/CHF |
| **Cooldown** | Neue Accounts: max. 3 Anzeigen pro Tag |
| **Report-Konsequenz** | 3+ Reports → automatische Prüfung, Anzeigen ausgeblendet |
| **Self-Rating-Schutz** | Nur nach beidseitig bestätigtem Deal bewertbar |
---
## Implementierung
### Phase 1: Backend (Directus)
- [ ] Collection `deals` anlegen (siehe Anleitung unten)
- [ ] Collection `ratings` anlegen (siehe Anleitung unten)
- [ ] Permissions setzen (Public-Rolle, siehe unten)
- [ ] Flow: Notification bei Deal-Bestätigung (optional)
---
## Directus: Collection `deals` anlegen
```
Settings > Data Model > + Create Collection
Name: deals
Primary Key: UUID (auto-generated)
```
| Feld | Typ | Interface | Einstellungen |
|------|-----|-----------|---------------|
| `id` | UUID | | Primary Key, auto-generated |
| `listing` | UUID (String) | Input | Listing-ID (kein FK, da Public-Rolle keinen Zugriff auf M2O hat) |
| `conversation` | UUID (String) | Input | Conversation-ID |
| `seller_hash` | String | Input | Required, max 64 chars |
| `buyer_hash` | String | Input | Required, max 64 chars |
| `seller_confirmed` | Boolean | Toggle | Default: `false` |
| `buyer_confirmed` | Boolean | Toggle | Default: `false` |
| `status` | String | Dropdown | `pending`, `confirmed`, `disputed` (Default: `pending`) |
| `date_created` | DateTime | DateTime | Auto, Read-only (Directus: "On Create" → Save Current Date/Time) |
| `date_confirmed` | DateTime | DateTime | Nullable, kein Default |
### Schritt-für-Schritt
1. **Collection erstellen:**
- Settings → Data Model → `+ Create Collection`
- Name: `deals`
- Primary Key Field: Type `UUID`, Auto-generated: ✓
- Optional Fields: ✓ `date_created`
2. **Felder anlegen (in dieser Reihenfolge):**
**listing** (String):
- Interface: Input
- Schema: String, max length 36
- Nicht nullable
**conversation** (String):
- Interface: Input
- Schema: String, max length 36
- Nicht nullable
**seller_hash** (String):
- Interface: Input
- Schema: String, max length 64
- Nicht nullable
**buyer_hash** (String):
- Interface: Input
- Schema: String, max length 64
- Nicht nullable
**seller_confirmed** (Boolean):
- Interface: Toggle
- Schema: Boolean, Default: `false`
**buyer_confirmed** (Boolean):
- Interface: Toggle
- Schema: Boolean, Default: `false`
**status** (String):
- Interface: Dropdown
- Options: `pending`, `confirmed`, `disputed`
- Schema: String, Default: `pending`
**date_confirmed** (DateTime):
- Interface: DateTime
- Schema: Timestamp, Nullable: ✓
---
## Directus: Collection `ratings` anlegen
```
Settings > Data Model > + Create Collection
Name: ratings
Primary Key: UUID (auto-generated)
```
| Feld | Typ | Interface | Einstellungen |
|------|-----|-----------|---------------|
| `id` | UUID | | Primary Key, auto-generated |
| `deal` | UUID (String) | Input | Deal-ID |
| `rater_hash` | String | Input | Required, max 64 chars, wer bewertet |
| `rated_hash` | String | Input | Required, max 64 chars, wer bewertet wird |
| `score` | Integer | Slider | Min: 1, Max: 5 |
| `date_created` | DateTime | DateTime | Auto, Read-only |
### Schritt-für-Schritt
1. **Collection erstellen:**
- Settings → Data Model → `+ Create Collection`
- Name: `ratings`
- Primary Key Field: Type `UUID`, Auto-generated: ✓
- Optional Fields: ✓ `date_created`
2. **Felder anlegen:**
**deal** (String):
- Interface: Input
- Schema: String, max length 36
- Nicht nullable
**rater_hash** (String):
- Interface: Input
- Schema: String, max length 64
- Nicht nullable
**rated_hash** (String):
- Interface: Input
- Schema: String, max length 64
- Nicht nullable
**score** (Integer):
- Interface: Slider
- Options: Min `1`, Max `5`, Step `1`
- Schema: Integer, nicht nullable
---
## Directus: Permissions (Public-Rolle)
```
Settings > Access Control > Public
```
### Collection `deals`
**Read:**
- ✓ Erlaubt
- Felder: Alle
- Filter: Eigene Deals sehen
```json
{
"_or": [
{ "seller_hash": { "_eq": "$CURRENT_USER" } },
{ "buyer_hash": { "_eq": "$CURRENT_USER" } }
]
}
```
**Hinweis:** Da wir SHA-256 Hashes nutzen (nicht Directus-User-IDs), funktioniert `$CURRENT_USER` hier NICHT.
Stattdessen: **Keine Read-Filter setzen** (alle Deals lesbar). Die Filterung passiert im Frontend via API-Query.
Alternativ: Kein Filter, aber nur bestimmte Felder freigeben (z.B. `seller_hash`, `buyer_hash`, `status`, `date_confirmed` — ohne `conversation`, `listing`).
**Empfohlene Read-Permissions (ohne Filter, mit Feld-Einschränkung):**
- Felder: `id`, `listing`, `conversation`, `seller_hash`, `buyer_hash`, `seller_confirmed`, `buyer_confirmed`, `status`, `date_created`, `date_confirmed`
**Create:**
- ✓ Erlaubt
- Felder: `listing`, `conversation`, `seller_hash`, `buyer_hash`, `seller_confirmed`, `buyer_confirmed`, `status`
- Kein Validation-Filter nötig (Frontend validiert)
**Update:**
- ✓ Erlaubt
- Felder: **Nur** `seller_confirmed`, `buyer_confirmed`, `status`, `date_confirmed`
- **Kein Custom-Filter** (da `$CURRENT_USER` nicht mit Hashes funktioniert)
- Schutz: Im Frontend wird geprüft ob der User Teilnehmer ist
**Delete:**
- ✗ Nicht erlaubt
### Collection `ratings`
**Read:**
- ✓ Erlaubt
- Felder: `id`, `deal`, `rater_hash`, `rated_hash`, `score`, `date_created`
- Kein Filter (Ratings sind öffentlich sichtbar für Reputation)
**Create:**
- ✓ Erlaubt
- Felder: `deal`, `rater_hash`, `rated_hash`, `score`
**Update:**
- ✗ Nicht erlaubt (Bewertungen sind final)
**Delete:**
- ✗ Nicht erlaubt
---
## Directus: DIRECTUS-SCHEMA.md aktualisieren
Nach dem Anlegen diese Tabellen in `docs/DIRECTUS-SCHEMA.md` ergänzen und die Permissions-Tabelle erweitern:
```
| `deals` | ✓ | ✓ | ✓ | - | Update nur confirmed/status Felder |
| `ratings` | ✓ | ✓ | - | - | Bewertungen sind final |
```
### Phase 2: Service (Frontend)
- [x] `js/services/reputation.js` — Deals/Ratings CRUD, Level-Berechnung, Caching
- [x] i18n-Keys in allen 7 Sprachen
- [ ] Integration in `conversations.js` — Deal-Bestätigung im Chat
### Phase 3: UI
- [x] Chat-Widget: "Deal abschliessen" Button + Bestätigungsanfrage
- [x] Chat-Widget: Sterne-Bewertung nach bestätigtem Deal
- [ ] Listing-Card: Badge + Deal-Count
- [x] Listing-Page Seller-Card: Level, Deals, Rating
- [x] Neuer-Account-Warnung auf Listing-Page
### Phase 4: Scam-Prävention
- [ ] Preis-Limit für neue Accounts
- [ ] Anzeigen-Cooldown für neue Accounts
- [ ] Report-Integration mit Konsequenzen
---
## i18n Keys (benötigt)
```
reputation.level.new = "Neuer Account"
reputation.level.active = "Aktiv"
reputation.level.trusted = "Vertrauenswürdig"
reputation.level.power = "Power Seller"
reputation.deals = "{{count}} Deals"
reputation.dealsSingular = "1 Deal"
reputation.avgRating = "⌀ {{rating}} ⭐"
reputation.newWarning = "Neuer Account — starte mit kleinen Beträgen"
reputation.confirmDeal = "Deal abschliessen"
reputation.dealPending = "Warte auf Bestätigung"
reputation.dealConfirmed = "Deal bestätigt"
reputation.rate = "Bewertung abgeben"
reputation.rated = "Bewertet"
```