add service for directus; add setup for directus

This commit is contained in:
2026-01-28 12:50:17 +01:00
parent ed270e7050
commit 6c8d686c7f
2 changed files with 1293 additions and 0 deletions

704
docs/DIRECTUS-SETUP.md Normal file
View File

@@ -0,0 +1,704 @@
# 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)](#1-data-models-collections)
2. [User Roles](#2-user-roles)
3. [Access Policies](#3-access-policies)
4. [Flows (Automatisierungen)](#4-flows-automatisierungen)
5. [Einstellungen](#5-einstellungen)
6. [Frontend-Integration](#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:**
```json
{
"read": {
"_and": [
{ "status": { "_eq": "published" } }
]
},
"fields": ["id", "title", "slug", "description", "price", "currency", "price_type", "category", "condition", "images", "location", "shipping", "date_created", "views"]
}
```
**categories:**
```json
{
"read": {
"status": { "_eq": "published" }
},
"fields": "*"
}
```
**locations:**
```json
{
"read": true,
"fields": "*"
}
```
**directus_files:**
```json
{
"read": true
}
```
---
### 3.2 User Role Permissions
**listings:**
```json
{
"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:**
```json
{
"create": true,
"read": {
"_or": [
{ "buyer": { "_eq": "$CURRENT_USER" } },
{ "seller": { "_eq": "$CURRENT_USER" } }
]
},
"update": {
"_or": [
{ "buyer": { "_eq": "$CURRENT_USER" } },
{ "seller": { "_eq": "$CURRENT_USER" } }
]
}
}
```
**messages:**
```json
{
"create": {
"conversation": {
"_or": [
{ "buyer": { "_eq": "$CURRENT_USER" } },
{ "seller": { "_eq": "$CURRENT_USER" } }
]
}
},
"read": {
"conversation": {
"_or": [
{ "buyer": { "_eq": "$CURRENT_USER" } },
{ "seller": { "_eq": "$CURRENT_USER" } }
]
}
}
}
```
**favorites:**
```json
{
"create": true,
"read": {
"user": { "_eq": "$CURRENT_USER" }
},
"delete": {
"user": { "_eq": "$CURRENT_USER" }
}
}
```
**reports:**
```json
{
"create": true
}
```
**directus_files:**
```json
{
"create": true,
"read": true,
"update": {
"uploaded_by": { "_eq": "$CURRENT_USER" }
},
"delete": {
"uploaded_by": { "_eq": "$CURRENT_USER" }
}
}
```
**directus_users (eigenes Profil):**
```json
{
"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:**
```json
{
"read": true,
"update": {
"fields": ["status", "admin_notes"]
}
}
```
**reports:**
```json
{
"read": true,
"update": {
"fields": ["status", "admin_notes", "resolved_by", "resolved_at"]
}
}
```
**directus_users (öffentliche Felder anderer User):**
```json
{
"read": {
"fields": ["id", "first_name", "avatar", "status"]
}
}
```
---
## 4. Flows (Automatisierungen)
### 4.1 Auto-Slug für Listings
**Trigger:** `items.create` auf `listings`
```javascript
// 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`
```javascript
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`
```javascript
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
```javascript
// 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)
```javascript
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):**
```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
```env
# 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
```env
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
```env
RATE_LIMITER_ENABLED=true
RATE_LIMITER_STORE=memory
RATE_LIMITER_POINTS=100
RATE_LIMITER_DURATION=60
```
### 5.6 Email Settings
```env
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
```javascript
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
```javascript
// 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
```javascript
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)
```json
[
{ "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
```json
[
{ "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