# FocusApp - Todo Application Eine moderne, minimalistische Todo-Anwendung mit Drag & Drop, gebaut mit React und ASP.NET Core. ## πŸš€ Features - βœ… **Todo-Verwaltung** - Erstellen, Bearbeiten, Loschen von Aufgaben - 🎯 **Drag & Drop** - Intuitive Neuordnung der Aufgaben - πŸ“± **Responsive Design** - Optimiert fur Desktop und Mobile - πŸ”„ **RESTful API** - Saubere Backend-Architektur [![UI](docs/screenshot_thumbnail.png)](docs/screenshot.png) ## πŸ“‹ Voraussetzungen - **Docker** und **Docker Compose** - **MariaDB** (externer Host, z.B. LXC Container) - **Git** ## 🐳 Deployment ### 1. MariaDB initialisieren (einmalig) Die FocusApp benΓΆtigt eine MariaDB Datenbank. Das Init-Script erstellt die Datenbank und den User auf dem externen MariaDB-Server. ```bash # .env aus Template erstellen und editieren cp .env.example .env vim .env # Init-Script als MariaDB Root ausfuehren mysql -h $DB_HOST -u root -p < init-db.sql ``` Die `.env.example` enthalt: ``` DB_HOST=maria-db-server.domain.local DB_PORT=3306 DB_NAME=focusapp DB_USER=focusapp DB_PASSWORD= ``` ### 2. Docker Image bauen ```bash docker compose build ``` Oder direkt aus dem GitHub Container Registry pullen (nach dem ersten CI-Run): ```bash docker compose pull ``` ### 3. App starten ```bash docker compose up -d ``` Die App laeuft auf: `http://localhost:5000` ### 4. Logs anzeigen ```bash docker compose logs -f ``` ## πŸ”„ CI/CD mit GitHub Actions Bei jedem Push auf `main` wird automatisch: 1. Das Docker-Image gebaut (Multi-Stage: Node + .NET) 2. Nach `ghcr.io` (GitHub Container Registry) gepusht 3. Alte Images aufgeraeumt (letzte 4 bleiben erhalten) ### Manuelles Deployment auf dem Host ```bash # Neuestes Image pullen docker compose pull # Container neustarten docker compose up -d ``` ## βš™οΈ Konfiguration Die App wird ueber Umgebungsvariablen konfiguriert, die in `docker-compose.yml` gesetzt werden: | Variable | Beschreibung | |---|---| | `ASPNETCORE_ENVIRONMENT` | `Production` oder `Development` | | `DB_HOST` | MariaDB Host | | `DB_PORT` | MariaDB Port | | `DB_NAME` | Datenbankname | | `DB_USER` | Datenbank-User | | `DB_PASSWORD` | Datenbank-Passwort | ## πŸ“‘ API Endpoints ### Todos abrufen ```http GET /api/focustasks ``` ### Todo erstellen ```http POST /api/focustasks Content-Type: application/json { "title": "Neue Aufgabe", "description": "Optional" } ``` ### Todo aktualisieren ```http PUT /api/focustasks/{id} Content-Type: application/json { "id": 1, "title": "Geaendert", "description": "Neue Beschreibung" } ``` ### Todo loeschen ```http DELETE /api/focustasks/{id} ``` ### Reihenfolge aktualisieren ```http POST /api/focustasks/reorder Content-Type: application/json [1, 3, 2, 4] ``` ### Health Check ```http GET /api/health ``` Response (200 OK): ```json { "status": "healthy", "database": "connected", "timestamp": "2026-08-09T12:00:00Z", "version": "2.0.0" } ``` Response (503 Service Unavailable): ```json { "status": "unhealthy", "database": "disconnected", "timestamp": "2026-08-09T12:00:00Z", "version": "2.0.0" } ``` **Uptime Kuma Konfiguration:** - URL: `http://localhost:5000/api/health` - Methode: GET - Erwarteter Status: 200 - Intervall: 60 Sekunden **Docker Healthcheck:** - Automatisch in `docker-compose.yml` konfiguriert - PrΓΌft alle 30 Sekunden den Health Endpoint - Container wird als `healthy`/`unhealthy` markiert ## 🎯 Entwicklung ### Demo-Modus Ohne MariaDB lΓ€uft die App automatisch im Demo-Modus mit SQLite und 4 Beispielaufgaben: ```bash dotnet run ``` - Frontend: `http://localhost:5000` - Swagger: `http://localhost:5000/swagger` - Datenbank: `focusapp-demo.db` (automatisch erstellt) FΓΌr Produktion mit MariaDB simply die `.env` ausfΓΌllen (siehe Deployment). ### Backend (Development) ```bash dotnet run ``` API laeuft auf: `http://localhost:5000` ### Frontend (Development) ```bash cd client npm install npm run dev ``` React Dev Server laeuft auf: `http://localhost:5173` API-Calls werden automatisch an `http://localhost:5000` weitergeleitet (siehe `vite.config.ts`). ### Tests **Backend (xUnit):** ```bash cd FocusApp.Tests dotnet test ``` **Frontend (ESLint):** ```bash cd client npm run lint ``` ## πŸ“¦ Projektstruktur ``` FocusApp/ β”œβ”€β”€ client/ # React Frontend (TypeScript, Vite) β”‚ β”œβ”€β”€ src/ β”‚ β”‚ β”œβ”€β”€ components/ # React Komponenten β”‚ β”‚ β”‚ β”œβ”€β”€ TaskCard.tsx β”‚ β”‚ β”‚ └── TaskForm.tsx β”‚ β”‚ β”œβ”€β”€ styles/ # CSS Design System β”‚ β”‚ β”œβ”€β”€ App.tsx # Haupt-App β”‚ β”‚ β”œβ”€β”€ api.ts # Axios API Client β”‚ β”‚ β”œβ”€β”€ types.ts # TypeScript Types β”‚ β”‚ └── main.tsx # Entry Point β”‚ β”œβ”€β”€ build/ # Production Build β”‚ └── package.json β”œβ”€β”€ Controllers/ β”‚ └── FocusTasksController.cs β”œβ”€β”€ Data/ β”‚ └── FocusContext.cs # EF Core DbContext β”œβ”€β”€ Models/ β”‚ β”œβ”€β”€ FocusTask.cs # Domain Model β”‚ └── Dtos.cs # API Data Transfer Objects β”œβ”€β”€ Properties/ β”œβ”€β”€ Program.cs # ASP.NET Startup β”œβ”€β”€ FocusApp.csproj # Projekt-Datei β”œβ”€β”€ FocusApp.sln # Solution File β”œβ”€β”€ Directory.Build.props β”œβ”€β”€ appsettings.json # Config β”œβ”€β”€ Dockerfile # Multi-Stage Docker Build β”œβ”€β”€ docker-compose.yml # Docker Compose Konfiguration β”œβ”€β”€ .env.example # Environment Template β”œβ”€β”€ init-db.sql # MariaDB Init Script β”œβ”€β”€ LICENSE # MIT License └── renovate.json # Dependency Updates ``` ## πŸ› Troubleshooting ### Container startet nicht ```bash # Logs pruefen docker compose logs app # Container Status docker compose ps ``` ### Datenbank-Fehler ```bash # Pruefen ob MariaDB erreichbar ist mysql -h $DB_HOST -u $DB_USER -p # DB neu initialisieren mysql -h $DB_HOST -u root -p < init-db.sql ``` ### Port bereits belegt Port in `docker-compose.yml` aendern: ```yaml ports: - "5001:5000" ``` ## πŸ” Sicherheit ### Authentifizierung mit Authelia Die FocusApp hat **keine eigene Authentifizierung**. Alle Endpoints sind offen zugΓ€nglich. FΓΌr einen Produktiveinsatz **muss** die App hinter einem Authentifizierungs-Proxy betrieben werden. [Authelia](https://www.authelia.com/) stellt Single Sign-On (SSO) bereit und schΓΌtzt die App mit einem Reverse Proxy. **Architektur:** ``` Browser β†’ nginx + Authelia β†’ FocusApp (Docker) ↓ MariaDB (extern) ``` **Voraussetzungen:** - Authelia lΓ€uft als eigener Container/Dienst - nginx als Reverse Proxy mit Authelia-Integration **nginx-Konfiguration mit Authelia:** ```nginx # Authelia snippet einbinden include /etc/nginx/authelia/authelia-location.conf; server { listen 443 ssl; server_name focus.example.com; ssl_certificate /etc/ssl/certs/focus.crt; ssl_certificate_key /etc/ssl/private/focus.key; # Schuetzt alle Routes hinter Authelia location / { include /etc/nginx/authelia/authelia-authrequest.conf; proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Remote-User $remote_user; } } ``` **Authelia-Konfiguration (`configuration.yml`):** ```yaml access_control: default_policy: one_factor rules: # API-Endpoints ebenfalls schuetzen - domain: focus.example.com resources: - "^/api/.*$" policy: one_factor session: name: focusapp_session secret: "your-session-secret" cookies: - domain: focus.example.com authelia_url: https://auth.example.com default_redirection_url: https://focus.example.com identity_providers: - id: ldap # oder: - id: openid_connect ``` **Wichtig:** - Ohne Authelia ist die App komplett offen β€” kein Schutz fΓΌr API oder Frontend - Authelia prΓΌft vor jedem Request die Sitzung - Der `/api/` Pfad muss ebenfalls geschΓΌtzt werden (nicht nur `/`) - FΓΌr API-Clients (z.B. Mobile Apps) kann eine API-Key-LΓΆsung implementiert werden ### Reverse Proxy (nginx) ohne Authelia FΓΌr lokale Entwicklung/Tests ohne Auth: ```nginx server { listen 443 ssl; server_name focus.example.com; ssl_certificate /etc/ssl/certs/focus.crt; ssl_certificate_key /etc/ssl/private/focus.key; location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` --- ## License Licensed under the [MIT License](LICENSE) - Copyright (c) 2026 Stefan Koelle (https://stefankoelle.de)