Initial commit: Beisel Rallye Graz Project with full documentation

This commit is contained in:
2026-06-29 12:20:12 +02:00
commit 7bdc792019
9 changed files with 2193 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
# API-Design: Beisel Rallye 🍺
## Übersicht
Die API ermöglicht server-seitige Speicherung aller Rallye-Einträge und unterstützt echte Multi-User-Funktionalität über verschiedene Geräte hinweg.
## Endpunkte
### 1. Bezirk besuchen (POST /api/visit)
**Zweck:** Neuen Besuch eines Bezirks speichern
**Request Body:**
```json
{
"user_name": "Gregor",
"district": "I. Innere Stadt",
"beisel_name": "Schloßhotel Greising"
}
```
**Response (201 Created):**
```json
{
"success": true,
"message": "Bezirk gespeichert!",
"data": {
"id": 6,
"user_name": "Gregor",
"district": "I. Innere Stadt",
"beisel_name": "Schloßhotel Greising",
"timestamp": "2026-06-29T10:30:00.000Z"
},
"progress": {
"visited_count": 8,
"total_districts": 17,
"percentage": 47
}
}
```
**Fehler (400 Bad Request):**
```json
{
"success": false,
"error": "Ungültige Eingabe",
"details": {
"user_name": "Pflichtfeld",
"district": "Ungültiger Bezirk"
}
}
```
---
### 2. Fortschritt abrufen (GET /api/status/:username)
**Zweck:** Aktuelle Rallye-Statistik für einen Benutzer laden
**Response (200 OK):**
```json
{
"user_name": "Gregor",
"progress": {
"visited_count": 8,
"total_districts": 17,
"percentage": 47
},
"visits": [
{
"district": "I. Innere Stadt",
"beisel_name": "Schloßhotel Greising",
"timestamp": "2026-06-29T10:30:00.000Z"
},
{
"district": "II. St. Leonhard",
"beisel_name": "Jazzkeller",
"timestamp": "2026-06-29T11:15:00.000Z"
}
],
"last_updated": "2026-06-29T11:15:00.000Z"
}
```
---
### 3. Alle Teilnehmer anzeigen (GET /api/all-users)
**Zweck:** Leaderboard mit allen aktiven Teilnehmern
**Response (200 OK):**
```json
{
"users": [
{
"user_name": "Gregor",
"visited_count": 8,
"last_active": "2026-06-29T11:15:00.000Z"
},
{
"user_name": "Maria",
"visited_count": 12,
"last_active": "2026-06-29T10:45:00.000Z"
}
],
"total_users": 2,
"generated_at": "2026-06-29T12:00:00.000Z"
}
```
---
### 4. Reset für User (DELETE /api/reset/:username)
**Zweck:** Alle Einträge eines Users löschen
**Response (200 OK):**
```json
{
"success": true,
"message": "Alle Einträge für 'Gregor' gelöscht",
"deleted_count": 5
}
```
---
## Fehlerbehandlung
### Allgemeine Fehler (4xx/5xx)
```json
{
"error_code": "INTERNAL_ERROR",
"message": "Unerwarteter Serverfehler",
"timestamp": "2026-06-29T12:00:00.000Z"
}
```
### Validierungsfehler (400)
```json
{
"error_code": "VALIDATION_ERROR",
"message": "Ungültige Eingabe",
"details": {
"field_name": ["Fehlermeldung"]
}
}
```
---
## Implementierungs-Hinweise
### Datenbank-Operationen
- Verwende `sqlite3` Package für asynchrone Operationen
- Parameterisierte Queries gegen SQL Injection schützen
- Connection Pooling für bessere Performance
### Rate Limiting
- Max. 10 Anfragen pro Minute pro IP
- Besonders bei POST /api/visit wichtig
### Sicherheit
- Input Validation auf allen Endpunkten
- User-Namen sanitieren (keine Script-Injection)
- CORS Headers setzen falls nötig
### Performance
- SQLite ist ausreichend für diese Anwendung
- Keine komplexen Joins notwendig
- Periodische Datenbank-Vacuum Operationen empfohlen
---
*Stand: 2026-06-29 | Version: 1.0*
+112
View File
@@ -0,0 +1,112 @@
# Architektur-Dokumentation: Beisel Rallye 🍺
## Systemübersicht
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Browser │──────→│ Express Server │──────→│ SQLite DB │
│ (Client) │ │ (:8083) │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
↕ ↕
Nginx Reverse Proxy beisel_rally.db
(aufeins.click.jetzt) :8083
```
## Komponenten
### 1. Frontend (Browser)
- **Technologie:** Vanilla JavaScript + EJS Templates
- **Responsibility:** UI-Rendering, User-Interaktion, Client-Side Validation
- **Speicherung:** Aktuelle Implementierung nutzt localStorage (→ wird durch Server API ersetzt)
### 2. Backend (Express.js)
- **Port:** 8083
- **Routing:** Express Router
- **Datenbank:** SQLite via `sqlite3` Package
- **Middleware:** body-parser, CORS (geplant)
### 3. Datenbank (SQLite)
- **Datei:** `beisel_rally.db`
- **Schema:** Einfache Tabelle `rally_logs`
- **Skalierung:** Ausreichend für erwartete User-Anzahl (<1000)
## Datenfluss
### Aktuelle Implementierung (localStorage-basiert):
```
User → Browser UI → localStorage → (keine Server-Kommunikation)
```
### Ziel-Implementierung (Server-seitig):
```
User → Browser UI → API Request → Express Server → SQLite DB
↑ |
└─────────────── API Response ←────────────────┘
```
## Datenbank-Schema
```sql
CREATE TABLE rally_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT NOT NULL,
district TEXT NOT NULL,
beisel_name TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Index für häufige Queries
CREATE INDEX idx_user_district ON rally_logs(user_name, district);
```
## Deployment-Architektur
### Nginx Reverse Proxy
- **Funktion:** SSL-Terminierung, Load Balancing (geplant)
- **Domain:** aufeins.click.jetzt
- **Backend:** 192.168.0.187:8083
- **SSL:** Let's Encrypt automatisch
### Server-Setup
```bash
# Abhängigkeiten installieren
cd /home/claw/.openclaw/agents/wisp/workspace/beisel-rally
npm install
# Server starten
node index.js
# Im Hintergrund laufen lassen (optional)
nohup node index.js > server.log 2>&1 &
```
## Skalierungsbetrachtung
### Aktuelle Kapazitäten
- **SQLite:** Bis ~1M Einträge performant
- **Express:** Bis ~100 req/s ohne Optimierung
- **Nginx:** Hoch skalierbar
### Erweiterungsmöglichkeiten
1. **PostgreSQL Migration** bei >10k Usern
2. **Redis Caching** für statische Daten (Bezirke, Beiseln)
3. **Load Balancing** mit mehreren Express Instanzen
4. **WebSocket** für Echtzeit-Updates
## Sicherheitshinweise
### Aktuelle Schwachstellen
- ❌ Keine Input Validation auf Server-Side
- ❌ Keine Rate Limiting
- ❌ SQL Injection möglich (bei korrekter Implementierung)
- ❌ CORS nicht konfiguriert
### Verbesserungen
- ✅ Parameterisierte Queries verwenden
- ✅ Rate Limiting implementieren
- ✅ CORS Headers setzen
- ✅ Input Sanitization hinzufügen
- ✅ HTTPS erzwingen (bereits durch Nginx gegeben)
---
*Stand: 2026-06-29 | Version: 1.0*
+183
View File
@@ -0,0 +1,183 @@
# Entwicklungshandbuch: Beisel Rallye 🍺
## Schnellstart
### Voraussetzungen
- Node.js v18+
- npm oder yarn
- SQLite3 (wird via npm installiert)
### Setup
```bash
# In das Projektverzeichnis wechseln
cd /home/claw/.openclaw/agents/wisp/workspace/beisel-rally
# Abhängigkeiten installieren
npm install
# Server starten
node index.js
# Server läuft auf http://localhost:8083
```
## Code-Struktur
### Wichtige Dateien
- `index.js` - Hauptserver mit Express App
- `views/index.ejs` - Frontend Template
- `public/css/style.css` - Styling
- `beisel_rally.db` - SQLite Datenbank
- `package.json` - Dependencies und Scripts
### Entwicklungs-Wiederholungszyklus
1. Code ändern
2. Server neu starten (`Ctrl+C`, dann `node index.js`)
3. Im Browser testen (Cache leeren!)
4. Git commit (wenn fertig)
## Wichtige Konzepte
### Express Middleware
```javascript
// Body Parser für JSON Requests
app.use(express.json());
// Statische Dateien servieren
app.use(express.static('public'));
// Custom Middleware (z.B. Logging)
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
```
### SQLite Operationen
```javascript
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./beisel_rally.db');
// Query ausführen
db.run("INSERT INTO rally_logs (user_name, district) VALUES (?, ?)",
["Gregor", "I. Innere Stadt"], function(err) {
if (err) {
console.error('Insert fehlgeschlagen:', err);
return;
}
console.log('Erfolgreich eingefügt, ID:', this.lastID);
});
// Daten abrufen
db.all("SELECT * FROM rally_logs WHERE user_name = ?", ["Gregor"], (err, rows) => {
if (err) {
console.error('Query fehlgeschlagen:', err);
return;
}
console.log(rows);
});
```
### Fehlerbehandlung
```javascript
// Try-Catch für async Operationen
try {
const result = await someAsyncOperation();
res.json({ success: true, data: result });
} catch (error) {
console.error('Fehler:', error);
res.status(500).json({
success: false,
error: 'Interner Serverfehler'
});
}
```
## Testing
### Manuelles Testen
1. Server starten
2. Browser öffnen: `http://localhost:8083`
3. API Endpunkte testen mit curl oder Postman:
```bash
# New Visit testen
curl -X POST http://localhost:8083/api/visit \
-H "Content-Type: application/json" \
-d '{"user_name":"Gregor","district":"I. Innere Stadt","beisel_name":"Test"}'
# Status abrufen
curl http://localhost:8083/api/status/Gregor
```
### Datenbank-Checks
```bash
# SQLite CLI starten
sqlite3 beisel_rally.db
# Alle Einträge anzeigen
SELECT * FROM rally_logs;
# Statistik pro User
SELECT user_name, COUNT(*) as visits FROM rally_logs GROUP BY user_name;
```
## Debugging
### Häufige Probleme
**Server startet nicht:**
- Port 8083 ist belegt? → `lsof -i :8083` und Prozess killen
- Node.js Version zu alt? → `node --version` (braucht v18+)
**Datenbank Fehler:**
- Datei nicht gefunden? → Prüfen ob `beisel_rally.db` existiert
- Permission denied? → `chmod 664 beisel_rally.db` und Gruppe prüfen
**Frontend lädt nicht:**
- Cache geleert? → Hard Reload (Ctrl+Shift+R)
- CORS Fehler? → Server Logs checken
## Best Practices
### Code-Qualität
- **ESLint** für JavaScript Linting
- **Prettier** für konsistentes Formatting
- **Git Commits** mit klaren Nachrichten
- **Comments** erklären das "Warum", nicht das "Was"
### Sicherheit
- **Input Validation** auf allen Endpunkten
- **Parameterisierte Queries** gegen SQL Injection
- **Rate Limiting** für API Endpunkte
- **HTTPS** erzwingen (bereits durch Nginx gegeben)
### Performance
- **Datenbank-Verbindungen** schließen wenn nicht benötigt
- **Caching** für statische Daten (Bezirke, Beiseln)
- **Pagination** bei großen Datensätzen
- **Lazy Loading** für komplexe Views
## Deployment Checklist
Vor dem Deployen prüfen:
- [ ] Alle Tests bestanden
- [ ] Datenbank-Schema aktuell
- [ ] Environment-Variablen gesetzt (falls nötig)
- [ ] Logging konfiguriert
- [ ] Backup-Mechanismus vorhanden
- [ ] Monitoring/Alerting eingerichtet
## Ressourcen
### Offizielle Dokumentation
- [Express.js](https://expressjs.com/)
- [SQLite3 Node.js](https://github.com/TryGhost/node-sqlite3)
- [Nginx Documentation](https://nginx.org/en/docs/)
### Lokale Hilfe
- `PROJECT.md` - Projektübersicht
- `API_DESIGN.md` - API Spezifikation
- `ARCHITECTURE.md` - System-Architektur
---
*Stand: 2026-06-29 | Version: 1.0*
+131
View File
@@ -0,0 +1,131 @@
# Beisel Rallye Graz 🍺
## Projektübersicht
Interaktive Website für eine Bier-Rallye durch 17 Grazer Bezirke. Teilnehmende tragen bei jedem besuchten Bezirk das entsprechende Beisel (Bierlokal) ein und verfolgen ihren Fortschritt.
### Konzept
- **Ziel:** Alle 17 Grazer Bezirke besuchen
- **Aktivität:** Für jeden Bezirk ein lokales Beisel finden und eintragen
- **Zeitrahmen:** 24 Stunden Rallye
- **Multi-User:** Mehrere Teilnehmer können parallel teilnehmen
## Technologie-Stack
- **Backend:** Express.js (Node.js)
- **Frontend:** EJS Templates + Vanilla JavaScript
- **Datenbank:** SQLite (beisel_rally.db)
- **Styling:** Custom CSS mit Wirtshaus-Theme
- **Deployment:** Nginx Reverse Proxy auf `aufeins.click.jetzt`
## Architektur
```
beisel-rally/
├── index.js # Express Server (Port 8083)
├── package.json # Dependencies & Scripts
├── beisel_rally.db # SQLite Datenbank
├── server.log # Laufzeit-Logs
├── PROJECT.md # Diese Dokumentation
├── public/
│ └── css/
│ └── style.css # Responsive Styling
└── views/
└── index.ejs # Haupttemplate mit Client-Side Logic
```
## Datenbank-Design
### Tabelle: `rally_logs`
```sql
CREATE TABLE rally_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_name TEXT NOT NULL,
district TEXT NOT NULL,
beisel_name TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
**Zweck:** Server-seitige Speicherung aller Rallye-Einträge für persistente Multi-User-Fähigkeit.
## API-Endpunkte (geplant)
### Aktuelle Situation
Derzeit wird **nur localStorage** im Browser genutzt. Die SQLite-Datenbank ist vorhanden, wird aber nicht integriert.
### Zu implementierende Endpunkte:
- `POST /api/visit` - Neuen Bezirk besuchen
- `GET /api/status/:username` - Fortschritt abrufen
- `GET /api/all-users` - Alle Teilnehmer anzeigen
- `DELETE /api/reset/:username` - Reset für User
## Deployment
### Nginx Konfiguration
```nginx
server {
listen 443 ssl;
server_name aufeins.click.jetzt;
ssl_certificate /etc/letsencrypt/live/aufeins.click.jetzt/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/aufeins.click.jetzt/privkey.pem;
location / {
proxy_pass http://192.168.0.187:8083;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
### Server Starten
```bash
cd /home/claw/.openclaw/agents/wisp/workspace/beisel-rally
node index.js
# Server läuft auf http://localhost:8083
```
## Entwicklungs-Status
### ✅ Abgeschlossen
- Grundlegende Frontend-Struktur
- Responsive Design mit Mobile-Support
- User-Auswahl und -Verwaltung (localStorage)
- Fortschrittsanzeige
- Wirtshaus-Theme Implementierung
### ❌ Offene Punkte
1. **SQLite-Integration** - API-Endpunkte für Datenbank-Zugriff
2. **Server-Datenpersistenz** - localStorage → Server-Side Storage
3. **Deployment** - Backend-Server auf 192.168.0.187:8083 starten
4. **Sicherheit** - Input Validation, Rate Limiting
5. **Export/Import** - Backup-Funktionalität
## Nächste Schritte
### Kurzfristig (Prio 1)
- [ ] API-Endpunkte für SQLite-Datenbank implementieren
- [ ] Client-Seite anpassen um API zu nutzen
- [ ] Server starten und testen
### Mittelfristig (Prio 2)
- [ ] Authentifizierung hinzufügen
- [ ] Leaderboard implementieren
- [ ] Export/Import Funktionalität
### Langfristig (Prio 3)
- [ ] Mobile App (Progressive Web App)
- [ ] Social Features (Teilen, Einladungen)
- [ ] Anbindung an bestehende Beisel-Datenbank
## Kontakt & Credits
Projekt erstellt von Gregor. Entwicklung durch Wisp (AI Coding Agent).
**Domain:** aufeins.click.jetzt
**Server:** 192.168.0.187:8083
**Gitea:** http://gitea.click.jetzt:3000/clawdia/beisel-rally
---
*Prost! 🍻*
BIN
View File
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
const express = require('express');
const path = require('path');
const app = express();
const port = 8083;
// Kein Caching für HTML
app.use((req, res, next) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.set('Pragma', 'no-cache');
res.set('Expires', '0');
res.set('Surrogate-Control', 'no-store');
next();
});
app.use(express.static(path.join(__dirname, 'public')));
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.get('/', (req, res) => {
res.render('index');
});
app.listen(port, () => {
console.log(`Beisel-Rallye läuft auf http://localhost:${port}`);
});
+1548
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
{
"name": "beisel-rally",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^2.2.2",
"ejs": "^6.0.1",
"express": "^5.2.1",
"sqlite3": "^6.0.1"
}
}
+1
View File
@@ -0,0 +1 @@
Beisel-Rallye läuft auf http://localhost:8083