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
|
||||
USERS ||--o{ LISTINGS : creates
|
||||
USERS ||--o{ FAVORITES : has
|
||||
USERS ||--o{ CONVERSATIONS : participates
|
||||
USERS ||--o{ MESSAGES : sends
|
||||
USERS ||--o{ REPORTS : submits
|
||||
|
||||
LISTINGS ||--o{ LISTINGS_FILES : has
|
||||
LISTINGS ||--o{ FAVORITES : receives
|
||||
LISTINGS ||--o{ CONVERSATIONS : has
|
||||
LISTINGS }o--|| CATEGORIES : belongs_to
|
||||
LISTINGS }o--o| LOCATIONS : located_at
|
||||
|
||||
@@ -66,18 +63,21 @@ erDiagram
|
||||
|
||||
CONVERSATIONS {
|
||||
uuid id PK
|
||||
uuid listing FK
|
||||
uuid buyer FK
|
||||
uuid seller FK
|
||||
uuid listing_id
|
||||
string participant_hash_1
|
||||
string participant_hash_2
|
||||
text public_key_1
|
||||
text public_key_2
|
||||
string status
|
||||
}
|
||||
|
||||
MESSAGES {
|
||||
uuid id PK
|
||||
uuid conversation FK
|
||||
uuid sender FK
|
||||
text content
|
||||
datetime read_at
|
||||
string sender_hash
|
||||
text content_encrypted
|
||||
string nonce
|
||||
string type
|
||||
}
|
||||
|
||||
FAVORITES {
|
||||
@@ -102,6 +102,8 @@ erDiagram
|
||||
}
|
||||
```
|
||||
|
||||
**Privacy-Hinweis:** Conversations und Messages haben keine direkten User-Referenzen - nur Hashes!
|
||||
|
||||
---
|
||||
|
||||
## 1. Data Models (Collections)
|
||||
@@ -110,31 +112,31 @@ erDiagram
|
||||
|
||||
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` |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key, auto-generated |
|
||||
| `status` | String | Dropdown | `draft`, `published`, `sold`, `expired`, `deleted` |
|
||||
| `sort` | Integer | Input | Für manuelle Sortierung |
|
||||
| `user_created` | User (M2O) | User | Auto, Read-only |
|
||||
| `date_created` | DateTime | DateTime | Auto, Read-only |
|
||||
| `user_updated` | User (M2O) | User | Auto |
|
||||
| `date_updated` | DateTime | DateTime | Auto |
|
||||
| `title` | String | Input | Required, max 100 chars |
|
||||
| `slug` | String | Input | Unique, auto-generated via Flow |
|
||||
| `description` | Text | WYSIWYG | Required |
|
||||
| `price` | Decimal | Input | Required, precision 10, scale 2 |
|
||||
| `currency` | String | Dropdown | `XMR`, `EUR`, `CHF`, `USD`, `GBP`, `JPY` (Default: XMR) |
|
||||
| `price_mode` | String | Dropdown | `fiat`, `xmr` (Default: fiat) |
|
||||
| `price_type` | String | Dropdown | `fixed`, `negotiable`, `free`, `on_request` |
|
||||
| `category` | Categories (M2O) | Many-to-One | Required |
|
||||
| `condition` | String | Dropdown | `new`, `like_new`, `good`, `fair`, `poor` |
|
||||
| `images` | Files (M2M) | Files | Junction: `listings_files` |
|
||||
| `location` | Locations (M2O) | Many-to-One | Optional |
|
||||
| `shipping` | Boolean | Toggle | Versand möglich? |
|
||||
| `shipping_cost` | Decimal | Input | Optional |
|
||||
| `views` | Integer | Input | Default: 0, Read-only |
|
||||
| `expires_at` | DateTime | DateTime | Auto-set via Flow, 30 Tage |
|
||||
| `monero_address` | String | Input | Für Direktzahlung |
|
||||
|
||||
**Erstellen in Directus Admin:**
|
||||
```
|
||||
@@ -149,16 +151,16 @@ Primary Key: UUID (auto-generated)
|
||||
|
||||
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` |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `status` | String | Dropdown | `draft`, `published` |
|
||||
| `sort` | Integer | Input | Für Sortierung |
|
||||
| `name` | String | Input | Required (Fallback-Name) |
|
||||
| `slug` | String | Input | Unique |
|
||||
| `icon` | String | Input | Icon-Name (z.B. `laptop`, `car`) |
|
||||
| `parent` | Categories (M2O) | Many-to-One | Self-referencing |
|
||||
| `translations` | Translations | Translations | Junction: `categories_translations` |
|
||||
|
||||
**Translations Fields:**
|
||||
| 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 |
|
||||
|------|-----|---------------|
|
||||
| `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) | |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `date_created` | DateTime | DateTime | Auto |
|
||||
| `date_updated` | DateTime | DateTime | Auto |
|
||||
| `listing_id` | UUID | Input | Listing-Referenz (kein FK für Privacy) |
|
||||
| `participant_hash_1` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||
| `participant_hash_2` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||
| `public_key_1` | Text | Textarea | X25519 Public Key für E2E |
|
||||
| `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 |
|
||||
|------|-----|---------------|
|
||||
| `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` |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `date_created` | DateTime | DateTime | Auto |
|
||||
| `conversation` | Conversations (M2O) | Many-to-One | Required |
|
||||
| `sender_hash` | String | Input | SHA256(user_uuid + conversation_secret) |
|
||||
| `content_encrypted` | Text | Textarea | XChaCha20-Poly1305 verschlüsselt |
|
||||
| `nonce` | String | Input | Unique nonce für Entschlüsselung |
|
||||
| `type` | String | Dropdown | `text`, `offer`, `system` |
|
||||
|
||||
**Keine Klartext-Inhalte auf dem Server!**
|
||||
|
||||
---
|
||||
|
||||
### 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 |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `date_created` | DateTime | DateTime | Auto |
|
||||
| `user` | User (M2O) | User | Auto (user_created) |
|
||||
| `listing` | Listings (M2O) | Many-to-One | Required |
|
||||
|
||||
**Unique Constraint:** `user` + `listing`
|
||||
|
||||
@@ -215,33 +239,33 @@ Chat zwischen Käufer und Verkäufer.
|
||||
|
||||
### 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 | |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `date_created` | DateTime | DateTime | Auto |
|
||||
| `reporter` | User (M2O) | User | Auto |
|
||||
| `listing` | Listings (M2O) | Many-to-One | Optional |
|
||||
| `reported_user` | User (M2O) | Many-to-One | Optional |
|
||||
| `reason` | String | Dropdown | `spam`, `fraud`, `inappropriate`, `illegal`, `other` |
|
||||
| `details` | Text | Textarea | |
|
||||
| `status` | String | Dropdown | `pending`, `reviewed`, `resolved`, `dismissed` |
|
||||
| `admin_notes` | Text | Textarea | Nur für Admins sichtbar |
|
||||
| `resolved_by` | User (M2O) | User | |
|
||||
| `resolved_at` | DateTime | 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 |
|
||||
| Feld | Typ | Interface | Einstellungen |
|
||||
|------|-----|-----------|---------------|
|
||||
| `id` | UUID | – | Primary Key |
|
||||
| `name` | String | Input | Stadt/Ort |
|
||||
| `postal_code` | String | Input | PLZ |
|
||||
| `region` | String | Input | Bundesland/Kanton |
|
||||
| `country` | String | Dropdown | Default: `DE` |
|
||||
| `latitude` | Float | Input | Optional, für Kartenansicht |
|
||||
| `longitude` | Float | Input | Optional |
|
||||
|
||||
---
|
||||
|
||||
@@ -500,8 +524,14 @@ Zusätzlich zu User-Permissions:
|
||||
|
||||
**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
|
||||
// Operation: Run Script
|
||||
// Operation 1: Run Script
|
||||
module.exports = async function(data) {
|
||||
const title = data.title;
|
||||
const slug = title
|
||||
@@ -510,11 +540,19 @@ module.exports = async function(data) {
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/(^-|-$)/g, '');
|
||||
|
||||
// Timestamp für Eindeutigkeit
|
||||
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.1 Project Settings
|
||||
@@ -656,17 +681,94 @@ RATE_LIMITER_POINTS=100
|
||||
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
|
||||
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
|
||||
### 5.6 Währungsumrechnung (Kraken API)
|
||||
|
||||
Für die Anzeige von Fiat-Preisen in XMR wird die Kraken API genutzt.
|
||||
|
||||
**API Endpoint:**
|
||||
```
|
||||
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
|
||||
2. **Keine echten E-Mails** - UUID-basierte Authentifizierung
|
||||
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
|
||||
6. **UUID-Warnung** bei Registrierung: User muss UUID sicher speichern
|
||||
### Privacy by Design
|
||||
|
||||
| Aspekt | Umsetzung |
|
||||
|--------|-----------|
|
||||
| **Authentifizierung** | UUID-basiert, keine echten E-Mails |
|
||||
| **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