improve DIRECTUS-SETUP
This commit is contained in:
@@ -27,13 +27,10 @@ Komplette Anleitung zur Einrichtung von Directus als Backend für die dgray Klei
|
|||||||
erDiagram
|
erDiagram
|
||||||
USERS ||--o{ LISTINGS : creates
|
USERS ||--o{ LISTINGS : creates
|
||||||
USERS ||--o{ FAVORITES : has
|
USERS ||--o{ FAVORITES : has
|
||||||
USERS ||--o{ CONVERSATIONS : participates
|
|
||||||
USERS ||--o{ MESSAGES : sends
|
|
||||||
USERS ||--o{ REPORTS : submits
|
USERS ||--o{ REPORTS : submits
|
||||||
|
|
||||||
LISTINGS ||--o{ LISTINGS_FILES : has
|
LISTINGS ||--o{ LISTINGS_FILES : has
|
||||||
LISTINGS ||--o{ FAVORITES : receives
|
LISTINGS ||--o{ FAVORITES : receives
|
||||||
LISTINGS ||--o{ CONVERSATIONS : has
|
|
||||||
LISTINGS }o--|| CATEGORIES : belongs_to
|
LISTINGS }o--|| CATEGORIES : belongs_to
|
||||||
LISTINGS }o--o| LOCATIONS : located_at
|
LISTINGS }o--o| LOCATIONS : located_at
|
||||||
|
|
||||||
@@ -66,18 +63,21 @@ erDiagram
|
|||||||
|
|
||||||
CONVERSATIONS {
|
CONVERSATIONS {
|
||||||
uuid id PK
|
uuid id PK
|
||||||
uuid listing FK
|
uuid listing_id
|
||||||
uuid buyer FK
|
string participant_hash_1
|
||||||
uuid seller FK
|
string participant_hash_2
|
||||||
|
text public_key_1
|
||||||
|
text public_key_2
|
||||||
string status
|
string status
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGES {
|
MESSAGES {
|
||||||
uuid id PK
|
uuid id PK
|
||||||
uuid conversation FK
|
uuid conversation FK
|
||||||
uuid sender FK
|
string sender_hash
|
||||||
text content
|
text content_encrypted
|
||||||
datetime read_at
|
string nonce
|
||||||
|
string type
|
||||||
}
|
}
|
||||||
|
|
||||||
FAVORITES {
|
FAVORITES {
|
||||||
@@ -102,6 +102,8 @@ erDiagram
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Privacy-Hinweis:** Conversations und Messages haben keine direkten User-Referenzen - nur Hashes!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Data Models (Collections)
|
## 1. Data Models (Collections)
|
||||||
@@ -110,31 +112,31 @@ erDiagram
|
|||||||
|
|
||||||
Die Haupt-Collection für alle Kleinanzeigen.
|
Die Haupt-Collection für alle Kleinanzeigen.
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key, auto-generated |
|
| `id` | UUID | – | Primary Key, auto-generated |
|
||||||
| `status` | String (Dropdown) | `draft`, `published`, `sold`, `expired`, `deleted` |
|
| `status` | String | Dropdown | `draft`, `published`, `sold`, `expired`, `deleted` |
|
||||||
| `sort` | Integer | Für manuelle Sortierung |
|
| `sort` | Integer | Input | Für manuelle Sortierung |
|
||||||
| `user_created` | User (M2O) | Auto, Read-only |
|
| `user_created` | User (M2O) | User | Auto, Read-only |
|
||||||
| `date_created` | DateTime | Auto, Read-only |
|
| `date_created` | DateTime | DateTime | Auto, Read-only |
|
||||||
| `user_updated` | User (M2O) | Auto |
|
| `user_updated` | User (M2O) | User | Auto |
|
||||||
| `date_updated` | DateTime | Auto |
|
| `date_updated` | DateTime | DateTime | Auto |
|
||||||
| `title` | String | Required, max 100 chars |
|
| `title` | String | Input | Required, max 100 chars |
|
||||||
| `slug` | String | Unique, auto-generated from title |
|
| `slug` | String | Input | Unique, auto-generated via Flow |
|
||||||
| `description` | Text (WYSIWYG) | Required |
|
| `description` | Text | WYSIWYG | Required |
|
||||||
| `price` | Decimal | Required, precision 10, scale 2 |
|
| `price` | Decimal | Input | Required, precision 10, scale 2 |
|
||||||
| `currency` | String | Default: `XMR`, Options: `XMR`, `EUR` |
|
| `currency` | String | Dropdown | `XMR`, `EUR`, `CHF`, `USD`, `GBP`, `JPY` (Default: XMR) |
|
||||||
| `price_type` | String | `fixed`, `negotiable`, `free`, `on_request` |
|
| `price_mode` | String | Dropdown | `fiat`, `xmr` (Default: fiat) |
|
||||||
| `category` | Categories (M2O) | Required |
|
| `price_type` | String | Dropdown | `fixed`, `negotiable`, `free`, `on_request` |
|
||||||
| `condition` | String | `new`, `like_new`, `good`, `fair`, `poor` |
|
| `category` | Categories (M2O) | Many-to-One | Required |
|
||||||
| `images` | Files (M2M) | Junction: `listings_files` |
|
| `condition` | String | Dropdown | `new`, `like_new`, `good`, `fair`, `poor` |
|
||||||
| `location` | Locations (M2O) | Optional |
|
| `images` | Files (M2M) | Files | Junction: `listings_files` |
|
||||||
| `shipping` | Boolean | Versand möglich? |
|
| `location` | Locations (M2O) | Many-to-One | Optional |
|
||||||
| `shipping_cost` | Decimal | Optional |
|
| `shipping` | Boolean | Toggle | Versand möglich? |
|
||||||
| `views` | Integer | Default: 0 |
|
| `shipping_cost` | Decimal | Input | Optional |
|
||||||
| `expires_at` | DateTime | Auto-set, 30 Tage nach Erstellung |
|
| `views` | Integer | Input | Default: 0, Read-only |
|
||||||
| `monero_address` | String | Für Direktzahlung |
|
| `expires_at` | DateTime | DateTime | Auto-set via Flow, 30 Tage |
|
||||||
| `contact_method` | String | `chat`, `email`, `both` |
|
| `monero_address` | String | Input | Für Direktzahlung |
|
||||||
|
|
||||||
**Erstellen in Directus Admin:**
|
**Erstellen in Directus Admin:**
|
||||||
```
|
```
|
||||||
@@ -149,16 +151,16 @@ Primary Key: UUID (auto-generated)
|
|||||||
|
|
||||||
Hierarchische Kategorien mit Übersetzungen.
|
Hierarchische Kategorien mit Übersetzungen.
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `status` | String | `draft`, `published` |
|
| `status` | String | Dropdown | `draft`, `published` |
|
||||||
| `sort` | Integer | Für Sortierung |
|
| `sort` | Integer | Input | Für Sortierung |
|
||||||
| `name` | String | Required (Fallback-Name) |
|
| `name` | String | Input | Required (Fallback-Name) |
|
||||||
| `slug` | String | Unique |
|
| `slug` | String | Input | Unique |
|
||||||
| `icon` | String | Icon-Name (z.B. `laptop`, `car`) |
|
| `icon` | String | Input | Icon-Name (z.B. `laptop`, `car`) |
|
||||||
| `parent` | Categories (M2O) | Self-referencing |
|
| `parent` | Categories (M2O) | Many-to-One | Self-referencing |
|
||||||
| `translations` | Translations | Junction: `categories_translations` |
|
| `translations` | Translations | Translations | Junction: `categories_translations` |
|
||||||
|
|
||||||
**Translations Fields:**
|
**Translations Fields:**
|
||||||
| Feld | Typ |
|
| Feld | Typ |
|
||||||
@@ -169,45 +171,67 @@ Hierarchische Kategorien mit Übersetzungen.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.3 conversations (Konversationen)
|
### 1.3 conversations (Konversationen) - Metadaten-verschlüsselt
|
||||||
|
|
||||||
Chat zwischen Käufer und Verkäufer.
|
E2E-verschlüsselter Chat mit **Zero-Knowledge Metadaten** - Server weiß nicht, wer mit wem chattet.
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `date_created` | DateTime | Auto |
|
| `date_created` | DateTime | DateTime | Auto |
|
||||||
| `date_updated` | DateTime | Auto |
|
| `date_updated` | DateTime | DateTime | Auto |
|
||||||
| `listing` | Listings (M2O) | Required |
|
| `listing_id` | UUID | Input | Listing-Referenz (kein FK für Privacy) |
|
||||||
| `buyer` | User (M2O) | Wer die Konversation gestartet hat |
|
| `participant_hash_1` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||||
| `seller` | User (M2O) | Auto from listing.user_created |
|
| `participant_hash_2` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||||
| `status` | String | `active`, `archived`, `blocked` |
|
| `public_key_1` | Text | Textarea | X25519 Public Key für E2E |
|
||||||
| `messages` | Messages (O2M) | |
|
| `public_key_2` | Text | Textarea | X25519 Public Key für E2E |
|
||||||
|
| `status` | String | Dropdown | `active`, `archived` |
|
||||||
|
|
||||||
|
**Privacy-Konzept:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 1. Käufer startet Chat zu Listing │
|
||||||
|
│ → generiert conversation_secret (random 32 bytes) │
|
||||||
|
│ → berechnet participant_hash = SHA256(own_uuid + secret) │
|
||||||
|
│ → sendet public_key für E2E-Encryption │
|
||||||
|
│ │
|
||||||
|
│ 2. Verkäufer erhält Notification (via Listing-Polling) │
|
||||||
|
│ → berechnet eigenen participant_hash mit gleichem secret │
|
||||||
|
│ → sendet eigenen public_key │
|
||||||
|
│ │
|
||||||
|
│ 3. Server sieht nur: │
|
||||||
|
│ - Zwei Hashes (nicht welche User) │
|
||||||
|
│ - Verschlüsselte Nachrichten │
|
||||||
|
│ - Kein buyer/seller Bezug! │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.4 messages (Nachrichten)
|
### 1.4 messages (Nachrichten) - E2E-verschlüsselt
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `date_created` | DateTime | Auto |
|
| `date_created` | DateTime | DateTime | Auto |
|
||||||
| `conversation` | Conversations (M2O) | Required |
|
| `conversation` | Conversations (M2O) | Many-to-One | Required |
|
||||||
| `sender` | User (M2O) | Auto (user_created) |
|
| `sender_hash` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||||
| `content` | Text | Required, max 2000 chars |
|
| `content_encrypted` | Text | Textarea | XChaCha20-Poly1305 verschlüsselt |
|
||||||
| `read_at` | DateTime | Null = ungelesen |
|
| `nonce` | String | Input | Unique nonce für Entschlüsselung |
|
||||||
| `type` | String | `text`, `offer`, `system` |
|
| `type` | String | Dropdown | `text`, `offer`, `system` |
|
||||||
|
|
||||||
|
**Keine Klartext-Inhalte auf dem Server!**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.5 favorites (Favoriten/Merkliste)
|
### 1.5 favorites (Favoriten/Merkliste)
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `date_created` | DateTime | Auto |
|
| `date_created` | DateTime | DateTime | Auto |
|
||||||
| `user` | User (M2O) | Auto (user_created) |
|
| `user` | User (M2O) | User | Auto (user_created) |
|
||||||
| `listing` | Listings (M2O) | Required |
|
| `listing` | Listings (M2O) | Many-to-One | Required |
|
||||||
|
|
||||||
**Unique Constraint:** `user` + `listing`
|
**Unique Constraint:** `user` + `listing`
|
||||||
|
|
||||||
@@ -215,33 +239,33 @@ Chat zwischen Käufer und Verkäufer.
|
|||||||
|
|
||||||
### 1.6 reports (Meldungen)
|
### 1.6 reports (Meldungen)
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `date_created` | DateTime | Auto |
|
| `date_created` | DateTime | DateTime | Auto |
|
||||||
| `reporter` | User (M2O) | Auto |
|
| `reporter` | User (M2O) | User | Auto |
|
||||||
| `listing` | Listings (M2O) | Optional |
|
| `listing` | Listings (M2O) | Many-to-One | Optional |
|
||||||
| `reported_user` | User (M2O) | Optional |
|
| `reported_user` | User (M2O) | Many-to-One | Optional |
|
||||||
| `reason` | String | `spam`, `fraud`, `inappropriate`, `illegal`, `other` |
|
| `reason` | String | Dropdown | `spam`, `fraud`, `inappropriate`, `illegal`, `other` |
|
||||||
| `details` | Text | |
|
| `details` | Text | Textarea | |
|
||||||
| `status` | String | `pending`, `reviewed`, `resolved`, `dismissed` |
|
| `status` | String | Dropdown | `pending`, `reviewed`, `resolved`, `dismissed` |
|
||||||
| `admin_notes` | Text | Nur für Admins sichtbar |
|
| `admin_notes` | Text | Textarea | Nur für Admins sichtbar |
|
||||||
| `resolved_by` | User (M2O) | |
|
| `resolved_by` | User (M2O) | User | |
|
||||||
| `resolved_at` | DateTime | |
|
| `resolved_at` | DateTime | DateTime | |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.7 locations (Orte)
|
### 1.7 locations (Orte)
|
||||||
|
|
||||||
| Feld | Typ | Einstellungen |
|
| Feld | Typ | Interface | Einstellungen |
|
||||||
|------|-----|---------------|
|
|------|-----|-----------|---------------|
|
||||||
| `id` | UUID | Primary Key |
|
| `id` | UUID | – | Primary Key |
|
||||||
| `name` | String | Stadt/Ort |
|
| `name` | String | Input | Stadt/Ort |
|
||||||
| `postal_code` | String | PLZ |
|
| `postal_code` | String | Input | PLZ |
|
||||||
| `region` | String | Bundesland/Kanton |
|
| `region` | String | Input | Bundesland/Kanton |
|
||||||
| `country` | String | Default: `DE` |
|
| `country` | String | Dropdown | Default: `DE` |
|
||||||
| `latitude` | Float | Optional, für Kartenansicht |
|
| `latitude` | Float | Input | Optional, für Kartenansicht |
|
||||||
| `longitude` | Float | Optional |
|
| `longitude` | Float | Input | Optional |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -500,8 +524,14 @@ Zusätzlich zu User-Permissions:
|
|||||||
|
|
||||||
**Trigger:** `items.create` auf `listings`
|
**Trigger:** `items.create` auf `listings`
|
||||||
|
|
||||||
|
**Flow-Aufbau:**
|
||||||
|
|
||||||
|
1. **Trigger:** `items.create` auf `listings`
|
||||||
|
2. **Operation 1:** Run Script (Slug generieren)
|
||||||
|
3. **Operation 2:** Update Data (Slug ins Feld schreiben)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Operation: Run Script
|
// Operation 1: Run Script
|
||||||
module.exports = async function(data) {
|
module.exports = async function(data) {
|
||||||
const title = data.title;
|
const title = data.title;
|
||||||
const slug = title
|
const slug = title
|
||||||
@@ -510,11 +540,19 @@ module.exports = async function(data) {
|
|||||||
.replace(/[^a-z0-9]+/g, '-')
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
.replace(/(^-|-$)/g, '');
|
.replace(/(^-|-$)/g, '');
|
||||||
|
|
||||||
|
// Timestamp für Eindeutigkeit
|
||||||
return { slug: `${slug}-${Date.now().toString(36)}` };
|
return { slug: `${slug}-${Date.now().toString(36)}` };
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Operation:** Update Data → `listings` mit Ergebnis
|
```
|
||||||
|
// Operation 2: Update Data
|
||||||
|
Collection: listings
|
||||||
|
ID: {{$trigger.key}}
|
||||||
|
Payload: { "slug": "{{$last.slug}}" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ergebnis:** Title "iPhone 15 Pro!" → Slug `iphone-15-pro-m1abc2def`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -580,19 +618,6 @@ module.exports = async function(data, { database }) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 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. Einstellungen
|
||||||
|
|
||||||
### 5.1 Project Settings
|
### 5.1 Project Settings
|
||||||
@@ -656,17 +681,94 @@ RATE_LIMITER_POINTS=100
|
|||||||
RATE_LIMITER_DURATION=60
|
RATE_LIMITER_DURATION=60
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.6 Email Settings
|
**Hinweis:** Keine E-Mail-Konfiguration nötig - dgray.io nutzt keine E-Mails (Privacy by Design).
|
||||||
|
|
||||||
```env
|
### 5.6 Währungsumrechnung (Kraken API)
|
||||||
EMAIL_FROM=noreply@dgray.io
|
|
||||||
EMAIL_TRANSPORT=smtp
|
Für die Anzeige von Fiat-Preisen in XMR wird die Kraken API genutzt.
|
||||||
EMAIL_SMTP_HOST=smtp.example.com
|
|
||||||
EMAIL_SMTP_PORT=587
|
**API Endpoint:**
|
||||||
EMAIL_SMTP_USER=xxx
|
|
||||||
EMAIL_SMTP_PASSWORD=xxx
|
|
||||||
EMAIL_SMTP_SECURE=false
|
|
||||||
```
|
```
|
||||||
|
https://api.kraken.com/0/public/Ticker?pair=XMRUSD,XMREUR,XMRGBP,XMRCHF,XMRJPY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend-Implementation:**
|
||||||
|
```javascript
|
||||||
|
const KRAKEN_API = 'https://api.kraken.com/0/public/Ticker';
|
||||||
|
const PAIRS = {
|
||||||
|
USD: 'XMRUSD',
|
||||||
|
EUR: 'XMREUR',
|
||||||
|
GBP: 'XMRGBP',
|
||||||
|
CHF: 'XMRCHF',
|
||||||
|
JPY: 'XMRJPY'
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getXmrRates() {
|
||||||
|
const pairs = Object.values(PAIRS).join(',');
|
||||||
|
const response = await fetch(`${KRAKEN_API}?pair=${pairs}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
const rates = {};
|
||||||
|
for (const [currency, pair] of Object.entries(PAIRS)) {
|
||||||
|
const ticker = data.result[pair];
|
||||||
|
if (ticker) {
|
||||||
|
rates[currency] = parseFloat(ticker.c[0]); // Last trade price
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToXmr(amount, currency, rates) {
|
||||||
|
if (currency === 'XMR') return amount;
|
||||||
|
return amount / rates[currency];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beispiel: 100 EUR in XMR
|
||||||
|
// const rates = await getXmrRates();
|
||||||
|
// const xmrAmount = convertToXmr(100, 'EUR', rates);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caching:** Rates werden client-side für 5 Minuten gecached.
|
||||||
|
|
||||||
|
### 5.7 Preismodus (Fiat vs. XMR)
|
||||||
|
|
||||||
|
Listings können in zwei Modi erstellt werden:
|
||||||
|
|
||||||
|
| Modus | `price_mode` | Verhalten |
|
||||||
|
|-------|--------------|-----------|
|
||||||
|
| **Fiat-fix** | `fiat` | Preis bleibt z.B. 100 EUR, XMR-Äquivalent ändert sich mit Kurs |
|
||||||
|
| **XMR-fix** | `xmr` | Preis bleibt z.B. 0.5 XMR, Fiat-Äquivalent ändert sich mit Kurs |
|
||||||
|
|
||||||
|
**Frontend-Anzeige:**
|
||||||
|
```javascript
|
||||||
|
function displayPrice(listing, rates) {
|
||||||
|
const { price, currency, price_mode } = listing;
|
||||||
|
|
||||||
|
if (price_mode === 'xmr' || currency === 'XMR') {
|
||||||
|
// XMR ist der Referenzpreis
|
||||||
|
const xmrPrice = currency === 'XMR' ? price : price / rates[currency];
|
||||||
|
return {
|
||||||
|
primary: `${xmrPrice.toFixed(4)} XMR`,
|
||||||
|
secondary: currency !== 'XMR' ? `≈ ${price} ${currency}` : null
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Fiat ist der Referenzpreis
|
||||||
|
const xmrEquivalent = price / rates[currency];
|
||||||
|
return {
|
||||||
|
primary: `${price} ${currency}`,
|
||||||
|
secondary: `≈ ${xmrEquivalent.toFixed(4)} XMR`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beispiele:**
|
||||||
|
|
||||||
|
| Listing | Anzeige (bei 1 XMR = 150 EUR) |
|
||||||
|
|---------|-------------------------------|
|
||||||
|
| 100 EUR, mode=`fiat` | **100 EUR** ≈ 0.6667 XMR |
|
||||||
|
| 100 EUR, mode=`xmr` | **0.6667 XMR** ≈ 100 EUR |
|
||||||
|
| 0.5 XMR, mode=`xmr` | **0.5 XMR** ≈ 75 EUR |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -877,11 +979,32 @@ AUTH_PASSWORD_RESET=false
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Sicherheitshinweise
|
## 10. Sicherheits- & Privacy-Hinweise
|
||||||
|
|
||||||
1. **Monero-Adressen** werden nur dem Listing-Ersteller und authentifizierten Nutzern angezeigt
|
### Privacy by Design
|
||||||
2. **Keine echten E-Mails** - UUID-basierte Authentifizierung
|
|
||||||
3. **Rate Limiting** für API-Endpunkte aktivieren
|
| Aspekt | Umsetzung |
|
||||||
4. **Bilder-Upload** auf max. 10MB und erlaubte Typen beschränken
|
|--------|-----------|
|
||||||
5. **XSS-Schutz** für WYSIWYG-Felder in Directus aktivieren
|
| **Authentifizierung** | UUID-basiert, keine echten E-Mails |
|
||||||
6. **UUID-Warnung** bei Registrierung: User muss UUID sicher speichern
|
| **Kommunikation** | Nur verschlüsselter Chat, keine E-Mail-Option |
|
||||||
|
| **Chat-Metadaten** | Zero-Knowledge (participant_hash statt user_id) |
|
||||||
|
| **Chat-Inhalte** | E2E-verschlüsselt (XChaCha20-Poly1305) |
|
||||||
|
| **Währungen** | XMR (primär) + EUR, CHF, USD, GBP, JPY |
|
||||||
|
| **Kontakt** | Ausschließlich über internen Chat |
|
||||||
|
|
||||||
|
### Technische Sicherheit
|
||||||
|
|
||||||
|
1. **Monero-Adressen** werden nur authentifizierten Nutzern angezeigt
|
||||||
|
2. **Rate Limiting** für alle API-Endpunkte aktivieren
|
||||||
|
3. **Bilder-Upload** auf max. 10MB und erlaubte Typen beschränken
|
||||||
|
4. **XSS-Schutz** für WYSIWYG-Felder in Directus aktivieren
|
||||||
|
5. **UUID-Warnung** bei Registrierung: User muss UUID sicher speichern
|
||||||
|
6. **Kein Passwort-Reset** möglich (by design)
|
||||||
|
7. **Keine Server-Logs** mit User-Bezug speichern
|
||||||
|
|
||||||
|
### Was der Server NICHT weiß
|
||||||
|
|
||||||
|
- Echte Identität der User (nur UUIDs)
|
||||||
|
- Wer mit wem chattet (nur Hashes)
|
||||||
|
- Chat-Inhalte (nur verschlüsselt)
|
||||||
|
- E-Mail-Adressen (existieren nicht)
|
||||||
|
|||||||
Reference in New Issue
Block a user