Files
kashilo/docs/DIRECTUS-SETUP.md

15 KiB

Directus Setup für dgray.io

Komplette Anleitung zur Einrichtung von Directus als Backend für die dgray Kleinanzeigen-PWA.

API URL: https://api.dgray.io/


Inhaltsverzeichnis

  1. Data Models (Collections)
  2. User Roles
  3. Access Policies
  4. Flows (Automatisierungen)
  5. Einstellungen
  6. Frontend-Integration

1. Data Models (Collections)

1.1 listings (Anzeigen)

Die Haupt-Collection für alle Kleinanzeigen.

Feld Typ Einstellungen
id UUID Primary Key, auto-generated
status String (Dropdown) draft, published, sold, expired, deleted
sort Integer Für manuelle Sortierung
user_created User (M2O) Auto, Read-only
date_created DateTime Auto, Read-only
user_updated User (M2O) Auto
date_updated DateTime Auto
title String Required, max 100 chars
slug String Unique, auto-generated from title
description Text (WYSIWYG) Required
price Decimal Required, precision 10, scale 2
currency String Default: XMR, Options: XMR, EUR
price_type String fixed, negotiable, free, on_request
category Categories (M2O) Required
condition String new, like_new, good, fair, poor
images Files (M2M) Junction: listings_files
location Locations (M2O) Optional
shipping Boolean Versand möglich?
shipping_cost Decimal Optional
views Integer Default: 0
expires_at DateTime Auto-set, 30 Tage nach Erstellung
monero_address String Für Direktzahlung
contact_method String chat, email, both

Erstellen in Directus Admin:

Settings > Data Model > + Create Collection
Name: listings
Primary Key: UUID (auto-generated)

1.2 categories (Kategorien)

Hierarchische Kategorien mit Übersetzungen.

Feld Typ Einstellungen
id UUID Primary Key
status String draft, published
sort Integer Für Sortierung
name String Required (Fallback-Name)
slug String Unique
icon String Icon-Name (z.B. laptop, car)
parent Categories (M2O) Self-referencing
translations Translations Junction: categories_translations

Translations Fields:

Feld Typ
languages_code String (de, en, fr)
name String
description Text

1.3 conversations (Konversationen)

Chat zwischen Käufer und Verkäufer.

Feld Typ Einstellungen
id UUID Primary Key
date_created DateTime Auto
date_updated DateTime Auto
listing Listings (M2O) Required
buyer User (M2O) Wer die Konversation gestartet hat
seller User (M2O) Auto from listing.user_created
status String active, archived, blocked
messages Messages (O2M)

1.4 messages (Nachrichten)

Feld Typ Einstellungen
id UUID Primary Key
date_created DateTime Auto
conversation Conversations (M2O) Required
sender User (M2O) Auto (user_created)
content Text Required, max 2000 chars
read_at DateTime Null = ungelesen
type String text, offer, system

1.5 favorites (Favoriten/Merkliste)

Feld Typ Einstellungen
id UUID Primary Key
date_created DateTime Auto
user User (M2O) Auto (user_created)
listing Listings (M2O) Required

Unique Constraint: user + listing


1.6 reports (Meldungen)

Feld Typ Einstellungen
id UUID Primary Key
date_created DateTime Auto
reporter User (M2O) Auto
listing Listings (M2O) Optional
reported_user User (M2O) Optional
reason String spam, fraud, inappropriate, illegal, other
details Text
status String pending, reviewed, resolved, dismissed
admin_notes Text Nur für Admins sichtbar
resolved_by User (M2O)
resolved_at DateTime

1.7 locations (Orte)

Feld Typ Einstellungen
id UUID Primary Key
name String Stadt/Ort
postal_code String PLZ
region String Bundesland/Kanton
country String Default: DE
latitude Float Optional, für Kartenansicht
longitude Float Optional

1.8 listings_files (Junction Table)

Verknüpfung Listings ↔ Dateien (für mehrere Bilder pro Anzeige).

Feld Typ
id Integer (auto)
listings_id Listings (M2O)
directus_files_id Files (M2O)
sort Integer

2. User Roles

2.1 Rollen-Übersicht

Rolle Beschreibung App Access Admin Access
Administrator Vollzugriff
Moderator Content-Moderation (eingeschränkt)
User Registrierte Nutzer
Public Nicht angemeldet

2.2 Rollen erstellen

Settings > Access Control > Roles > + Create Role

Administrator

Name: Administrator
Description: Vollzugriff auf alle Funktionen
Icon: shield
Admin Access: ON
App Access: ON

Moderator

Name: Moderator  
Description: Kann Inhalte moderieren und Reports bearbeiten
Icon: eye
Admin Access: ON (mit eingeschränkten Permissions)
App Access: ON

User

Name: User
Description: Registrierte Benutzer
Icon: account_circle
Admin Access: OFF
App Access: ON

3. Access Policies

3.1 Public (Nicht angemeldet)

listings:

{
  "read": {
    "_and": [
      { "status": { "_eq": "published" } }
    ]
  },
  "fields": ["id", "title", "slug", "description", "price", "currency", "price_type", "category", "condition", "images", "location", "shipping", "date_created", "views"]
}

categories:

{
  "read": {
    "status": { "_eq": "published" }
  },
  "fields": "*"
}

locations:

{
  "read": true,
  "fields": "*"
}

directus_files:

{
  "read": true
}

3.2 User Role Permissions

listings:

{
  "create": true,
  "read": {
    "_or": [
      { "status": { "_eq": "published" } },
      { "user_created": { "_eq": "$CURRENT_USER" } }
    ]
  },
  "update": {
    "user_created": { "_eq": "$CURRENT_USER" }
  },
  "delete": {
    "_and": [
      { "user_created": { "_eq": "$CURRENT_USER" } },
      { "status": { "_in": ["draft", "expired"] } }
    ]
  }
}

conversations:

{
  "create": true,
  "read": {
    "_or": [
      { "buyer": { "_eq": "$CURRENT_USER" } },
      { "seller": { "_eq": "$CURRENT_USER" } }
    ]
  },
  "update": {
    "_or": [
      { "buyer": { "_eq": "$CURRENT_USER" } },
      { "seller": { "_eq": "$CURRENT_USER" } }
    ]
  }
}

messages:

{
  "create": {
    "conversation": {
      "_or": [
        { "buyer": { "_eq": "$CURRENT_USER" } },
        { "seller": { "_eq": "$CURRENT_USER" } }
      ]
    }
  },
  "read": {
    "conversation": {
      "_or": [
        { "buyer": { "_eq": "$CURRENT_USER" } },
        { "seller": { "_eq": "$CURRENT_USER" } }
      ]
    }
  }
}

favorites:

{
  "create": true,
  "read": {
    "user": { "_eq": "$CURRENT_USER" }
  },
  "delete": {
    "user": { "_eq": "$CURRENT_USER" }
  }
}

reports:

{
  "create": true
}

directus_files:

{
  "create": true,
  "read": true,
  "update": {
    "uploaded_by": { "_eq": "$CURRENT_USER" }
  },
  "delete": {
    "uploaded_by": { "_eq": "$CURRENT_USER" }
  }
}

directus_users (eigenes Profil):

{
  "read": {
    "id": { "_eq": "$CURRENT_USER" }
  },
  "update": {
    "id": { "_eq": "$CURRENT_USER" }
  },
  "fields": ["id", "email", "first_name", "last_name", "avatar", "status"]
}

3.3 Moderator Role Permissions

Zusätzlich zu User-Permissions:

listings:

{
  "read": true,
  "update": {
    "fields": ["status", "admin_notes"]
  }
}

reports:

{
  "read": true,
  "update": {
    "fields": ["status", "admin_notes", "resolved_by", "resolved_at"]
  }
}

directus_users (öffentliche Felder anderer User):

{
  "read": {
    "fields": ["id", "first_name", "avatar", "status"]
  }
}

4. Flows (Automatisierungen)

4.1 Auto-Slug für Listings

Trigger: items.create auf listings

// Operation: Run Script
module.exports = async function(data) {
    const title = data.title;
    const slug = title
        .toLowerCase()
        .replace(/[äöüß]/g, match => ({ä:'ae',ö:'oe',ü:'ue',ß:'ss'}[match]))
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/(^-|-$)/g, '');
    
    return { slug: `${slug}-${Date.now().toString(36)}` };
}

Operation: Update Data → listings mit Ergebnis


4.2 Auto-Set Expiry Date

Trigger: items.create auf listings

module.exports = async function(data) {
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 30);
    return { expires_at: expiresAt.toISOString() };
}

4.3 Set Seller on Conversation Create

Trigger: items.create auf conversations

module.exports = async function(data, { database }) {
    const listing = await database('listings')
        .where('id', data.listing)
        .first();
    
    return { seller: listing.user_created };
}

4.4 Increment View Counter

Trigger: Custom Endpoint oder Hook

// Webhook/Endpoint für View-Tracking
module.exports = async function(data, { database }) {
    await database('listings')
        .where('id', data.listing_id)
        .increment('views', 1);
}

4.5 Auto-Expire Listings (Scheduled)

Trigger: Schedule (täglich um 00:00)

module.exports = async function(data, { database }) {
    const now = new Date().toISOString();
    
    await database('listings')
        .where('expires_at', '<', now)
        .where('status', 'published')
        .update({ status: 'expired' });
}

4.6 Welcome Email bei Registrierung

Trigger: users.create

Operation: Send Email

To: {{$trigger.email}}
Subject: Willkommen bei dgray.io
Template: welcome-email

5. Einstellungen

5.1 Project Settings

Settings > Project Settings

Project Name: dgray.io
Project URL: https://dgray.io
Project Color: #555555

5.2 CORS Settings

Environment Variables (.env):

CORS_ENABLED=true
CORS_ORIGIN=https://dgray.io,https://www.dgray.io,http://localhost:8080
CORS_METHODS=GET,POST,PATCH,DELETE
CORS_ALLOWED_HEADERS=Content-Type,Authorization
CORS_CREDENTIALS=true

5.3 Auth Settings

# Token Expiry
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=7d

# Password Policy
AUTH_PASSWORD_POLICY=/^.{8,}$/

# Disable Public Registration (optional)
# USER_REGISTER_URL_ALLOW_LIST=https://dgray.io

5.4 File Storage

STORAGE_LOCATIONS=local
STORAGE_LOCAL_ROOT=./uploads

# Für S3/Cloudflare R2:
# STORAGE_LOCATIONS=s3
# STORAGE_S3_DRIVER=s3
# STORAGE_S3_KEY=xxx
# STORAGE_S3_SECRET=xxx
# STORAGE_S3_BUCKET=dgray-files
# STORAGE_S3_REGION=auto
# STORAGE_S3_ENDPOINT=https://xxx.r2.cloudflarestorage.com

5.5 Rate Limiting

RATE_LIMITER_ENABLED=true
RATE_LIMITER_STORE=memory
RATE_LIMITER_POINTS=100
RATE_LIMITER_DURATION=60

5.6 Email Settings

EMAIL_FROM=noreply@dgray.io
EMAIL_TRANSPORT=smtp
EMAIL_SMTP_HOST=smtp.example.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USER=xxx
EMAIL_SMTP_PASSWORD=xxx
EMAIL_SMTP_SECURE=false

6. Frontend-Integration

6.1 Service einbinden

import { directus, DirectusError } from './services/directus.js';

// Login
try {
    await directus.login('user@example.com', 'password');
    console.log('Logged in!');
} catch (error) {
    if (error instanceof DirectusError) {
        console.error('Login failed:', error.message);
    }
}

// Listings laden
const { items, meta } = await directus.getListings({
    limit: 20,
    page: 1
});

// Listing erstellen
const newListing = await directus.createListing({
    title: 'iPhone 15 Pro',
    description: 'Neuwertig, OVP',
    price: 0.5,
    currency: 'XMR',
    category: 'electronics-id',
    condition: 'like_new'
});

6.2 Bilder anzeigen

// Thumbnail URL
const thumbUrl = directus.getThumbnailUrl(fileId, 300);

// Optimiertes Bild
const imageUrl = directus.getFileUrl(fileId, {
    width: 800,
    height: 600,
    fit: 'cover',
    quality: 80,
    format: 'webp'
});

6.3 Auth State prüfen

if (directus.isAuthenticated()) {
    const user = await directus.getCurrentUser();
    console.log('Logged in as:', user.email);
} else {
    // Redirect to login
}

7. Initiale Daten

7.1 Kategorien (Beispiel)

[
  { "name": "Elektronik", "slug": "elektronik", "icon": "devices" },
  { "name": "Fahrzeuge", "slug": "fahrzeuge", "icon": "directions_car" },
  { "name": "Immobilien", "slug": "immobilien", "icon": "home" },
  { "name": "Mode & Accessoires", "slug": "mode", "icon": "checkroom" },
  { "name": "Haus & Garten", "slug": "haus-garten", "icon": "yard" },
  { "name": "Freizeit & Hobby", "slug": "freizeit", "icon": "sports_esports" },
  { "name": "Jobs", "slug": "jobs", "icon": "work" },
  { "name": "Dienstleistungen", "slug": "dienstleistungen", "icon": "handyman" },
  { "name": "Sonstiges", "slug": "sonstiges", "icon": "more_horiz" }
]

7.2 Sub-Kategorien Elektronik

[
  { "name": "Smartphones", "slug": "smartphones", "parent": "elektronik-id" },
  { "name": "Computer & Laptops", "slug": "computer", "parent": "elektronik-id" },
  { "name": "TV & Audio", "slug": "tv-audio", "parent": "elektronik-id" },
  { "name": "Foto & Video", "slug": "foto-video", "parent": "elektronik-id" },
  { "name": "Gaming", "slug": "gaming", "parent": "elektronik-id" },
  { "name": "Zubehör", "slug": "zubehoer", "parent": "elektronik-id" }
]

8. Checkliste

  • Collections erstellen (listings, categories, etc.)
  • Felder konfigurieren (Typen, Validierung)
  • Rollen anlegen (Admin, Moderator, User)
  • Access Policies für jede Rolle setzen
  • Public Access für Listings/Categories aktivieren
  • Flows für Auto-Slug, Expiry, etc. erstellen
  • CORS für Frontend-Domain konfigurieren
  • Email-Transport einrichten
  • Kategorien importieren
  • Test-User erstellen
  • Frontend mit directus.js Service verbinden

9. Sicherheitshinweise

  1. Monero-Adressen werden nur dem Listing-Ersteller und authentifizierten Nutzern angezeigt
  2. Keine echten E-Mails in Public-Responses - nur über Chat-System
  3. Rate Limiting für API-Endpunkte aktivieren
  4. Bilder-Upload auf max. 10MB und erlaubte Typen beschränken
  5. XSS-Schutz für WYSIWYG-Felder in Directus aktivieren