12 KiB
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 | 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
dealsanlegen (siehe Anleitung unten) - Collection
ratingsanlegen (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
-
Collection erstellen:
- Settings → Data Model →
+ Create Collection - Name:
deals - Primary Key Field: Type
UUID, Auto-generated: ✓ - Optional Fields: ✓
date_created
- Settings → Data Model →
-
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
-
Collection erstellen:
- Settings → Data Model →
+ Create Collection - Name:
ratings - Primary Key Field: Type
UUID, Auto-generated: ✓ - Optional Fields: ✓
date_created
- Settings → Data Model →
-
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, Max5, Step1 - Schema: Integer, nicht nullable
Directus: Permissions (Public-Rolle)
Settings > Access Control > Public
Collection deals
Read:
- ✓ Erlaubt
- Felder: Alle
- Filter: Eigene Deals sehen
Hinweis: Da wir SHA-256 Hashes nutzen (nicht Directus-User-IDs), funktioniert
{ "_or": [ { "seller_hash": { "_eq": "$CURRENT_USER" } }, { "buyer_hash": { "_eq": "$CURRENT_USER" } } ] }$CURRENT_USERhier 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— ohneconversation,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_USERnicht 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"