Files
kashilo/docs/DIRECTUS-SCHEMA.md

12 KiB

Directus Schema Documentation

Directus Version: 11.14.1
Database: PostgreSQL
API Endpoint: https://api.kashilo.com

Collections Overview

Collection Beschreibung
listings Haupttabelle für Anzeigen
listings_files Junction-Table für Listing-Bilder
categories Kategorien mit Übersetzungen
categories_translations Kategorie-Übersetzungen (i18n)
locations Standorte für Anzeigen
languages Verfügbare Sprachen
conversations Chat-Konversationen
messages Chat-Nachrichten
favorites Favoriten (User-Listing Relation)
reports Meldungen/Beschwerden

listings

Haupttabelle für alle Anzeigen.

Feld Typ Beschreibung
id UUID Primary Key
status string draft, published, archived
sort integer Sortierung
title string Titel der Anzeige
slug string URL-freundlicher Titel
description text Beschreibung
price decimal Preis
currency string Währung: EUR, CHF, USD, XMR
price_mode string fiat oder xmr (Referenzwährung)
price_type string fixed, negotiable, free
condition string new, like_new, good, fair, poor
shipping boolean Versand möglich
shipping_cost decimal Versandkosten
views integer Aufrufzähler
expires_at datetime Ablaufdatum
monero_address string XMR-Adresse für Zahlung
contact_public_key text NaCl Public Key für E2E-Chat (pro Listing)
verified boolean false default — Verifikation abgeschlossen
verification_code string(6) 6-stelliger Klartext-Code (für Käufer-Vergleich mit Foto)
verification_image UUID FK → directus_files (Verifikationsfoto)
verification_date datetime Zeitpunkt der Verifikation
date_created datetime Erstellungsdatum
date_updated datetime Änderungsdatum
user_created UUID Ersteller (FK → directus_users)
category UUID Kategorie (FK → categories)
location UUID Standort (FK → locations)
images o2m Bilder (→ listings_files)

listings_files

Junction-Table für Listing-Bilder (Many-to-Many).

Feld Typ Beschreibung
id integer Primary Key
listings_id UUID FK → listings
directus_files_id UUID FK → directus_files
sort integer Sortierung der Bilder

categories

Kategorien mit hierarchischer Struktur.

Feld Typ Beschreibung
id UUID Primary Key
status string draft, published, archived
sort integer Sortierung
name string Kategorie-Name (Fallback)
slug string URL-freundlicher Name
icon string Icon (Emoji oder Icon-Name)
parent UUID Parent-Kategorie (FK → categories)
translations o2m Übersetzungen (→ categories_translations)

categories_translations

Übersetzungen für Kategorien.

Feld Typ Beschreibung
id integer Primary Key
categories_id UUID FK → categories
languages_code string Sprachcode (de-DE, en-US, fr-FR, it-IT, es-ES, pt-BR, ru-RU)
name string Übersetzter Name

locations

Standorte für Anzeigen.

Feld Typ Beschreibung
id UUID Primary Key
name string Ortsname
postal_code string Postleitzahl
region string Region/Kanton
country string Land: DE, AT, CH, FR, IT, LI
latitude float Breitengrad
longitude float Längengrad

languages

Verfügbare Sprachen.

Feld Typ Beschreibung
code string Primary Key, z.B. de-DE, en-US, fr-FR, it-IT, es-ES, pt-BR, ru-RU
name string Sprachname
direction string Textrichtung: ltr oder rtl

conversations

Zero-Knowledge Chat-Konversationen zwischen anonymen Usern.

Feld Typ Beschreibung
id UUID Primary Key
listing_id UUID FK → listings
participant_hash_1 string(64) SHA-256 Hash des ersten Teilnehmers (Käufer)
participant_hash_2 string(64) SHA-256 Hash des zweiten Teilnehmers (Verkäufer)
public_key_1 text NaCl Public Key Teilnehmer 1
public_key_2 text NaCl Public Key Teilnehmer 2
status string active, closed
buyer_user UUID FK → directus_users, Buyer's Directus user ID
date_created datetime Erstellungsdatum
date_updated datetime Letzte Nachricht

Hinweis: Die participant_hash_* Felder ermöglichen anonyme Zuordnung ohne Directus-User-Accounts. Das buyer_user Feld speichert die Directus-User-ID des Käufers und wird bei Conversation-Erstellung gesetzt.


messages

E2E-verschlüsselte Nachrichten in Konversationen.

Feld Typ Beschreibung
id UUID Primary Key
conversation UUID FK → conversations
sender_hash string(64) SHA-256 Hash des Senders
content_encrypted text NaCl-verschlüsselter Inhalt
nonce string Nonce für Entschlüsselung
type string text, image, system
date_created datetime Zeitstempel

Hinweis: Nachrichten sind client-seitig E2E-verschlüsselt. Server sieht nur Ciphertext.


favorites

User-Favoriten.

Feld Typ Beschreibung
id UUID Primary Key
user UUID FK → directus_users
listing UUID FK → listings
date_created datetime Hinzugefügt am

directus_users (Custom Fields)

Zusätzliche Felder für User-Einstellungen.

Feld Typ Beschreibung
preferred_currency string Bevorzugte Währung: USD, EUR, CHF (Default: USD)
preferred_locale string Bevorzugte Sprache: de-DE, en-US, fr-FR, it-IT, es-ES, pt-BR, ru-RU

Hinweis: Diese Felder müssen in Directus unter Settings → Data Model → directus_users angelegt werden.


reports

Meldungen von Anzeigen.

Feld Typ Beschreibung
id UUID Primary Key
listing UUID FK → listings
reporter UUID FK → directus_users
reason string Grund der Meldung
description text Details
status string pending, reviewed, resolved
date_created datetime Meldungsdatum

Public Role Permissions

Collection Read Create Update Delete Hinweise
listings - Siehe Details unten
listings_files - - Für Bilder-Upload
directus_files - - Asset-Upload
categories - - - Nur status=published
categories_translations - - - Für i18n
locations - - User kann neue Orte anlegen
languages - - - Für Sprach-Auswahl
conversations - - - - Nur User-Rolle (s.u.)
messages - - - - Nur User-Rolle (s.u.)
favorites - Nur eigene
reports - - - Nur erstellen

Listings Update-Berechtigungen — Public Role (Detail)

Custom Filter:

{ "user_created": { "_eq": "$CURRENT_USER" } }

Field Permissions (Update):

  • title, slug, description
  • price, currency, price_mode, price_type
  • category, condition, location
  • shipping, shipping_cost
  • monero_address
  • contact_public_key
  • images
  • views (geschützt durch Flow)
  • verified, verification_code, verification_image, verification_date

Read Filter:

{ "status": { "_eq": "published" } }

Hinweis: Die Felder paid_at, payment_status und btcpay_invoice_id sind in der Public-Rolle nicht lesbar.

User Role: Listings (zusätzliche Felder)

Eingeloggte User haben zusätzlich zu den Public-Feldern Zugriff auf:

Read (zusätzlich):

  • paid_at, payment_status, btcpay_invoice_id

Update:

  • Gleiche Felder wie Public Update
  • Filter: user_created = $CURRENT_USER

Hinweis: paid_at und payment_status dürfen NICHT in der Public-Rolle lesbar sein, da sonst getListing() für nicht-eingeloggte Besucher fehlschlägt.


Directus Flows

Increment Listing Views

Zweck: Sichert ab, dass views nur inkrementiert (nicht beliebig gesetzt) werden kann.

Schritt Typ Beschreibung
1. Trigger Action (Non-Blocking) items.update auf listings
2. Condition Filter Rule Prüft ob views im Payload vorhanden
3. Operation Run Script Prüft ob nur views geändert wurde
4. Condition Filter Rule Prüft Script-Ergebnis

Schritt 2 - Condition Rule (views vorhanden):

{
  "$trigger": {
    "payload": {
      "views": {
        "_nnull": true
      }
    }
  }
}

Schritt 3 - Run Script (nur views im Payload):

module.exports = async function(data) {
  const keys = Object.keys(data.$trigger.payload)
  return keys.length === 1 && keys[0] === 'views' ? 1 : 0
}

Schritt 4 - Condition Rule (Script-Ergebnis prüfen):

{
  "$last": {
    "_eq": 1
  }
}

Hinweis: Ohne diese Absicherung könnte jeder views auf beliebige Werte setzen.

Archive Expired Listings

Zweck: Archiviert abgelaufene Listings automatisch.

Schritt Typ Beschreibung
1. Trigger Schedule */15 * * * * Alle 15 Minuten
2. Operation Update Data status → archived wenn expires_at < NOW

Notify: Listing Published (Deaktiviert)

Status: Deaktiviert — wird jetzt direkt von btcpay-webhook.php gehandhabt (mit Duplikat-Check: prüft auf existierende Notification bevor eine neue erstellt wird, verarbeitet nur InvoiceSettled).

Notify: New Message

Zweck: Erstellt eine Benachrichtigung für den Empfänger einer neuen Chat-Nachricht.

Schritt Typ Beschreibung
1. Trigger Event Hook items.create auf messages
2. Read Data Read Data Conversation laden (anhand conversation ID aus Payload)
3. Read Data Read Data Listing laden (anhand listing_id aus Conversation)
4. Run Script Run Script Empfänger ermitteln (sender_hashparticipant_hash_1/2 → anderer ist Empfänger)
5. Condition Filter Rule Prüft ob Empfänger ermittelt wurde
6. Create Data Create Data Notification erstellen für Empfänger

Wichtig: Die Read Data und Create Data Operations benötigen $full Permissions (nicht $trigger), da sie auf Collections zugreifen die nicht im Trigger-Context verfügbar sind.


User Role Permissions (Chat)

Conversations und Messages sind nicht über die Public-Rolle zugänglich. Nur eingeloggte User (User-Rolle) dürfen diese Collections nutzen.

conversations (User-Rolle)

Aktion Erlaubt Filter / Einschränkung
Read Alle (Client filtert via participant_hash)
Create Felder inkl. buyer_user
Update Nur status, buyer_user Felder, kein Row-Filter
Delete - Nicht erlaubt

Hinweis: Im Zero-Knowledge-Design gibt es kein server-verifizierbares Feld, das beide Teilnehmer identifiziert (participant_hash ist nicht an einen Directus-User gebunden). Deshalb kann kein $CURRENT_USER-Filter für Read/Update gesetzt werden. Die Absicherung erfolgt durch: (1) Authentifizierungspflicht (Public hat keinen Zugriff), (2) Client-seitige Filterung via participant_hash, (3) Update nur auf status und buyer_user Felder beschränkt, (4) Conversation-UUIDs sind nicht erratbar.

messages (User-Rolle)

Aktion Erlaubt Filter / Einschränkung
Read Alle (Inhalte sind E2E-verschlüsselt)
Create Keine Einschränkung
Update - Nicht erlaubt
Delete - Nicht erlaubt

Sicherheitsmodell:

  • Nachrichten sind client-seitig E2E-verschlüsselt — Server sieht nur Ciphertext
  • Ohne Private Key kann niemand Nachrichten entschlüsseln
  • Public-Rolle hat keinen Zugriff auf conversations und messages
  • Nur eingeloggte User können Conversations erstellen und Nachrichten senden