# 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 | 1–5 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 (1–5 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" ```