StimmArt ist eine moderne Web-Anwendung für die Verwaltung und Präsentation eines Pop-Chors. Die Website bietet sowohl einen öffentlichen Bereich für Besucher als auch einen umfangreichen Admin-Bereich zur Content-Verwaltung.
Das Projekt war ursprünglich für den “Liederkranz Bieber 1847 e.V.” entwickelt und wurde 2025 zu StimmArt umgebrandet - einem modernen Pop-Chor der Singgemeinschaft Bieber/Wiesen.
# Mit Git
git clone <repository-url>
cd stimmart-chor
# Oder ZIP herunterladen und entpacken
unzip Stimmart-Final.zip
cd projectnpm installDies installiert alle benötigten Pakete aus der
package.json.
-- In MySQL/MariaDB-Konsole
CREATE DATABASE website CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;# Aus dem Projekt-Verzeichnis
mysql -u root -p website < mysql-migrations/MASTER_complete_schema.sqlHinweis: Das MASTER_complete_schema.sql
enthält das komplette Schema mit allen Tabellen.
Erstelle die Datei mysql.conf im Projekt-Root:
HOST=localhost
PORT=3306
DATABASE=website
USERNAME=dein_mysql_benutzer
PASSWORD=dein_mysql_passwortWichtig: Die Datei mysql.conf sollte
NICHT in Git eingecheckt werden (steht in .gitignore).
Kopiere die Beispiel-Datei:
cp smtp.conf.example smtp.confBearbeite smtp.conf:
HOST=smtp.gmail.com
PORT=587
SECURE=false
USERNAME=deine-email@gmail.com
PASSWORD=dein-app-passwort
FROM_EMAIL=noreply@stimmart-chor.de
FROM_NAME=StimmArt Website
TO_EMAIL=info@stimmart-chor.deGmail-Hinweis: Verwende ein App-Passwort, nicht dein normales Passwort.
npm run devDies startet: - Frontend auf
http://localhost:90 - Backend-API auf
http://localhost:3001
Erstelle einen Admin-Benutzer direkt in der Datenbank:
-- Passwort: "admin123" (bitte in Produktion ändern!)
INSERT INTO site_settings (id, admin_password)
VALUES (1, 'admin123')
ON DUPLICATE KEY UPDATE admin_password = 'admin123';Dann öffne: http://localhost:90/admin
Das Projekt verwendet primär Konfigurationsdateien
(.conf), unterstützt aber auch Environment-Variablen:
# Für MySQL
export MYSQL_HOST=localhost
export MYSQL_PORT=3306
export MYSQL_DATABASE=website
export MYSQL_USERNAME=user
export MYSQL_PASSWORD=password
# Für SMTP
export SMTP_HOST=smtp.gmail.com
export SMTP_PORT=587
export SMTP_SECURE=false
export SMTP_USERNAME=user@gmail.com
export SMTP_PASSWORD=password
export SMTP_FROM_EMAIL=noreply@domain.de
export SMTP_FROM_NAME="StimmArt"
export SMTP_TO_EMAIL=info@domain.de
# Für Node.js
export NODE_ENV=production
export PORT=3001Die Ports sind in verschiedenen Dateien definiert:
package.json →
"dev": "vite --port 90"src/server/index-mysql.ts →
Port 3001# Development
npm run dev # Startet Frontend + Backend
npm run dev:frontend # Nur Frontend
npm run dev:backend:mysql # Nur Backend mit MySQL
# Build
npm run build # Frontend-Build
npm run build:server # Backend-Build (TypeScript → JavaScript)
npm run build:all # Frontend + Backend
# Production
npm start # Startet Production-Server
npm run start:mysql # Startet MySQL-Server
# Code Quality
npm run lint # ESLint-Check
npm run lint:fix # ESLint Auto-Fix
npm run format # Code formatieren mit Prettier
npm run format:check # Format-Check
npm run type-check # TypeScript-Typ-Check
# Testing
npm test # Tests ausführen
npm run test:ui # Vitest UI
npm run test:coverage # Test-Coverage
# Cleanup
npm run clean # Löscht dist/, dist-server/, build/project/
├── src/
│ ├── components/ # React-Komponenten
│ │ ├── admin/ # Admin-Panel Komponenten
│ │ │ ├── AboutPage.tsx
│ │ │ ├── AdminLayout.tsx
│ │ │ ├── BieberInfoPage.tsx
│ │ │ ├── ContactFormPage.tsx
│ │ │ ├── ContactPage.tsx
│ │ │ ├── DashboardHome.tsx
│ │ │ ├── DashboardLayout.tsx
│ │ │ ├── FontsPage.tsx
│ │ │ ├── GalleryPage.tsx
│ │ │ ├── ImprintPage.tsx
│ │ │ ├── NewsPage.tsx
│ │ │ ├── PosterImagesPage.tsx
│ │ │ ├── PostersPage.tsx
│ │ │ ├── PrivacyPage.tsx
│ │ │ ├── SectionsPage.tsx
│ │ │ ├── SettingsPage.tsx
│ │ │ ├── SlideshowPage.tsx
│ │ │ ├── StimmArtPage.tsx
│ │ │ ├── VereinePage.tsx
│ │ │ ├── WiesenInfoPage.tsx
│ │ │ └── YoungStarsPage.tsx
│ │ ├── testing/ # Test-Komponenten
│ │ │ └── FontTesting.tsx
│ │ ├── AdminDashboard.tsx
│ │ ├── AdminLogin.tsx
│ │ ├── BieberFooter.tsx
│ │ ├── BieberHeader.tsx
│ │ ├── ContactFormSection.tsx
│ │ ├── CookieConsent.tsx
│ │ ├── CustomSection.tsx
│ │ ├── DynamicFontLoader.tsx
│ │ ├── ErrorBoundary.tsx
│ │ ├── Footer.tsx
│ │ ├── Header.tsx
│ │ ├── Imprint.tsx
│ │ ├── NewsSection.tsx
│ │ ├── PosterGallery.tsx
│ │ ├── PostersSection.tsx
│ │ ├── Privacy.tsx
│ │ ├── SEOHead.tsx
│ │ ├── Slideshow.tsx
│ │ ├── ThemeToggle.tsx
│ │ ├── VereineSection.tsx
│ │ ├── VoiceTypeSection.tsx
│ │ ├── WiesenFooter.tsx
│ │ ├── WiesenHeader.tsx
│ │ ├── WiesenInfo.tsx
│ │ └── YoungStarsSection.tsx
│ ├── contexts/ # React Contexts
│ │ └── ThemeContext.tsx # Dark/Light Mode
│ ├── lib/ # Utilities & Services
│ │ ├── analytics.ts
│ │ ├── database.types.ts
│ │ ├── db.ts # Frontend DB API
│ │ ├── design-system.ts
│ │ ├── frontend-db.ts
│ │ ├── image-storage.ts
│ │ ├── mysql-connection.ts # MySQL-Verbindung
│ │ ├── mysql-db.ts # MySQL-Datenbankschicht
│ │ ├── session.ts
│ │ ├── slug.ts
│ │ └── smtp-config.ts # SMTP-Konfiguration
│ ├── server/ # Backend
│ │ ├── middleware/
│ │ │ ├── errorHandler.ts
│ │ │ └── validation.ts
│ │ ├── routes/
│ │ │ └── mysql-api.ts # MySQL API-Routen
│ │ ├── index.ts # JSON-basierter Server (Legacy)
│ │ ├── index-http.ts # HTTP-Server
│ │ ├── index-mysql.ts # MySQL-Server (AKTUELL)
│ │ └── index-production.ts # HTTPS Production-Server
│ ├── types/ # TypeScript-Typdefinitionen
│ │ └── ntp-client.d.ts
│ ├── App.tsx # Haupt-App-Komponente
│ ├── main.tsx # React-Einstiegspunkt
│ ├── index.css # Globale Styles
│ └── vite-env.d.ts
├── public/ # Statische Dateien
│ ├── fonts/ # Hochgeladene Fonts
│ └── uploads/ # Hochgeladene Bilder
│ └── general/
├── dist/ # Frontend-Build (generiert)
├── dist-server/ # Backend-Build (generiert)
├── mysql-migrations/ # Datenbank-Migrationen
│ ├── MASTER_complete_schema.sql
│ ├── 20250112_stimmart_rebranding.sql
│ ├── 20250114_add_contact_form_field_type.sql
│ └── 20250217_add_site_settings_info_fields.sql
├── api/ # API-Hilfsdateien
│ └── upload.ts
├── mysql.conf # MySQL-Konfiguration (nicht in Git)
├── smtp.conf # SMTP-Konfiguration (nicht in Git)
├── smtp.conf.example # SMTP-Beispiel-Konfiguration
├── package.json
├── tsconfig.json # TypeScript-Konfiguration
├── tsconfig.app.json # Frontend-TypeScript-Config
├── tsconfig.server.json # Backend-TypeScript-Config
├── vite.config.ts # Vite-Konfiguration
├── tailwind.config.js # Tailwind-Konfiguration
├── eslint.config.js # ESLint-Konfiguration
├── vitest.config.ts # Vitest-Konfiguration
└── README.md
# 1. Frontend bauen
npm run build
# 2. Backend bauen
npm run build:server
# 3. Oder beides zusammen
npm run build:alldist/)dist/
├── index.html
├── assets/
│ ├── index-[hash].css # Gebündeltes CSS
│ ├── index-[hash].js # Haupt-JavaScript
│ ├── vendor-[hash].js # Third-Party-Libraries
│ ├── router-[hash].js # React Router
│ ├── ui-[hash].js # UI-Komponenten
│ └── utils-[hash].js # Utilities
├── fonts/ # Kopierte Fonts
└── uploads/ # Kopierte Uploads
dist-server/)dist-server/
├── server/
│ ├── index.js
│ ├── index-mysql.js # MySQL-Server (WICHTIG!)
│ ├── middleware/
│ │ ├── errorHandler.js
│ │ └── validation.js
│ └── routes/
│ └── mysql-api.js
└── lib/
├── mysql-connection.js
├── mysql-db.js
└── smtp-config.js
.map
Dateien)Basierend auf deiner Server-Installation:
Lokaler Rechner → Server
------------------------ ------------------------
1. Build erstellen 5. Projekt hochladen
2. Dateien komprimieren 6. Entpacken
3. ZIP hochladen 7. Dependencies installieren
4. - 8. Build (optional)
9. MySQL einrichten
10. Services starten
# Auf deinem lokalen Rechner
cd C:\Users\jonas\Desktop\project
# Build erstellen
npm run build
npm run build:serverErstelle eine ZIP-Datei mit folgenden Inhalten:
Erforderliche Dateien/Ordner:
Stimmart-Final.zip
├── package.json # WICHTIG
├── package-lock.json # WICHTIG
├── dist/ # Frontend-Build (optional, kann auf Server erstellt werden)
├── dist-server/ # Backend-Build (optional)
├── src/ # Quellcode (WICHTIG für Server-Build)
├── public/uploads/ # Hochgeladene Bilder
├── public/fonts/ # Hochgeladene Fonts
├── mysql-migrations/ # Datenbank-Schema
├── mysql.conf.example # Beispiel-Konfiguration
├── smtp.conf.example # Beispiel-Konfiguration
├── tsconfig.json # TypeScript-Config
├── tsconfig.server.json # Server-TypeScript-Config
├── vite.config.ts # Vite-Config
└── README.md
NICHT einpacken: - node_modules/ (wird
auf Server installiert) - mysql.conf (enthält Passwörter!)
- smtp.conf (enthält Passwörter!) - .git/
# Beispiel mit SCP (von lokalem Rechner)
scp Stimmart-Final.zip adminuser@server-ip:/home/adminuser/upload/
# Oder mit SFTP/FileZilla
# Hochladen nach: /home/adminuser/upload/# SSH-Verbindung zum Server
ssh adminuser@server-ip
# Zum Upload-Verzeichnis wechseln
cd /home/adminuser/upload/
# ZIP entpacken
unzip Stimmart-Final.zip -d project
# In Projekt-Verzeichnis wechseln
cd project# Node-Module installieren
npm install
# Bei Problemen: Cache leeren und neu installieren
rm -rf node_modules package-lock.json
npm install# Frontend-Build
npm run build
# Backend-Build
npm run build:serverFehler beheben:
# Wenn "vite: Permission denied"
chmod +x node_modules/.bin/vite
# Oder mit sudo (wenn nötig)
sudo npm run build
sudo npm run build:server# MySQL-Konsole öffnen
mysql -u root -p
# Datenbank erstellen (falls nicht vorhanden)
CREATE DATABASE website CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# Datenbank auswählen
USE website;
# Schema importieren
source /home/adminuser/upload/project/mysql-migrations/MASTER_complete_schema.sql;
# Beenden
exit;Alternative (von Shell aus):
cd /home/adminuser/upload/project/mysql-migrations
mysql -u root -p website < MASTER_complete_schema.sqlcd /home/adminuser/upload/project
# MySQL-Konfiguration
nano mysql.confInhalt von mysql.conf:
HOST=localhost
PORT=3306
DATABASE=website
USERNAME=dein_mysql_user
PASSWORD=dein_mysql_passwortSpeichern: Ctrl+O, Enter,
Ctrl+X
# SMTP-Konfiguration (optional)
nano smtp.confInhalt von smtp.conf:
HOST=smtp.gmail.com
PORT=587
SECURE=false
USERNAME=deine-email@gmail.com
PASSWORD=dein-app-passwort
FROM_EMAIL=noreply@stimmart-chor.de
FROM_NAME=StimmArt Website
TO_EMAIL=info@stimmart-chor.de# Root-Rechte
sudo su
# Altes Projekt sichern/entfernen
cd /var/www/Stimmart
rm -rf project/ dist/
# Neues Projekt kopieren
cp -r /home/adminuser/upload/project /var/www/Stimmart/
# dist/ Ordner separat kopieren (für Apache)
cp -r /var/www/Stimmart/project/dist /var/www/Stimmart/
# Berechtigungen setzen
chown -R www-data:www-data /var/www/Stimmart/
chmod -R 755 /var/www/Stimmart/
# Upload-Ordner beschreibbar machen
chmod -R 775 /var/www/Stimmart/project/public/uploads/
chmod -R 775 /var/www/Stimmart/project/public/fonts/Erstelle die Datei
/etc/systemd/system/stimmart-api.service:
sudo nano /etc/systemd/system/stimmart-api.serviceInhalt:
[Unit]
Description=StimmArt Chor API Server
After=network.target mysql.service
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/Stimmart/project
ExecStart=/usr/bin/npm exec tsx src/server/index-mysql.ts
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetService aktivieren:
# Service neu laden
sudo systemctl daemon-reload
# Service aktivieren (automatischer Start beim Booten)
sudo systemctl enable stimmart-api.service
# Service starten
sudo systemctl start stimmart-api.service
# Status prüfen
sudo systemctl status stimmart-api.serviceErstelle eine Virtual Host-Datei:
sudo nano /etc/apache2/sites-available/stimmart.confInhalt:
<VirtualHost *:80>
ServerName stimmart-chor.de
ServerAlias www.stimmart-chor.de
DocumentRoot /var/www/Stimmart/dist
# Frontend (statische Dateien)
<Directory /var/www/Stimmart/dist>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
# React Router Support
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</Directory>
# API-Proxy zu Node.js-Backend
ProxyPreserveHost On
ProxyPass /api http://localhost:3001/api
ProxyPassReverse /api http://localhost:3001/api
ProxyPass /health http://localhost:3001/health
ProxyPassReverse /health http://localhost:3001/health
# Uploads und Fonts
Alias /uploads /var/www/Stimmart/project/public/uploads
Alias /fonts /var/www/Stimmart/project/public/fonts
<Directory /var/www/Stimmart/project/public/uploads>
Options -Indexes
Require all granted
</Directory>
<Directory /var/www/Stimmart/project/public/fonts>
Options -Indexes
Require all granted
</Directory>
# Logs
ErrorLog ${APACHE_LOG_DIR}/stimmart_error.log
CustomLog ${APACHE_LOG_DIR}/stimmart_access.log combined
</VirtualHost>Apache-Module aktivieren und Site aktivieren:
# Erforderliche Module
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod rewrite
# Site aktivieren
sudo a2ensite stimmart.conf
# Apache neu laden
sudo systemctl reload apache2# API-Service starten
sudo systemctl start stimmart-api.service
# Apache starten
sudo systemctl start apache2
# Status prüfen
sudo systemctl status stimmart-api.service
sudo systemctl status apache2# API-Health-Check
curl http://localhost:3001/health
# Website öffnen
# Im Browser: http://deine-server-ip# API-Logs
sudo journalctl -u stimmart-api.service -f
# Apache-Logs
sudo tail -f /var/log/apache2/stimmart_error.log
sudo tail -f /var/log/apache2/stimmart_access.log# Nach Code-Änderungen
sudo systemctl restart stimmart-api.service
sudo systemctl reload apache2# Datenbank-Backup
mysqldump -u root -p website > backup_$(date +%Y%m%d).sql
# Dateien-Backup
tar -czf backup_files_$(date +%Y%m%d).tar.gz /var/www/Stimmart/project/public/uploads/Die Datenbank enthält folgende Tabellen:
site_settingsCREATE TABLE site_settings (
id INT PRIMARY KEY AUTO_INCREMENT,
site_title VARCHAR(255),
site_description TEXT,
about_title VARCHAR(255),
about_description TEXT,
about_youtube_url VARCHAR(500),
header_logo_url VARCHAR(500),
admin_password VARCHAR(255),
youngstars_enabled BOOLEAN DEFAULT false,
youngstars_title VARCHAR(255),
youngstars_description TEXT,
youngstars_logo_url VARCHAR(500),
-- ... weitere Felder
);newsCREATE TABLE news (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT,
excerpt TEXT,
date DATE,
image_url VARCHAR(500),
category VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);slidesCREATE TABLE slides (
id VARCHAR(36) PRIMARY KEY,
image_url VARCHAR(500) NOT NULL,
caption TEXT,
display_order INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);galleryCREATE TABLE gallery (
id VARCHAR(36) PRIMARY KEY,
image_url VARCHAR(500) NOT NULL,
category VARCHAR(100),
caption TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);postersCREATE TABLE posters (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
event_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);poster_imagesCREATE TABLE poster_images (
id VARCHAR(36) PRIMARY KEY,
poster_id VARCHAR(36),
image_url VARCHAR(500) NOT NULL,
display_order INT DEFAULT 0,
FOREIGN KEY (poster_id) REFERENCES posters(id) ON DELETE CASCADE
);fontsCREATE TABLE fonts (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
family_name VARCHAR(255),
uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);font_filesCREATE TABLE font_files (
id VARCHAR(36) PRIMARY KEY,
font_id VARCHAR(36),
file_path VARCHAR(500) NOT NULL,
format VARCHAR(50),
FOREIGN KEY (font_id) REFERENCES fonts(id) ON DELETE CASCADE
);custom_sectionsCREATE TABLE custom_sections (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(255),
content TEXT,
display_order INT DEFAULT 0,
is_active BOOLEAN DEFAULT true,
background_color VARCHAR(50),
text_color VARCHAR(50),
font_family VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);contact_form_settingsCREATE TABLE contact_form_settings (
id INT PRIMARY KEY AUTO_INCREMENT,
enabled BOOLEAN DEFAULT false,
title VARCHAR(255),
description TEXT,
-- ... viele weitere Felder für Texte und Styling
);contact_form_requestsCREATE TABLE contact_form_requests (
id VARCHAR(36) PRIMARY KEY,
request_number VARCHAR(20) UNIQUE,
name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
message TEXT,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
email_sent BOOLEAN DEFAULT false
);# Komplettes Schema neu erstellen
mysql -u root -p website < mysql-migrations/MASTER_complete_schema.sql
# Einzelne Migration
mysql -u root -p website < mysql-migrations/20250112_stimmart_rebranding.sql# Komplettes Backup
mysqldump -u root -p website > backup_website_$(date +%Y%m%d_%H%M%S).sql
# Nur Struktur (ohne Daten)
mysqldump -u root -p --no-data website > schema_only.sql
# Nur Daten (ohne Struktur)
mysqldump -u root -p --no-create-info website > data_only.sqlmysql -u root -p website < backup_website_20251014_120000.sql-- Tabellengrößen anzeigen
SELECT
table_name AS 'Tabelle',
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Größe (MB)'
FROM information_schema.TABLES
WHERE table_schema = 'website'
ORDER BY (data_length + index_length) DESC;
-- Anzahl Einträge pro Tabelle
SELECT
table_name AS 'Tabelle',
table_rows AS 'Anzahl Zeilen'
FROM information_schema.TABLES
WHERE table_schema = 'website';
-- Tabellen optimieren
OPTIMIZE TABLE news, slides, gallery, posters;https://deine-domain.de/admin oder
http://localhost:90/adminStandard-Passwort ändern:
-- In MySQL
UPDATE site_settings SET admin_password = 'dein_neues_passwort' WHERE id = 1;Nach dem Login siehst du das Dashboard mit folgenden Bereichen:
┌─────────────────────────────────────────┐
│ Dashboard │
├─────────────────────────────────────────┤
│ □ Dashboard │
│ □ Website-Einstellungen │
│ □ Slideshow │
│ □ News │
│ □ Galerie │
│ □ Plakate │
│ □ Plakat-Bilder │
│ □ Stimmarten │
│ □ YoungStars │
│ □ Vereine │
│ □ Kontaktformular │
│ □ Custom Sections │
│ □ Schriftarten │
│ □ Über uns │
│ □ Kontakt │
│ □ Impressum │
│ □ Datenschutz │
└─────────────────────────────────────────┘
Allgemein: - Seitentitel: Browser-Tab-Titel (z.B. “StimmArt - Pop-Chor”) - Seitenbeschreibung: Meta-Description für Suchmaschinen
Header-Logo: 1. Klicke auf “Bild hochladen” 2. Wähle dein Logo (empfohlen: PNG mit transparentem Hintergrund, Höhe ca. 60px) 3. Logo wird automatisch im Header angezeigt
YouTube-Video: 1. Gib die YouTube-URL ein:
https://www.youtube.com/watch?v=VIDEO_ID 2. Das Video
erscheint in der “Das sind wir!”-Sektion
Farben & Styling: - Primärfarbe, Sekundärfarbe, Akzentfarbe - Dark Mode automatisch angepasst
Bilder verwalten: 1. Klicke auf “Bilder hochladen” 2. Wähle ein oder mehrere Bilder (JPG, PNG) 3. Optional: Bildunterschrift hinzufügen 4. Reihenfolge ändern: Ziehe Bilder per Drag & Drop 5. Löschen: Klicke auf das Papierkorb-Icon
Tipps: - Empfohlene Größe: 1920x1080px (Full HD) - Format: JPG für Fotos, PNG für Grafiken - Dateigröße: Max. 5 MB pro Bild
Artikel erstellen: 1. Klicke auf “Neuer Artikel” 2. Titel: Überschrift des Artikels 3. Kategorie: z.B. “Konzert”, “Probe”, “Allgemein” 4. Datum: Veröffentlichungsdatum 5. Kurzbeschreibung: Teaser-Text (2-3 Sätze) 6. Inhalt: Vollständiger Artikel-Text 7. Bild hochladen: Optional 8. Klicke auf “Speichern”
Artikel bearbeiten: - Klicke in der Liste auf den Stift-Button - Ändere die Felder - Speichern
Artikel löschen: - Klicke auf das Papierkorb-Icon - Bestätige die Löschung
Bilder hochladen: 1. Klicke auf “Bilder hochladen” 2. Mehrfach-Auswahl möglich! 3. Wähle eine Kategorie (z.B. “Konzert 2024”, “Proben”, “Ausflug”) 4. Optional: Bildunterschrift pro Bild 5. Hochladen
Kategorien verwenden: - Kategorien helfen bei der Organisation - Besucher können nach Kategorie filtern - Kategorien werden automatisch erstellt
Plakat-Event erstellen: 1. Klicke auf “Neues Plakat” 2. Titel: Name des Events (z.B. “Frühjahrskonzert 2025”) 3. Beschreibung: Details zum Event 4. Datum: Event-Datum 5. Speichern
Bilder zum Plakat hinzufügen: 1. Gehe zu Plakat-Bilder 2. Wähle das Event aus dem Dropdown 3. Lade ein oder mehrere Plakat-Bilder hoch 4. Reihenfolge per Drag & Drop anpassen
Für jede Stimme (Sopran, Alt, Tenor, Bass):
Aktivierung: 1. Toggle “YoungStars-Sektion aktivieren” auf AN 2. Die Sektion erscheint auf der Website
Konfiguration: 1. Titel: z.B. “StimmArt YoungStars” 2. Logo hochladen: Logo für den Jugendchor (optional) 3. Beschreibung: Text über den Jugendchor 4. Galerie: Bilder hochladen (Mehrfachauswahl möglich) 5. Speichern
Deaktivierung: - Toggle ausschalten → Sektion verschwindet von der Website - Daten bleiben gespeichert!
Tab 1: Aktivierung - Toggle “Kontaktformular aktivieren” auf AN
Tab 2: Inhalte Hier kannst du ALLE Texte anpassen: - Sektions-Titel (z.B. “Kontaktieren Sie uns!”) - Beschreibung - Feld-Labels (Name, E-Mail, Telefon, Nachricht) - Button-Text (“Absenden”) - Erfolgs- und Fehlermeldungen - Datenschutz-Checkbox-Text
Tab 3: Styling - Hintergrundfarbe (transparent = durchsichtig) - Textfarbe (inherit = erbt von Theme) - Button-Farben - Border-Farbe
Tipp: Lass Farben auf “transparent” oder “inherit” für automatische Dark/Light Mode-Anpassung!
Tab 4: Anfragen Hier siehst du alle eingegangenen Anfragen: - Anfrage-ID: Eindeutige Nummer (#00001, #00002, …) - Name, E-Mail, Telefon - Nachricht - Datum/Uhrzeit - E-Mail versendet? Ja/Nein
Du kannst Anfragen durchsuchen und exportieren.
Neue Sektion erstellen: 1. Klicke auf “Neue Sektion” 2. Titel: Überschrift der Sektion 3. Inhalt: Rich-Text mit Formatierung - Fett, Kursiv, Unterstrichen - Listen (nummeriert, Bullet-Points) - Links - Bilder einfügen 4. Styling: - Hintergrundfarbe - Textfarbe - Schriftart (aus hochgeladenen Fonts wählen) 5. Reihenfolge: Ziffer eingeben (niedrigere Zahl = weiter oben) 6. Aktiv/Inaktiv: Toggle zum Ein-/Ausblenden 7. Speichern
Sektion bearbeiten: - Klicke auf “Bearbeiten” - Ändere Felder - Speichern
Reihenfolge ändern: - Per Drag & Drop - Oder Zahl in “Display Order” ändern
Font hochladen: 1. Klicke auf “Schriftart hochladen” 2. Name: Display-Name (z.B. “Cavolini”) 3. Familie: Font-Family-Name (z.B. “Cavolini”) 4. Dateien auswählen: - TTF (TrueType) - OTF (OpenType) - WOFF (Web Open Font Format) - WOFF2 (beste Browser-Unterstützung!) 5. Mehrere Formate hochladen (empfohlen für beste Kompatibilität) 6. Hochladen
Font-Vorschau: - Automatische Vorschau mit Demo-Text - Teste verschiedene Größen
Font verwenden: - In Custom Sections: Dropdown-Auswahl - In Website-Einstellungen für globale Nutzung
Font löschen: - Klicke auf Papierkorb - Achtung: Wird auch aus allen Sektionen entfernt!
Diese Bereiche haben ein einheitliches Interface:
Impressum & Datenschutz: - Rechtliche
Pflichtangaben - Automatisch verlinkt im Footer - Eigene Seiten
(/imprint, /privacy)
Automatische Erkennung: - System-Präferenz wird
erkannt (prefers-color-scheme) - Beim ersten Besuch:
System-Einstellung übernommen
Manueller Toggle: - Button in der Navigation - Wahl
wird in localStorage gespeichert - Bleibt über
Seitenaufrufe hinweg erhalten
Für Entwickler:
// ThemeContext verwenden
import { useTheme } from '@/contexts/ThemeContext';
function MyComponent() {
const { theme, toggleTheme } = useTheme();
return (
<div className={theme === 'dark' ? 'bg-gray-900' : 'bg-white'}>
<button onClick={toggleTheme}>Toggle</button>
</div>
);
}Workflow: 1. Designer erstellt Font 2. Admin lädt
Font hoch (.woff2 empfohlen) 3. System generiert @font-face
CSS 4. Font ist in Custom Sections verfügbar
Technische Details: - Fonts werden in
/public/fonts/[font-id]/ gespeichert - Automatische
CSS-Generierung in DynamicFontLoader.tsx - Dynamisches
Nachladen beim Seitenwechsel
Font-Format-Priorität: 1. WOFF2 (beste Kompression, moderne Browser) 2. WOFF (gute Kompatibilität) 3. TTF/OTF (Fallback für alte Browser)
Ablauf: 1. Besucher füllt Formular aus 2. Frontend
sendet Daten an /api/contact-form 3. Backend: - Validiert
Eingaben - Speichert in Datenbank mit eindeutiger ID - Versucht E-Mail
zu versenden 4. Erfolg- oder Fehlermeldung
E-Mail-Vorlage:
Betreff: Neue Kontaktanfrage von [Name]
Neue Kontaktanfrage über das Website-Formular:
Anfrage-ID: #00042
Name: Max Mustermann
E-Mail: max@example.com
Telefon: 0123/456789
Nachricht:
Hallo, ich interessiere mich für...
---
Diese E-Mail wurde automatisch über das Kontaktformular auf stimmart-chor.de gesendet.
Fehlerbehandlung: - SMTP-Fehler → Anfrage wird trotzdem gespeichert - Admin kann Anfragen im Panel einsehen - Log-Eintrag im Server-Log
Meta-Tags:
<head>
<title>StimmArt - Pop-Chor Singgemeinschaft Bieber/Wiesen</title>
<meta name="description" content="...">
<meta name="keywords" content="Pop-Chor, StimmArt, ...">
<!-- Open Graph (Facebook, WhatsApp) -->
<meta property="og:title" content="StimmArt">
<meta property="og:description" content="...">
<meta property="og:image" content="...">
<meta property="og:url" content="https://stimmart-chor.de">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<!-- Canonical URL -->
<link rel="canonical" href="https://stimmart-chor.de">
</head>Strukturierte Daten:
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "StimmArt",
"description": "Pop-Chor...",
"url": "https://stimmart-chor.de"
}Best Practices: - Sprechende URLs - Alt-Texte für
Bilder - Semantisches HTML (<header>,
<nav>, <main>,
<footer>) - Performance-Optimierung (Lighthouse Score
> 90)
Verwendung: - Slideshow: Bilder sortieren - Plakat-Bilder: Reihenfolge festlegen - Custom Sections: Sortierung
Technologie: - @dnd-kit/core -
@dnd-kit/sortable - Touch-Support für Mobile
Code-Beispiel:
import { DndContext, closestCenter } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
{items.map(item => <SortableItem key={item.id} item={item} />)}
</SortableContext>
</DndContext>
http://localhost:3001/apihttps://deine-domain.de/apiGET /api/news
// Alle News abrufen
fetch('/api/news')
.then(res => res.json())
.then(data => console.log(data));
// Response
[
{
"id": "uuid-here",
"title": "Konzert am 15. März",
"content": "...",
"excerpt": "...",
"date": "2025-03-15",
"image_url": "/uploads/general/concert.jpg",
"category": "Konzert",
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-10T12:00:00Z"
}
]GET /api/news/:id
// Einzelnen Artikel abrufen
fetch('/api/news/uuid-here')POST /api/news
// Neuen Artikel erstellen (Admin)
fetch('/api/news', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: "Neuer Artikel",
content: "...",
date: "2025-03-15",
category: "Konzert"
})
})PUT /api/news/:id
// Artikel aktualisieren
fetch('/api/news/uuid-here', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: "Geänderter Titel" })
})DELETE /api/news/:id
// Artikel löschen
fetch('/api/news/uuid-here', { method: 'DELETE' })GET /api/slides POST /api/slides DELETE /api/slides/:id PUT /api/slides/reorder - Reihenfolge ändern
GET /api/gallery POST /api/gallery DELETE /api/gallery/:id
GET /api/settings
// Website-Einstellungen abrufen
fetch('/api/settings')
.then(res => res.json())
.then(settings => {
console.log(settings.site_title);
console.log(settings.header_logo_url);
console.log(settings.about_youtube_url);
});PUT /api/settings
GET /api/contact-form/settings PUT /api/contact-form/settings
POST /api/contact-form
// Kontaktanfrage senden
fetch('/api/contact-form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: "Max Mustermann",
email: "max@example.com",
phone: "0123/456789",
message: "Hallo, ich interessiere mich..."
})
})GET /api/contact-form/requests
// Alle Anfragen abrufen (Admin)
fetch('/api/contact-form/requests')
.then(res => res.json())
.then(requests => {
requests.forEach(req => {
console.log(req.request_number); // #00001
console.log(req.name);
console.log(req.email);
console.log(req.message);
console.log(req.email_sent); // true/false
});
});POST /api/upload
// Bild hochladen
const formData = new FormData();
formData.append('image', fileInput.files[0]);
formData.append('category', 'general');
fetch('/api/upload', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
console.log('Bild-URL:', data.url);
// z.B. "/uploads/general/1760445086693-7aad5049.png"
});GET /api/fonts POST /api/fonts/upload DELETE /api/fonts/:id
GET /health
// Server-Status prüfen
fetch('/health')
.then(res => res.json())
.then(data => {
console.log(data.status); // "healthy"
console.log(data.database); // "connected"
console.log(data.timestamp);
});GET /api/db-status
// Datenbankstatus
fetch('/api/db-status')
.then(res => res.json())
.then(data => {
console.log(data.connected); // true/false
console.log(data.tables); // Liste aller Tabellen
});Problem:
npm ERR! code EACCES
npm ERR! syscall access
Lösung:
# Berechtigungen reparieren
sudo chown -R $(whoami) ~/.npm
sudo chown -R $(whoami) node_modules
# Oder mit sudo (nicht empfohlen)
sudo npm installProblem:
sh: 1: vite: Permission denied
Lösung:
# Ausführungsrechte setzen
chmod +x node_modules/.bin/vite
# Oder node_modules neu installieren
rm -rf node_modules package-lock.json
npm installProblem:
Error: connect ECONNREFUSED 127.0.0.1:3306
Lösungen:
a) MySQL läuft nicht:
# MySQL starten
sudo systemctl start mysql
# Status prüfen
sudo systemctl status mysqlb) Falsche Zugangsdaten:
# mysql.conf überprüfen
cat mysql.conf
# Zugangsdaten testen
mysql -u USERNAME -p DATABASEc) MySQL hört nur auf localhost:
# In /etc/mysql/mariadb.conf.d/50-server.cnf
bind-address = 127.0.0.1
# MySQL neustarten
sudo systemctl restart mysqlProblem:
Error: listen EADDRINUSE: address already in use :::3001
Lösung:
# Prozess finden
lsof -i :3001
# Prozess beenden
kill -9 <PID>
# Oder anderen Port in src/server/index-mysql.ts verwenden
const PORT = process.env.PORT || 3002;Problem: Bilder werden nicht hochgeladen oder angezeigt.
Lösungen:
a) Berechtigungen:
# Upload-Ordner beschreibbar machen
sudo chmod -R 775 /var/www/Stimmart/project/public/uploads/
sudo chown -R www-data:www-data /var/www/Stimmart/project/public/uploads/b) Apache-Konfiguration:
# In /etc/apache2/sites-available/stimmart.conf
Alias /uploads /var/www/Stimmart/project/public/uploads
<Directory /var/www/Stimmart/project/public/uploads>
Options -Indexes
Require all granted
</Directory>c) Maximale Upload-Größe:
# In php.ini (falls PHP verwendet wird)
upload_max_filesize = 20M
post_max_size = 20MProblem: Kontaktformular-E-Mails werden nicht versendet.
Lösungen:
a) SMTP-Konfiguration prüfen:
cat smtp.confb) Firewall-Regel für Port 587:
# Port 587 testen
telnet smtp.gmail.com 587
# Firewall öffnen (Ubuntu)
sudo ufw allow 587/tcpc) Gmail App-Passwort: - Normale Gmail-Passwörter funktionieren nicht - Erstelle ein App-Passwort: 1. Google-Konto → Sicherheit 2. 2-Faktor-Authentifizierung aktivieren 3. App-Passwörter generieren 4. Passwort in smtp.conf eintragen
d) Server-Logs prüfen:
# API-Logs ansehen
sudo journalctl -u stimmart-api.service -f
# Nach SMTP-Fehlern suchen
sudo journalctl -u stimmart-api.service | grep SMTPProblem: Direkter Aufruf von /admin
oder /imprint gibt 404.
Lösung: Apache Rewrite-Rules
# In /etc/apache2/sites-available/stimmart.conf
<Directory /var/www/Stimmart/dist>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</Directory># mod_rewrite aktivieren
sudo a2enmod rewrite
sudo systemctl reload apache2Problem: Theme wechselt nicht oder bleibt hängen.
Lösungen:
a) localStorage leeren:
// In Browser-Konsole
localStorage.removeItem('theme');
location.reload();b) ThemeContext prüfen:
# Stelle sicher, dass App.tsx ThemeProvider verwendet
grep -n "ThemeProvider" src/App.tsxProblem: Custom Fonts erscheinen nicht auf der Website.
Lösungen:
a) Font-Dateien überprüfen:
# Existieren die Font-Dateien?
ls -la /var/www/Stimmart/project/public/fonts/
# Berechtigungen
sudo chmod -R 755 /var/www/Stimmart/project/public/fonts/b) Browser-Cache leeren: - Strg+Shift+R (Hard Reload) - Browser-DevTools → Network → “Disable cache”
c) @font-face CSS prüfen:
// In Browser-Konsole
console.log(document.styleSheets);Problem: Website lädt langsam.
Lösungen:
a) Bildgrößen reduzieren:
# ImageMagick installieren
sudo apt install imagemagick
# Bilder optimieren
mogrify -resize 1920x1080 -quality 85 *.jpgb) Browser-Caching aktivieren:
# In /etc/apache2/sites-available/stimmart.conf
<Directory /var/www/Stimmart/dist>
# Browser-Caching
<FilesMatch "\.(jpg|jpeg|png|gif|webp|css|js|woff|woff2)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>
</Directory>c) Gzip-Kompression:
# mod_deflate aktivieren
sudo a2enmod deflate
# In Apache-Config
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
</IfModule># Auf lokalem Rechner
git pull origin main
npm install
npm run build:all
# ZIP erstellen und auf Server hochladen
# Dann auf Server:
cd /var/www/Stimmart
sudo systemctl stop stimmart-api.service
cp -r /home/adminuser/upload/project/ .
cd project
npm install
npm run build:all
sudo systemctl start stimmart-api.service# Neue Migration ausführen
mysql -u root -p website < mysql-migrations/NEW_MIGRATION.sql
# Service neustarten
sudo systemctl restart stimmart-api.service# Veraltete Pakete anzeigen
npm outdated
# Alle Minor/Patch-Updates
npm update
# Major-Update (vorsichtig!)
npm install package-name@latest
# Testen!
npm run dev
npm test# Systemd-Service-Status
sudo systemctl status stimmart-api.service
sudo systemctl status apache2
sudo systemctl status mysql
# Ressourcen-Nutzung
htop
df -h # Speicherplatz
free -h # RAM
# Logs in Echtzeit
sudo journalctl -u stimmart-api.service -f
sudo tail -f /var/log/apache2/stimmart_error.logErstelle ein Cronjob:
sudo crontab -eFüge hinzu:
# Jede 5 Minuten: Health-Check
*/5 * * * * curl -s http://localhost:3001/health > /dev/null || systemctl restart stimmart-api.service
# Täglich um 2 Uhr: Datenbank-Backup
0 2 * * * mysqldump -u root -pPASSWORD website > /var/backups/website_$(date +\%Y\%m\%d).sql
Erstelle /usr/local/bin/backup-stimmart.sh:
#!/bin/bash
BACKUP_DIR="/var/backups/stimmart"
DATE=$(date +%Y%m%d_%H%M%S)
# Verzeichnis erstellen
mkdir -p $BACKUP_DIR
# Datenbank-Backup
mysqldump -u root -p$(cat /root/.mysql_password) website > $BACKUP_DIR/db_$DATE.sql
# Dateien-Backup
tar -czf $BACKUP_DIR/files_$DATE.tar.gz /var/www/Stimmart/project/public/uploads /var/www/Stimmart/project/public/fonts
# Alte Backups löschen (älter als 30 Tage)
find $BACKUP_DIR -type f -mtime +30 -delete
echo "Backup abgeschlossen: $DATE"Ausführbar machen und testen:
sudo chmod +x /usr/local/bin/backup-stimmart.sh
sudo /usr/local/bin/backup-stimmart.shCronjob hinzufügen:
# Täglich um 2:30 Uhr
30 2 * * * /usr/local/bin/backup-stimmart.sh >> /var/log/backup.log 2>&1
# API-Server-Logs
sudo journalctl -u stimmart-api.service -n 100
# Apache-Error-Log
sudo tail -n 100 /var/log/apache2/stimmart_error.log
# MySQL-Logs
sudo tail -n 100 /var/log/mysql/error.log
# Node.js im Debug-Modus
NODE_ENV=development DEBUG=* npm start# Projekt-Info
npm list --depth=0 # Installierte Pakete
npm run # Verfügbare Scripts
node -v # Node.js-Version
mysql --version # MySQL-Version
# Git-Status
git status
git log --oneline -10
# Systeminfo
uname -a # Linux-Version
lsb_release -a # Ubuntu-Version
systemctl list-units --type=service --state=running # Laufende ServicesEntwickelt für: StimmArt - Pop-Chor Singgemeinschaft
Bieber/Wiesen
Original: Liederkranz Bieber 1847 e.V.
Rebranding: Januar 2025
🎉 Herzlichen Glückwunsch! Deine StimmArt-Website läuft!
Bei Fragen oder Problemen: Sieh dir das Troubleshooting-Kapitel an oder erstelle ein Issue im Repository.