Files
kashilo/docs/REPUTATION.md

12 KiB
Raw Blame History

Reputation-System — dgray.io

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
    {
      "_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)

  • js/services/reputation.js — Deals/Ratings CRUD, Level-Berechnung, Caching
  • i18n-Keys in allen 7 Sprachen
  • Integration in conversations.js — Deal-Bestätigung im Chat

Phase 3: UI

  • Chat-Widget: "Deal abschliessen" Button + Bestätigungsanfrage
  • Chat-Widget: Sterne-Bewertung nach bestätigtem Deal
  • Listing-Card: Badge + Deal-Count
  • Listing-Page Seller-Card: Level, Deals, Rating
  • 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"