diff --git a/docs/fahrschuldesk/PLAN.md b/docs/fahrschuldesk/PLAN.md index fa98fcb..cd632d5 100644 --- a/docs/fahrschuldesk/PLAN.md +++ b/docs/fahrschuldesk/PLAN.md @@ -22,8 +22,14 @@ - **Frontend:** Vue 3 + Vite (SPA) - **Backend:** PHP (Laravel oder Slim Framework) - **Datenbank:** PostgreSQL (fahrschuldesk DB, Host: 10.255.30.11:5433) -- **Auth:** JWT-basiert -- **Deploy:** Git → Server via rsync +- **WebSockets:** Für Messenger (Socket.io oder native WebSockets) +- **Dateien:** Lokaler Storage + S3-kompatibel (für Self-Hosted einfach lokaler Ordner) +- **Auth:** JWT-basiert mit Refresh-Token +- **CI/CD:** GitHub Actions / GitLab CI + +### 2.3 Kalender-Integration + +Der Kalender wird über die fahrschultermin.de API gesteuert (spätere Integration). Die fahrschuldesk-API dient als Schnittstelle. --- @@ -41,7 +47,7 @@ Plattform-Admins (intern) #### Plattform-Admins (interne Verwaltung) ``` platform_admins - - id + - id (uuid) - email (unique) - password_hash - name @@ -54,28 +60,39 @@ platform_admins #### Fahrschulen (Mandanten) ``` tenants - - id + - id (uuid) - name - slug (unique, für Subdomain?) - email (Rechnungs-Kontakt) - phone - address - - status (active | suspended | trial) + - status (pending_approval | active | suspended | trial) - trial_ends_at - created_at - updated_at + + # Branding + - logo_url + - primary_color + - secondary_color + - custom_css (optional) + + # Self-Hosted + - is_self_hosted (bool) + - self_hosted_license_key ``` #### Filialen ``` branches - - id + - id (uuid) - tenant_id (FK) - name - address - phone - email - opening_hours (JSON) + - is_headquarters (bool) - created_at - updated_at ``` @@ -83,15 +100,17 @@ branches #### Benutzer ``` users - - id + - id (uuid) - tenant_id (FK) - branch_id (FK, nullable) - email (unique pro Tenant) - password_hash - first_name - last_name - - user_type (office | instructor | lecturer | student | admin) - - status (active | inactive) + - user_type (admin | office | instructor | lecturer | student) + - status (active | inactive | pending) + - avatar_url + - phone - created_at - updated_at - last_login_at @@ -100,26 +119,49 @@ users #### Module (pro Fahrschule zuweisbar) ``` modules - - id + - id (uuid) - key (office | calendar | messenger | learning_blank | learning_premium) - name - description - - monthly_price + - monthly_price (cent) - is_active (Plattform-global) + - created_at tenant_modules - - id + - id (uuid) - tenant_id (FK) - module_id (FK) - status (enabled | disabled) - enabled_at - expires_at (nullable) + - trial_ends_at (nullable) +``` + +#### Pakete (Preismodelle) +``` +packages + - id (uuid) + - key (starter | professional | enterprise) + - name + - description + - monthly_price (cent) + - modules_included (JSON array von module_keys) + - max_users + - max_branches + - created_at + +tenant_packages + - id (uuid) + - tenant_id (FK) + - package_id (FK) + - started_at + - expires_at ``` #### Lerninhalte (Premium-Modul) ``` learning_contents - - id + - id (uuid) - tenant_id (FK, nullable — null = Plattform-global) - category - title @@ -127,7 +169,8 @@ learning_contents - content_type (video | document | quiz | exercise) - content_url - thumbnail_url - - is_premium (nur mit Premium-Modul) + - duration_minutes + - is_premium - created_at - updated_at ``` @@ -135,7 +178,7 @@ learning_contents #### Fahrschüler-Fortschritt ``` student_progress - - id + - id (uuid) - user_id (FK, student) - content_id (FK) - status (not_started | in_progress | completed) @@ -145,18 +188,150 @@ student_progress - updated_at ``` +#### Audit-Log +``` +audit_logs + - id (uuid) + - tenant_id (FK, nullable — null für Plattform-Admin-Aktionen) + - user_id (FK, nullable) + - action (created | updated | deleted | login | logout | etc.) + - entity_type (tenant | user | branch | module | etc.) + - entity_id (UUID des betroffenen Datensatzes) + - old_values (JSON, nullable) + - new_values (JSON, nullable) + - ip_address + - user_agent + - created_at + + # Index auf tenant_id + created_at für schnelle Abfragen + # Index auf entity_type + entity_id +``` + +#### Rechnungen +``` +invoices + - id (uuid) + - tenant_id (FK) + - invoice_number (unique) + - amount (cent) + - vat_amount (cent) + - total_amount (cent) + - currency (EUR) + - status (draft | pending | paid | overdue | cancelled) + - billing_period_start + - billing_period_end + - due_date + - paid_at (nullable) + - created_at + - updated_at + +invoice_items + - id (uuid) + - invoice_id (FK) + - description + - quantity + - unit_price (cent) + - total_price (cent) +``` + +#### E-Mail / Notifications +``` +notification_templates + - id (uuid) + - key (welcome | password_reset | invoice | trial_expiring | etc.) + - subject + - body_html + - body_text + - variables (JSON) + - created_at + - updated_at + +notifications + - id (uuid) + - user_id (FK) + - tenant_id (FK, nullable) + - template_key + - subject + - body + - status (pending | sent | failed) + - sent_at + - created_at +``` + +#### Dateien / Uploads / DKS (Dokumenten-Klasse-System) +``` +files + - id (uuid) + - tenant_id (FK) + - user_id (FK, nullable — wer hochgeladen hat) + - entity_type (vehicle | student | instructor | branch | etc.) + - entity_id (UUID) + - file_type (insurance | tuv | logo | avatar | document | etc.) + - original_filename + - storage_path + - mime_type + - size_bytes + - created_at + + # Dateien werden im Storage abgelegt: + # SaaS: /storage/tenants/{tenant_id}/files/ + # Self-Hosted: /var/www/fahrschuldesk/storage/files/ +``` + +#### Messenger (DSGVO-konform) +``` +conversations + - id (uuid) + - tenant_id (FK) + - type (direct | group) + - name (nullable, nur für groups) + - created_at + - updated_at + +conversation_participants + - id (uuid) + - conversation_id (FK) + - user_id (FK) + - joined_at + - last_read_at + +messages + - id (uuid) + - conversation_id (FK) + - user_id (FK) + - body (Verschlüsselt auf DB-Ebene) + - is_edited (bool) + - is_deleted (bool) + - created_at + - updated_at + + # Keine Ende-zu-Ende Verschlüsselung (Plattform hat Zugriff für Support/Audit) + # DSGVO: Keine externen Dienste, alles auf eigenen Servern + # Nachrichten können auf Anfrage exportiert/gelöscht werden (DSGVO-Compliance) +``` + --- ## 4. API-Struktur ### 4.1 Auth ``` -POST /api/auth/login (Admin/Login) +POST /api/auth/login (JWT Token) POST /api/auth/logout +POST /api/auth/refresh (Refresh Token) GET /api/auth/me + +POST /api/auth/forgot-password +POST /api/auth/reset-password ``` -### 4.2 Plattform-Admin (nur Admins) +### 4.2 Registration (Self-Service) +``` +POST /api/auth/register (Fahrschule registrieren) +GET /api/auth/register/verify/:token (E-Mail verifizieren) +``` + +### 4.3 Plattform-Admin (nur Admins) ``` GET /api/admin/tenants POST /api/admin/tenants @@ -164,15 +339,30 @@ GET /api/admin/tenants/:id PUT /api/admin/tenants/:id DELETE /api/admin/tenants/:id +POST /api/admin/tenants/:id/approve (Freigabe nach Registration) +POST /api/admin/tenants/:id/suspend + GET /api/admin/tenants/:id/modules POST /api/admin/tenants/:id/modules DELETE /api/admin/tenants/:id/modules/:moduleId GET /api/admin/platform-modules GET /api/admin/platform-modules/:id + +GET /api/admin/invoices +GET /api/admin/invoices/:id +PUT /api/admin/invoices/:id/mark-paid + +GET /api/admin/audit-logs (Plattform-weit) +GET /api/admin/audit-logs/:id + +GET /api/admin/platform-admins +POST /api/admin/platform-admins +PUT /api/admin/platform-admins/:id +DELETE /api/admin/platform-admins/:id ``` -### 4.3 Tenant-Admin (Fahrschule) +### 4.4 Tenant-Admin (Fahrschule) ``` GET /api/branches POST /api/branches @@ -186,27 +376,69 @@ PUT /api/users/:id DELETE /api/users/:id GET /api/my-modules +GET /api/my-profile +PUT /api/my-profile + +GET /api/audit-logs (Tenant-spezifisch) +GET /api/audit-logs/:id + +# Branding +PUT /api/tenant/branding +GET /api/tenant/branding + +# Pakete +GET /api/my-package +PUT /api/tenant/logo ``` -### 4.4 Users (allgemein pro Tenant) +### 4.5 Kalender (Integration fahrschultermin.de) ``` -GET /api/calendar/events +GET /api/calendar/events (weiterleiten an fahrschultermin.de API) POST /api/calendar/events PUT /api/calendar/events/:id DELETE /api/calendar/events/:id - -GET /api/messages -POST /api/messages -GET /api/messages/conversations +GET /api/calendar/availability (Abfrage freier Slots) ``` -### 4.5 Lernsoftware +### 4.6 Messenger +``` +GET /api/messenger/conversations +POST /api/messenger/conversations +GET /api/messenger/conversations/:id +POST /api/messenger/conversations/:id/messages +GET /api/messenger/conversations/:id/messages +PUT /api/messenger/messages/:id (editieren) +DELETE /api/messenger/messages/:id (soft delete) +``` + +### 4.7 Lernsoftware ``` GET /api/learning/contents GET /api/learning/contents/:id GET /api/learning/my-progress POST /api/learning/my-progress PUT /api/learning/my-progress/:contentId + +# Nur für Admin / Dozenten +POST /api/learning/contents (Blanko: eigene Inhalte erstellen) +PUT /api/learning/contents/:id +DELETE /api/learning/contents/:id +``` + +### 4.8 Dateien / DKS +``` +GET /api/files (Liste, gefiltert nach entity) +POST /api/files (Upload) +GET /api/files/:id +DELETE /api/files/:id +GET /api/files/:id/download +``` + +### 4.9 Rechnungen (Tenant) +``` +GET /api/billing/invoices +GET /api/billing/invoices/:id +GET /api/billing/current-package ``` --- @@ -216,18 +448,28 @@ PUT /api/learning/my-progress/:contentId ### Plattform-Admins (intern) | Rolle | Rechte | |---|---| -| super_admin | Alles: Tenants, Admins, Module, Settings | -| support | Tenants einsehen/verwalten, keine Admins | -| sales | Tenants anlegen, keine Settings | +| super_admin | Alles: Tenants, Admins, Module, Settings, Invoices, Audit | +| support | Tenants einsehen/verwalten, keine Admins ändern, keine Settings | +| sales | Tenants anlegen, keine Settings, keine Admin-Verwaltung | ### Fahrschul-Benutzer | Typ | Beschreibung | |---|---| | admin | Voller Zugriff auf eigene Fahrschule | | office | Büromitarbeiter: Kunden, Kalender, Berichte | -| instructor | Fahrlehrer: Kalender, eigene Fahrschüler | +| instructor | Fahrlehrer: Kalender, eigene Fahrschüler, DKS | | lecturer | Dozent: Kalender, eigene Unterrichtseinheiten | -| student | Fahrschüler: Lernsoftware, eigener Fortschritt | +| student | Fahrschüler: Lernsoftware, eigener Fortschritt, Messenger | + +### Rechte-Matrix (pro Modul) +``` +user_types: + admin: [office:rw, calendar:rw, messenger:rw, learning:rw, files:rw] + office: [office:rw, calendar:r, messenger:rw, learning:r, files:r] + instructor:[office:r, calendar:rw, messenger:rw, learning:r, files:rw] + lecturer: [calendar:rw, messenger:rw, learning:rw, files:r] + student: [calendar:r, messenger:rw, learning:rw, files:r] +``` --- @@ -238,92 +480,333 @@ PUT /api/learning/my-progress/:contentId | Key | Name | Beschreibung | |---|---|---| | `office` | Office | Kundenverwaltung, Fahrzeugverwaltung, Berichte | -| `calendar` | Kalender | Terminplanung, Unterrichtszeiten | -| `messenger` | Messenger | Interne Kommunikation | -| `learning_blank` | Lernsoftware (Blanko) | Eigene Inhalte erstellen, keine Premium-Inhalte | +| `calendar` | Kalender | Terminplanung (Integration fahrschultermin.de) | +| `messenger` | Messenger | WhatsApp-ähnlich, DSGVO-konform | +| `learning_blank` | Lernsoftware (Blanko) | Eigene Inhalte erstellen | | `learning_premium` | Lernsoftware (Premium) | Inkl. vorgefertigter Lerninhalte | ### 6.2 Modul-Zuordnung - Jede Fahrschule kann Module einzeln aktivieren/deaktivieren -- Preise werden pro Modul hinterlegt (für Abrechnung) -- Das Modul `learning_blank` und `learning_premium` schließen sich gegenseitig aus +- Preise werden pro Modul hinterlegt +- `learning_blank` und `learning_premium` schließen sich gegenseitig aus --- -## 7. Frontend-Struktur (fahrschuldesk.de) +## 7. Billing & Pakete + +### 7.1 Pakete (Beispiel) +| Paket | Preis/Monat | Module | Max Users | Max Filialen | +|---|---|---|---|---| +| Starter | 49€ | Office + Kalender + Messenger | 5 | 1 | +| Professional | 99€ | Alle Module (Blanko) | 20 | 3 | +| Premium | 149€ | Alle Module (Premium) | Unlimited | Unlimited | + +### 7.2 Trial +- 14 Tage kostenlos testen +- Danach: Admin-Freigabe nötig +- Trial-End: E-Mail-Erinnerung 3 Tage vorher + +### 7.3 Bezahlung +- PayPal (monatlich/jährlich) +- Rechnung (auf Anfrage für Enterprise) +- Automatische Mahnung bei Überfälligkeit + +### 7.4 Self-Hosted +- License-Key nötig +- Keine weiteren Mandanten anlegbar (nur Filialen) +- Update-Management via License-Key + +--- + +## 8. Registration & Freigabe + +### 8.1 Self-Service Flow +``` +1. Fahrschule registriert sich (Name, E-Mail, Password) +2. E-Mail-Verifizierung (Token) +3. Status: "pending_approval" +4. Admin erhält E-Mail: "Neue Registrierung" +5. Admin prüft und genehmigt (oder ablehnen) +6. Status: "active" → Fahrschule kann Login +``` + +### 8.2 Multi-Admin pro Fahrschule +- Erster Admin = der Registrierer +- Weitere Admin-Accounts können durch den ersten Admin angelegt werden + +--- + +## 9. Branding & Design + +### 9.1 Plattform-Defaults +- Logo, Farben, Fonts werden durch Plattform vorgegeben +- Template: Default-Theme für alle neuen Fahrschulen + +### 9.2 Tenant-Branding +Jede Fahrschule kann anpassen: +- Logo (Header, E-Mails, Rechnungen) +- Primärfarbe (Buttons, Links, Akzente) +- Sekundärfarbe +- Custom CSS (optional, für fortgeschrittene Nutzer) + +### 9.3 E-Mail-Branding +- Rechnungs-E-Mails mit Logo der Fahrschule +- E-Mail-Templates pro Tenant individuell + +--- + +## 10. Audit-Log + +### 10.1 Plattform-Admin Audit +- Alle Aktionen aller Tenants einsehbar +- Filterbar nach Tenant, Aktionstyp, Zeitraum + +### 10.2 Tenant-Admin Audit +- Nur eigene Fahrschule +- Alle User-Aktionen geloggt +- Erfüllt DSGVO-Anforderungen + +### 10.3 Was geloggt wird +- Login / Logout +- CRUD-Operationen auf alle Entitäten +- Änderungen an Benutzern, Modulen, Rechten + +--- + +## 11. Messenger (WhatsApp-like) + +### 11.1 Features +- 1:1 Chat +- Gruppen-Chat +- Nachrichten editieren/löschen +- Timestamps, Read-Receipts +- Online-Status + +### 11.2 DSGVO +- Alle Daten auf eigenen Servern +- Keine externen Dienste (kein Twilio, keine Cloud-Dienste) +- Export aller Daten möglich (DSGVO-Art. 15) +- Löschen auf Anfrage (DSGVO-Art. 17) + +### 11.3 Technisch +- WebSockets für Echtzeit +- Polling-Fallback wenn WebSockets nicht verfügbar +- Messages in DB verschlüsselt (für Backend-Zugriff) + +--- + +## 12. DKS (Dokumenten-Klasse-System) + +### 12.1 Dateitypen +- Fahrzeug: Versicherungsunterlagen, TÜV-Berichte, Kennzeichen +- Fahrschüler: Personalausweis, Sehtest, Erste-Hilfe-Nachweis +- Fahrlehrer: Führerschein, Lehrberechtigung +- Filiale: Mietverträge, Gewerbeanmeldung + +### 12.2 Features +- Upload mit Drag & Drop +- Dateityp-Kategorisierung +- Ablage nach Entity (Fahrzeug, Student, etc.) +- Download +- Versionierung (neue Datei ersetzt alte, alte bleibt) +- Ablaufdatum-Erinnerungen (z.B. TÜV) + +--- + +## 13. Self-Hosted Option + +### 13.1 Anforderungen +- PHP 8.2+ +- PostgreSQL 15+ +- 4GB RAM minimum +- Ubuntu/Debian/CentOS + +### 13.2 Einschränkungen +- **Keine Mandanten** anlegbar (nur die eigene Fahrschule) +- Filialen: Ja (unbegrenzt) +- User: Ja (gemäß Paket) +- Updates: Manuell via License-Key + +### 13.3 Installation +- Docker-Compose für einfache Installation +- Oder klassisch via Composer + Migration + +--- + +## 14. Frontend-Struktur (fahrschuldesk.de) ``` src/ ├── views/ │ ├── auth/ │ │ ├── Login.vue -│ │ └── Logout.vue -│ ├── admin/ (Plattform-Admin-Bereich) +│ │ ├── Register.vue +│ │ ├── ForgotPassword.vue +│ │ └── VerifyEmail.vue +│ ├── admin/ (Plattform-Admin) │ │ ├── Dashboard.vue │ │ ├── Tenants.vue │ │ ├── TenantEdit.vue -│ │ └── Modules.vue -│ ├── tenant/ (Fahrschule-Bereich) +│ │ ├── Modules.vue +│ │ ├── Invoices.vue +│ │ ├── AuditLog.vue +│ │ └── PlatformAdmins.vue +│ ├── tenant/ (Fahrschule) │ │ ├── Dashboard.vue │ │ ├── Users.vue │ │ ├── Branches.vue -│ │ ├── Office.vue -│ │ ├── Calendar.vue +│ │ ├── Office.vue (Kunden, Fahrzeuge) +│ │ ├── Calendar.vue (→ fahrschultermin.de) │ │ ├── Messenger.vue -│ │ └── Learning.vue -│ └── student/ (Fahrschüler-Bereich) +│ │ ├── Learning.vue +│ │ ├── Files.vue (DKS) +│ │ ├── Branding.vue +│ │ └── Billing.vue +│ └── student/ (Fahrschüler) │ ├── Dashboard.vue -│ └── Learning.vue +│ ├── Learning.vue +│ └── Messenger.vue ├── components/ -├── stores/ (Pinia) +│ ├── common/ +│ │ ├── AppHeader.vue +│ │ ├── AppSidebar.vue +│ │ └── ModuleCard.vue +│ ├── messenger/ +│ │ ├── ChatWindow.vue +│ │ ├── ConversationList.vue +│ │ └── MessageBubble.vue +│ ├── files/ +│ │ ├── FileUploader.vue +│ │ └── FileList.vue +│ └── branding/ +│ └── ColorPicker.vue +├── stores/ (Pinia) +│ ├── auth.js +│ ├── tenant.js +│ ├── modules.js +│ └── messenger.js ├── router/ -└── api/ (API-Client) +│ └── index.js +├── api/ (Axios Client) +│ ├── client.js +│ ├── auth.js +│ ├── admin.js +│ └── tenant.js +└── styles/ + ├── variables.css + └── theme.css ``` --- -## 8. Deployment-Workflow +## 15. CI/CD Pipeline -``` -Lokal (~/drivetimeplaner/www/fahrschuldesk.de/) - └── git commit + push - ↓ -Git (https://git.shadow-land.de/madgerm/drivetimeplaner) - ↓ git push (oder rsync via CI/CD) -Server (/srv/web/fahrschuldesk/domains/fahrschuldesk.de/public/) -``` +```yaml +# .github/workflows/deploy.yml -### Sync-Befehle -```bash -# Server → Lokal (Daten holen) -rsync -az --exclude='storage/sessions/' --exclude='storage/database/' \ - -e "ssh -p 2225" fahrschuldesk@fahrschuldesk.de:/srv/web/fahrschuldesk/domains/ www/ +name: Deploy -# Lokal → Server (deployen) -rsync -az --exclude='storage/sessions/' --exclude='storage/database/' \ - -e "ssh -p 2225" www/fahrschuldesk.de/public/ \ - fahrschuldesk@fahrschuldesk.de:/srv/web/fahrschuldesk/domains/fahrschuldesk.de/public/ +on: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: PHP Setup + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: pgsql, curl + - name: Run Tests + run: composer test + + deploy-api: + needs: test + runs-on: ubuntu-latest + steps: + - name: Sync to Server + uses: appleboy/scp-action@master + with: + host: fahrschuldesk.de + port: 2225 + username: fahrschuldesk + key: ${{ secrets.SSH_KEY }} + source: "api/*" + target: "/srv/web/fahrschuldesk/domains/api.fahrschuldesk.de/public/" + + deploy-frontend: + needs: test + runs-on: ubuntu-latest + steps: + - name: Build Vue + run: | + npm ci + npm run build + - name: Sync to Server + uses: appleboy/scp-action@master + with: + host: fahrschuldesk.de + port: 2225 + username: fahrschuldesk + key: ${{ secrets.SSH_KEY }} + source: "dist/*" + target: "/srv/web/fahrschuldesk/domains/fahrschuldesk.de/public/" ``` --- -## 9. Offene Fragen / Next Steps +## 16. Offene Fragen / Next Steps - [ ] Tech-Stack final abstimmen (Laravel vs. Slim vs. anderes?) -- [ ] Auth-Flow: Login-Seite designen -- [ ] Erste Migration: Plattform-Admins + Tenants + Users -- [ ] Mockups/UI-Design für Dashboard -- [ ] Premium-Lerninhalte: woher kommen? Eigene Erstellung oder Drittanbieter? +- [ ] PayPal Integration: API Keys besorgen +- [ ] Design-System erstellen (Default-Theme) +- [ ] OpenAPI Spec schreiben +- [ ] Erste Migration: Alle Tabellen +- [ ] Auth-Flow: Login + Registration + E-Mail-Verifizierung +- [ ] Messenger: WebSocket-Integration planen +- [ ] Self-Hosted: Docker-Compose vorbereiten --- -## 10. Phase 1 — Erste Schritte +## 17. Phase 1 — Erste Schritte ### Ziel: Grundgerüst stehen 1. **DB-Schema:** Migrationen schreiben für alle Tabellen -2. **API-Skelett:** Auth + Tenant-CRUD + Module-CRUD -3. **Frontend-Skelett:** Vue 3 + Vite + Router + Pinia +2. **API-Skelett:** Auth + Tenant-Registration + Admin-CRUD + Module-CRUD +3. **Frontend-Skelett:** Vue 3 + Vite + Router + Pinia + TailwindCSS 4. **Login-Seite:** Plattform-Admin Login -5. **Admin-Dashboard:** Tenants auflisten, anlegen, Module zuweisen +5. **Registration:** Self-Service + Admin-Freigabe +6. **Admin-Dashboard:** Tenants auflisten, anlegen, Module zuweisen +7. **Branding:** Logo-Upload, Farben wählen +8. **Audit-Log:** Basis-Implementierung + +--- + +## 18. Datei-Ablage (Storage) + +### SaaS +``` +/srv/web/fahrschuldesk/storage/ +├── tenants/{tenant_id}/ +│ ├── files/ +│ │ ├── vehicles/ +│ │ ├── students/ +│ │ └── ... +│ ├── avatars/ +│ └── logos/ +└── backups/ +``` + +### Self-Hosted +``` +/var/www/fahrschuldesk/storage/ +├── files/ +├── avatars/ +├── logos/ +└── backups/ +``` \ No newline at end of file