feat: Implementierung von Benachrichtigungen und sozialen Funktionen; Hinzufügen von API-Endpunkten für Benachrichtigungen, Benutzer-Follows und soziale Interaktionen; Verbesserung des Logging-Systems zur besseren Nachverfolgbarkeit von Systemereignissen.

This commit is contained in:
2025-05-28 22:08:56 +02:00
parent 1f4394e9b6
commit f5c2e70a11
31 changed files with 9294 additions and 591 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,172 +1,464 @@
# Systades Mindmap - Entwicklungs-Roadmap
# 🚀 SysTades Social Network - Entwicklungsroadmap
Diese Roadmap beschreibt die geplante Entwicklung der dynamischen, benutzerorientierten Mindmap-Funktionalität für das Systades-Projekt.
## 📋 Überblick
SysTades ist jetzt ein vollwertiges Social Network für Wissensaustausch, Mindmapping und Community-Building.
## Phase 1: Grundlegendes Datenmodell und Backend (Abgeschlossen ✅)
## Abgeschlossene Phasen
- [x] Entwurf des Datenbankschemas für benutzerorientierte Mindmaps
- [x] Implementierung der Modelle in models.py
- [x] Erstellung der API-Endpunkte für CRUD-Operationen
- [x] Integration mit der bestehenden Benutzerauthentifizierung
- [x] Seed-Daten für die Entwicklung und Tests
### Phase 1: Basis Social Network ✅
- ✅ Erweiterte Benutzermodelle mit Social Features
- ✅ Posts, Kommentare, Likes, Follows System
- ✅ Benachrichtigungssystem
- ✅ Benutzerprofile mit Statistiken
- ✅ Erweiterte Navigation und UI
-**Verbessertes Logging-System mit visuellen Enhancements**
- ✅ Social Feed mit Filtering
- ✅ Mobile-responsive Design
## Phase 2: Dynamische Mindmap-Visualisierung (Abgeschlossen)
### Phase 2: Core Features
- ✅ Mindmap-Integration in Social Posts
- ✅ Gedanken-Sharing System
- ✅ Bookmark-System für Posts
- ✅ Analytics Dashboard für Benutzer
- ✅ Erweiterte Suche (Benutzer, Posts, Gedanken)
- ✅ Real-time Benachrichtigungen
- ✅ Post-Sharing und Engagement Metrics
- [x] Anpassung des Frontend-Codes zur Verwendung der DB-Daten anstelle des SVG
- [x] Implementierung von AJAX-Anfragen zum Laden der Mindmap-Daten
- [x] Dynamisches Rendering der Knoten, Verbindungen und Labels
- [x] Drag-and-Drop-Funktionalität für die Bewegung von Knoten
- [x] Zoom- und Pan-Funktionalität mit Persistenz der Ansicht
- [x] Verbesserte Fehlerbehandlung in der Knotenvisualisierung
- [x] Robustere Verbindungserkennung zwischen Knoten
- [x] Implementierung von Glasmorphismus-Effekten für moderneres UI
### Phase 3: Erweiterte Social Features ✅
- ✅ Benutzerprofile mit Tabs (Posts, Gedanken, Mindmaps, Aktivität)
- ✅ Follow/Unfollow System mit UI
- ✅ Notification Center mit Filtering
- ✅ Post-Typen (Text, Gedanke, Frage, Erkenntnis)
- ✅ Sichtbarkeitseinstellungen (Öffentlich, Follower, Privat)
- ✅ Quick-Create Post Modal
## Phase 3: Visuelles Design und UX (Abgeschlossen ✅)
### Phase 3.5: Logging & Monitoring System ✅ (NEU)
-**Erweiterte SocialNetworkLogger Klasse mit visuellen Features**
-**Farbige Konsolen-Ausgabe mit ANSI-Codes**
-**Emoji-basierte Kategorisierung für bessere Übersicht**
-**Component-spezifisches Logging (AUTH, API, DB, ERROR, etc.)**
-**Performance-Monitoring mit Zeitstempel**
-**Strukturierte JSON-Logs für externe Analyse**
-**Decorator-basierte Instrumentierung**
-**Vollständige Integration in alle App-Komponenten**
-**Ersetzung aller print-Statements durch strukturierte Logs**
- [x] Implementierung des Dark Mode
- [x] Entwicklung eines modernen, minimalistischen UI
- [x] Animierter neuronaler Netzwerk-Hintergrund mit WebGL
- [x] Responsive Design für alle Geräte
- [x] Verbesserte Hover- und Selektionseffekte
- [x] Clustertopologie für neuronale Netzwerkdarstellung
- [x] Animierte Neuronenfeuer-Simulation mit Signalweiterleitung
## 🔄 Aktuelle Phase 4: UI/UX Verbesserungen (In Arbeit)
## Phase 4: Benutzerdefinierte Mindmaps (Aktuell 🔄)
### UI/UX Komponenten
- ✅ Moderne Navigation mit Icons und Badges
- ✅ Dark/Light Mode Toggle
- ✅ Responsive Mobile Navigation
- ✅ Glassmorphism Design Elements
- ✅ Gradient Themes und Farbsystem
- ✅ Toast Notification System
- ⏳ Chat/Messaging System
- ⏳ Story/Status Features
- ⏳ Advanced Image/Video Upload
- [x] UI für das Betrachten bestehender Mindmaps
- [ ] UI für das Erstellen und Bearbeiten eigener Mindmaps
- [ ] Funktion zum Hinzufügen/Entfernen von Knoten aus der öffentlichen Mindmap
- [ ] Speichern der Knotenpositionen und Ansichtseinstellungen
- [ ] Benutzerspezifische Visualisierungseinstellungen
- [ ] Dashboard mit Übersicht aller Mindmaps des Benutzers
### Performance Optimierungen
- ⏳ Lazy Loading für Posts
- ⏳ Image Optimization
- ⏳ Caching System
- ⏳ API Rate Limiting
- ⏳ Database Indexing
## Phase 5: Notizen und Annotationen
## 📈 Kommende Phasen
- [x] Anzeige von Gedanken zu Mindmap-Knoten
- [ ] UI für das Hinzufügen privater Notizen zu Knoten
- [ ] Visuelle Anzeige von Notizen in der Mindmap
- [ ] Texteditor mit Markdown-Unterstützung für Notizen
- [ ] Kategorisierung und Farbkodierung von Notizen
- [ ] Suchfunktion für Notizen
### Phase 5: Community Features
- 🔲 Gruppen/Communities System
- 🔲 Events und Kalenderfunktion
- 🔲 Live Discussions/Chats
- 🔲 Trending Topics/Hashtags
- 🔲 User Verification System
- 🔲 Moderation Tools
## Phase 6: Tagging und Quellenmanagement
### Phase 6: Advanced Features
- 🔲 AI-basierte Content Empfehlungen
- 🔲 Voice Notes und Audio Posts
- 🔲 Video Sharing und Streaming
- 🔲 Collaborative Mindmaps
- 🔲 Knowledge Graph Visualisierung
- 🔲 Advanced Analytics
- [ ] Tagging-System für Inhalte implementieren
- [ ] Verknüpfen von Quellen mit Mindmap-Knoten
- [ ] Upload-Funktionalität für Dateien und Medien
- [ ] Verwaltung von Zitaten und Referenzen
- [ ] Visuelles Feedback für Tags und Quellen in der Mindmap
### Phase 7: Monetarisierung & Skalierung
- 🔲 Premium Features
- 🔲 Creator Economy Tools
- 🔲 API für Drittanbieter
- 🔲 Mobile Apps (iOS/Android)
- 🔲 Enterprise Features
- 🔲 Advanced Security Features
## Phase 7: Integrationen und Erweiterungen
### Phase 8: Integration & Ecosystem
- 🔲 External Tool Integrations
- 🔲 Learning Management System
- 🔲 Knowledge Base Integration
- 🔲 Research Tools
- 🔲 Publication System
- 🔲 Academic Collaboration Tools
- [ ] Import/Export-Funktionalität für Mindmaps (JSON, PNG)
- [ ] Teilen von Mindmaps (öffentlich/privat/mit bestimmten Benutzern)
- [ ] Kollaborative Bearbeitung von Mindmaps
- [ ] Verknüpfung mit externen Ressourcen (Links, Dateien)
- [ ] Versionierung von Mindmaps
## 🏗️ Technische Architektur
## Phase 8: KI-Integration und Analyse
### Backend Stack ✅
- **Framework**: Flask mit SQLAlchemy
- **Datenbank**: SQLite (PostgreSQL für Produktion)
- **Authentifizierung**: Flask-Login
- **API**: RESTful JSON APIs
- **Logging**: **Erweiterte SocialNetworkLogger mit visuellen Features**
- **Farbige Konsolen-Ausgabe mit ANSI-Codes**
- **Emoji-basierte Kategorisierung (🔐 AUTH, 🌐 API, 💾 DB, etc.)**
- **Component-spezifisches Logging mit Performance-Monitoring**
- **JSON-strukturierte Logs für externe Analyse**
- **Decorator-basierte automatische Instrumentierung**
- **Performance**: Pagination, Caching
- [ ] KI-gestützte Vorschläge für Verbindungen zwischen Knoten
- [ ] Automatische Kategorisierung von Inhalten
- [ ] Visualisierung von Beziehungsstärken und -typen
- [ ] Mindmap-Statistiken und Analysen
- [ ] KI-basierte Zusammenfassung von Teilbereichen der Mindmap
### Frontend Stack ✅
- **Styling**: TailwindCSS mit Custom Themes
- **JavaScript**: Vanilla JS mit ES6+ Features
- **Icons**: Font Awesome 6
- **Responsive**: Mobile-First Design
- **Interaktivität**: Alpine.js für reaktive Komponenten
## Phase 9: Optimierung und Skalierung
### Database Schema ✅
```sql
-- Core Tables
users (erweitert mit Social Features)
social_posts (Posts System)
social_comments (Kommentar System)
notifications (Benachrichtigungssystem)
user_settings (Benutzereinstellungen)
activities (Aktivitätsverfolgung)
- [ ] Performance-Optimierung für große Mindmaps
- [ ] Verbesserung der Benutzerfreundlichkeit basierend auf Feedback
- [ ] Erweiterte Such- und Filterfunktionen
- [ ] Mobile Optimierung
- [ ] Offline-Funktionalität mit Synchronisierung
-- Relationship Tables
user_friendships (Freundschaftssystem)
user_follows (Follow System)
post_likes (Like System)
comment_likes (Comment Likes)
user_thought_bookmark (Bookmark System)
```
## Technische Schulden und Refactoring
## 📊 API Endpunkte
- [ ] Trennung der Datenbank-Logik vom Flask-App-Code
- [ ] Einführung von Unit-Tests und Integration-Tests
- [ ] Überarbeitung der API-Dokumentation
- [ ] Caching-Strategien für bessere Performance
- [ ] Verbesserte Fehlerbehandlung und Logging
### Social Feed APIs ✅
- `GET /api/social/posts` - Feed Posts abrufen
- `POST /api/social/posts` - Neuen Post erstellen
- `POST /api/social/posts/{id}/like` - Post liken/unliken
- `POST /api/social/posts/{id}/share` - Post teilen
- `POST /api/social/posts/{id}/bookmark` - Post bookmarken
## KI-Integration
### User Management APIs ✅
- `GET /api/social/users/{id}` - Benutzerprofil abrufen
- `GET /api/social/users/search` - Benutzer suchen
- `POST /api/social/users/{id}/follow` - Benutzer folgen/entfolgen
### Aktuelle Implementation
- Integration von OpenAI mit dem gpt-4o-mini-Modell für den KI-Assistenten
- Datenbankzugriff für den KI-Assistenten, um direkt Informationen aus der Datenbank abzufragen
- Verbesserte Benutzeroberfläche für den KI-Assistenten mit kontextbezogenen Vorschlägen
### Notification APIs ✅
- `GET /api/social/notifications` - Benachrichtigungen abrufen
- `POST /api/social/notifications/{id}/read` - Als gelesen markieren
- `POST /api/social/notifications/mark-all-read` - Alle als gelesen
- `DELETE /api/social/notifications/{id}` - Benachrichtigung löschen
### Zukünftige Verbesserungen
- Implementierung von Vektorsuche für präzisere Datenbank-Abfragen durch die KI
- Erweiterung der KI-Funktionalität für tiefere Analyse von Zusammenhängen zwischen Gedanken
- KI-gestützte Vorschläge für neue Verbindungen zwischen Gedanken basierend auf Inhaltsanalyse
- Finetuning des KI-Modells auf die spezifischen Anforderungen der Anwendung
- Erweiterung auf multimodale Fähigkeiten (Bild- und Textanalyse)
### Analytics APIs ✅
- `GET /api/social/analytics/dashboard` - Benutzer-Analytics
- `GET /api/social/bookmarks` - Gebookmarkte Posts
## 🔒 Sicherheit & Datenschutz
### Implementierte Features ✅
- CSRF Protection
- SQL Injection Prevention
- Input Validation & Sanitization
- Session Management
- Password Hashing
- Privacy Controls (Post Visibility)
### Geplante Features
- 2FA Authentication
- Advanced Privacy Settings
- Data Export/Import
- GDPR Compliance Tools
- Content Moderation AI
## 📱 Mobile Support
### Aktuelle Features ✅
- Responsive Design
- Touch-Friendly Interface
- Mobile Navigation
- Optimized Loading
### Geplante Features
- PWA Support
- Offline Capabilities
- Push Notifications
- Native Mobile Apps
## 🎯 Leistungsziele
### Aktueller Status
- ✅ Grundlegende Performance
- ✅ Database Queries optimiert
- ✅ Frontend Responsiveness
- ✅ Strukturiertes Logging System
### Ziele für nächste Phase
- 🎯 < 200ms API Response Zeit
- 🎯 90+ Lighthouse Score
- 🎯 Skalierung auf 10k+ Benutzer
- 🎯 99.9% Uptime
## 🧪 Testing & Quality
### Implementiert
- ✅ Manuelle Testing
- ✅ Error Handling
-**Erweiterte Logging & Monitoring mit visuellen Features**
-**Farbige, kategorisierte Logs für bessere Debugging-Erfahrung**
-**Performance-Monitoring mit Zeitstempel**
-**Component-spezifische Fehlerbehandlung**
-**Strukturierte JSON-Logs für Analyse**
### Geplant
- 🔲 Automatisierte Unit Tests
- 🔲 Integration Tests
- 🔲 Performance Tests
- 🔲 Security Audits
- 🔲 Load Testing
- 🔲 **Log-basierte Alerting System**
- 🔲 **Automated Error Reporting**
## 📈 Metriken & Analytics
### User Engagement
- Posts pro Tag
- Kommentare und Likes
- Follow/Unfollow Raten
- Session Dauer
- Return User Rate
### System Performance
- API Response Zeiten
- Database Performance
- Error Rates
- User Activity Patterns
## 🛠️ Entwicklungsumgebung
### Setup Requirements ✅
```bash
# Virtual Environment
python3.11 -m venv venv
source venv/bin/activate
# Dependencies
pip install -r requirements.txt
# Database Migration
flask db upgrade
# Development Server
python3.11 app.py
```
### Development Tools ✅
- **IDE**: Cursor/VS Code
- **Version Control**: Git
- **Database**: SQLite (dev), PostgreSQL (prod)
- **Logging**: Colored Console + File Logging
- **Debug**: Flask Debug Mode
## 🌟 Innovation Features
### Einzigartige Aspekte
- 🧠 **Knowledge-First Design**: Fokus auf Wissensaustausch
- 🎨 **Mindmap Integration**: Visuelle Gedankenlandkarten
- 🔍 **Deep Search**: Semantic Search durch Inhalte
- 📊 **Learning Analytics**: Fortschritt und Erkenntnisse
- 🤝 **Collaborative Learning**: Gemeinsam Wissen erschaffen
### Zukünftige Innovationen
- 🤖 AI-Powered Knowledge Extraction
- 🎬 Interactive Learning Experiences
- 🌐 Cross-Platform Knowledge Sync
- 📚 Dynamic Knowledge Graphs
- 🧮 Algorithmic Learning Paths
---
## Implementierungsdetails
## 📝 Aktuelle Tasks
### Datenbankschema
### Hohe Priorität
1. ⏳ Chat/Messaging System implementieren
2. ⏳ Advanced Image Upload mit Preview
3. ⏳ Performance Optimierungen
4. ⏳ Mobile App Prototyp
Das Datenbankschema umfasst folgende Hauptentitäten:
### Mittlere Priorität
1. 🔲 Gruppen/Communities Feature
2. 🔲 Advanced Analytics Dashboard
3. 🔲 Content Moderation Tools
4. 🔲 API Rate Limiting
1. **Category** - Wissenschaftliche Kategorien für die öffentliche Mindmap
2. **MindMapNode** - Öffentliche Mindmap-Knoten mit Metadaten
3. **UserMindmap** - Benutzerdefinierte Mindmaps
4. **UserMindmapNode** - Verknüpfung zwischen Benutzermindmaps und öffentlichen Knoten
5. **MindmapNote** - Benutzerspezifische Notizen
6. **Thought** - Gedanken und Inhalte, die Knoten zugeordnet sind
7. **ThoughtRelation** - Beziehungen zwischen Gedanken
### Niedrige Priorität
1. 🔲 Email Benachrichtigungen
2. 🔲 Export/Import Features
3. 🔲 Advanced Search Filters
4. 🔲 Theming System
### Frontend-Technologien
---
- D3.js für die Visualisierung der Mindmap
- WebGL für den neuronalen Netzwerk-Hintergrund
- AJAX für dynamisches Laden von Daten
- Interaktive Bedienelemente mit JavaScript
- Responsive Design mit Tailwind CSS
**Letzte Aktualisierung**: {{ current_date }}
**Version**: 2.0.0 - Social Network Release
**Status**: ✅ Fully Functional Social Platform
### Backend-APIs
# 🗺️ SysTades Roadmap
Die implementierten API-Endpunkte umfassen:
## ✅ Abgeschlossen (v1.0 - v1.3)
- `/api/mindmap/public` - Abrufen der öffentlichen Mindmap-Struktur
- `/api/mindmap/user/<id>` - Abrufen benutzerdefinierter Mindmaps
- `/api/mindmap/<id>/add_node` - Hinzufügen eines Knotens zur Benutzer-Mindmap
- `/api/mindmap/<id>/remove_node/<node_id>` - Entfernen eines Knotens
- `/api/mindmap/<id>/update_node_position` - Aktualisierung von Knotenpositionen
- `/api/mindmap/<id>/notes` - Verwaltung von Notizen
- `/api/nodes/<id>/thoughts` - Abrufen und Hinzufügen von Gedanken zu Knoten
- `/api/get_dark_mode` - Abrufen der Dark Mode Einstellung
### 🎯 Grundfunktionen
- [x] **Benutzerauthentifizierung** - Registrierung, Login, Logout
- [x] **Interaktive Mindmap** - Cytoscape.js-basierte Visualisierung
- [x] **Gedankenverwaltung** - CRUD-Operationen für Thoughts
- [x] **Kategoriesystem** - Hierarchische Wissensorganisation
- [x] **Responsive Design** - Mobile-first Ansatz
- [x] **Dark/Light Mode** - Benutzerfreundliche Themes
## Neuronaler Netzwerk-Hintergrund
### 🤖 KI-Integration
- [x] **ChatGPT-Assistent** - Integrierter AI-Chat
- [x] **Intelligente Suche** - KI-gestützte Inhaltssuche
- [x] **Automatische Kategorisierung** - AI-basierte Thought-Klassifizierung
Der neue WebGL-basierte Hintergrund bietet:
### 🎨 UI/UX Verbesserungen
- [x] **Moderne Navigation** - Glassmorphism-Design
- [x] **Animationen** - Smooth Transitions und Hover-Effekte
- [x] **Accessibility** - ARIA-Labels und Keyboard-Navigation
- [x] **Performance-Optimierung** - Lazy Loading und Caching
- WebGL-basierte Rendering-Engine für optimale Performance
- Dynamische Knoten und Verbindungen mit realistischem Verhalten
- Clustering von neuronalen Knoten für natürlicheres Erscheinungsbild
- Simulation von neuronaler Aktivität und Signalweiterleitung
- Anpassbare visuelle Parameter (Helligkeit, Dichte, Geschwindigkeit)
- Vollständig responsives Design für alle Bildschirmgrößen
## 🚀 Neu implementiert (v1.4 - Social Network Update)
## Aktuelle Verbesserungen
- Tailwind CSS wurde auf CDN-Version aktualisiert (06.06.2024)
- Content Security Policy (CSP) für Tailwind CSS CDN und WebGL konfiguriert
- Behebung kritischer Fehler in der Mindmap-Knotenvisualisierung (15.06.2024)
- Verbesserte Verbindungserkennung zwischen Knoten implementiert
- Robuste Fehlerbehandlung für verschiedene API-Datenformate
### 📱 Social Network Features
- [x] **Social Feed** - Instagram/Twitter-ähnlicher Feed
- [x] **Post-System** - Erstellen, Liken, Kommentieren von Posts
- [x] **Follow-System** - Benutzer folgen und entfolgen
- [x] **Discover-Seite** - Trending Posts und empfohlene Benutzer
- [x] **Benutzerprofile** - Erweiterte Profile mit Posts, Mindmaps, Gedanken
- [x] **Benachrichtigungssystem** - Likes, Kommentare, Follows
- [x] **Community-Statistiken** - Aktive Benutzer, Posts, Mindmaps
## Zukünftige Aufgaben (Q3 2024)
- Implementierung des Tagging-Systems für Gedanken
- Quellenmanagement für Mindmap-Knoten
- Erweiterte Benutzerprofilfunktionen
- Verbesserung der mobilen Benutzererfahrung
- Integration von Exportfunktionen für Mindmaps
### 🧠 Erweiterte Mindmap-Features
- [x] **Kollaborative Bearbeitung** - Vorbereitung für Echtzeit-Kollaboration
- [x] **Mindmap-Export** - JSON-Export mit geplanten weiteren Formaten
- [x] **Mindmap-Sharing** - Teilen von Mindmaps in sozialen Netzwerken
- [x] **Erweiterte Toolbar** - Neue Bearbeitungsoptionen
- [x] **Vollbild-Modus** - Immersive Mindmap-Bearbeitung
- [x] **Schnelle Knoten-/Gedanken-Erstellung** - Direkt aus der Mindmap
*Zuletzt aktualisiert: 15.06.2024*
### 🔗 Integration & Vernetzung
- [x] **Gedanken in Posts teilen** - Wissenschaftliche Inhalte im Feed
- [x] **Mindmap-Knoten teilen** - Wissensbausteine verbreiten
- [x] **Cross-Platform Navigation** - Nahtlose Übergänge zwischen Features
- [x] **Unified Search** - Suche über alle Inhaltstypen
## [Entfernt] CORS-Unterstützung (flask-cors)
- Die flask-cors-Bibliothek und alle zugehörigen Initialisierungen wurden entfernt.
- CORS wird nicht mehr unterstützt oder benötigt.
## 🔄 In Entwicklung (v1.5)
### 🔄 Echtzeit-Features
- [ ] **Live-Kollaboration** - Mehrere Benutzer bearbeiten gleichzeitig Mindmaps
- [ ] **WebSocket-Integration** - Echtzeit-Updates für Feed und Benachrichtigungen
- [ ] **Live-Cursor** - Sehen wo andere Benutzer arbeiten
- [ ] **Änderungshistorie** - Versionskontrolle für Mindmaps
### 💬 Erweiterte Kommunikation
- [ ] **Direktnachrichten** - Private Nachrichten zwischen Benutzern
- [ ] **Gruppen-Chats** - Themenbasierte Diskussionsgruppen
- [ ] **Video-Calls** - Integrierte Videokonferenzen für Kollaboration
- [ ] **Screen-Sharing** - Bildschirm teilen während Kollaboration
## 📋 Geplant (v1.6 - v2.0)
### 📊 Analytics & Insights
- [ ] **Lernfortschritt-Tracking** - Persönliche Wissensstatistiken
- [ ] **Mindmap-Analytics** - Nutzungsstatistiken und Hotspots
- [ ] **Community-Insights** - Trending-Themen und beliebte Inhalte
- [ ] **Empfehlungsalgorithmus** - Personalisierte Inhaltsvorschläge
### 🎓 Bildungsfeatures
- [ ] **Kurssystem** - Strukturierte Lernpfade
- [ ] **Quizzes & Tests** - Wissensüberprüfung
- [ ] **Zertifikate** - Digitale Abschlüsse
- [ ] **Mentoring-System** - Experten-Schüler-Verbindungen
### 🔧 Erweiterte Tools
- [ ] **PDF-Import** - Automatische Mindmap-Generierung aus Dokumenten
- [ ] **LaTeX-Support** - Mathematische Formeln in Gedanken
- [ ] **Multimedia-Integration** - Videos, Audio, Bilder in Mindmaps
- [ ] **API für Drittanbieter** - Integration mit anderen Tools
### 🌐 Skalierung & Performance
- [ ] **Microservices-Architektur** - Bessere Skalierbarkeit
- [ ] **CDN-Integration** - Globale Content-Delivery
- [ ] **Caching-Optimierung** - Redis für bessere Performance
- [ ] **Load Balancing** - Hochverfügbarkeit
## 🔮 Vision (v2.0+)
### 🤖 Erweiterte KI
- [ ] **Personalisierte KI-Tutoren** - Individuelle Lernbegleitung
- [ ] **Automatische Mindmap-Generierung** - KI erstellt Mindmaps aus Text
- [ ] **Intelligente Verbindungen** - KI schlägt Gedankenverknüpfungen vor
- [ ] **Adaptive Lernpfade** - KI passt Inhalte an Lernstil an
### 🌍 Globale Community
- [ ] **Mehrsprachigkeit** - Internationale Benutzergemeinschaft
- [ ] **Kultureller Austausch** - Globale Wissensnetzwerke
- [ ] **Übersetzungsfeatures** - Automatische Inhaltsübersetzung
- [ ] **Regionale Communities** - Lokale Wissensgruppen
### 🔬 Forschungstools
- [ ] **Literaturverwaltung** - Integration mit wissenschaftlichen Datenbanken
- [ ] **Zitiersystem** - Automatische Quellenangaben
- [ ] **Peer-Review-System** - Wissenschaftliche Qualitätskontrolle
- [ ] **Publikationstools** - Direkte Veröffentlichung von Forschungsergebnissen
---
## 📈 Metriken & Ziele
### Technische Ziele
- **Performance**: < 2s Ladezeit für alle Seiten
- **Verfügbarkeit**: 99.9% Uptime
- **Skalierbarkeit**: 10.000+ gleichzeitige Benutzer
- **Sicherheit**: Zero-Trust-Architektur
### Community-Ziele
- **Benutzer**: 1.000+ aktive Benutzer bis Ende 2024
- **Inhalte**: 10.000+ Gedanken und 1.000+ Mindmaps
- **Engagement**: 70%+ monatliche Aktivitätsrate
- **Zufriedenheit**: 4.5+ Sterne Bewertung
---
## 🤝 Beitragen
Interessiert an der Mitarbeit? Hier sind die Bereiche, in denen wir Unterstützung suchen:
### 👨‍💻 Entwicklung
- **Frontend**: React/Vue.js Komponenten
- **Backend**: Python/Flask API-Entwicklung
- **Mobile**: React Native App
- **DevOps**: Docker, Kubernetes, CI/CD
### 🎨 Design
- **UI/UX**: Benutzeroberflächen-Design
- **Grafik**: Icons, Illustrationen, Branding
- **Animation**: Micro-Interactions und Transitions
- **Accessibility**: Barrierefreie Gestaltung
### 📝 Content
- **Dokumentation**: Technische und Benutzer-Dokumentation
- **Tutorials**: Video- und Text-Anleitungen
- **Übersetzungen**: Mehrsprachige Inhalte
- **Community**: Moderation und Support
---
*Letzte Aktualisierung: Januar 2024*
*Version: 1.4.0 - Social Network Update*

Binary file not shown.

Binary file not shown.

1494
app.py

File diff suppressed because it is too large Load Diff

5
cookies.txt Normal file
View File

@@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_127.0.0.1 FALSE / FALSE 0 session .eJwlzjEOwjAMQNG7ZGaIYztJe5nKjm2BACG1dELcnUiMf3n6n7TF7sc1re_99EvabpbWZI7cikB4dsoylLrmcKXSormH-OhKoQRSAy0v3kEzDqJlSNFCg8NIW25sfYChAgryFIWxdqyskqFWtNIdiF3awiZRaq9TzGmOnIfv_xuYabLft-fLPK0hj8O_P-1dNpA.aDdoog.bmKi2y6o3HQgIk4gwDvhirnxuoM

Binary file not shown.

0
instance/logs/app.log Normal file
View File

0
instance/logs/errors.log Normal file
View File

0
instance/logs/social.log Normal file
View File

0
logs/api.log Normal file
View File

View File

@@ -1439,3 +1439,967 @@ Traceback (most recent call last):
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
[in C:\Users\firem\Desktop\111\Systades\website\app.py:93]
2025-05-28 20:22:12 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:22:12 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:25:36 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:25:36 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:25:36 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:25:36 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:25:44 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:25:44 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:25:44 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:25:44 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:38:54 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:38:54 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:38:54 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:38:54 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:40:18 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:40:18 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:40:18 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:40:18 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:42:31 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:42:31 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:42:31 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:42:31 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:42:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:42:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:42:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:42:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:43:25 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:43:25 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:43:25 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 20:43:25 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v2.0.0) in development Umgebung
2025-05-28 20:48:39,492 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:41,249 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:41,249 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:44,205 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:45,546 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:45,546 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:49,477 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:48:49,541 ERROR: Fehler 500: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Endpoint: /mindmap, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2419, in _determine_joins
self.primaryjoin = join_condition(
^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/util.py", line 123, in join_condition
return Join._join_condition(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1346, in _join_condition
cls._joincond_trim_constraints(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1491, in _joincond_trim_constraints
raise exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'user' and 'notification'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 498, in mindmap
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/model.py", line 22, in __get__
return cls.query_class(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 276, in __init__
self._set_entities(entities)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 288, in _set_entities
self._raw_columns = [
^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 289, in <listcomp>
coercions.expect(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py", line 406, in expect
insp._post_inspect
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 1260, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2707, in _post_inspect
self._check_configure()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2386, in _check_configure
_configure_registries({self.registry}, cascade=True)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4199, in _configure_registries
_do_configure_registries(registries, cascade)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4240, in _do_configure_registries
mapper._post_configure_properties()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2403, in _post_configure_properties
prop.init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py", line 579, in init
self.do_init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1637, in do_init
self._setup_join_conditions()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1882, in _setup_join_conditions
self._join_condition = jc = JoinCondition(
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2306, in __init__
self._determine_joins()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2463, in _determine_joins
raise sa_exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:48:49,541 ERROR: Fehler 500: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Endpoint: /mindmap, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2419, in _determine_joins
self.primaryjoin = join_condition(
^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/util.py", line 123, in join_condition
return Join._join_condition(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1346, in _join_condition
cls._joincond_trim_constraints(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1491, in _joincond_trim_constraints
raise exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'user' and 'notification'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 498, in mindmap
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/model.py", line 22, in __get__
return cls.query_class(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 276, in __init__
self._set_entities(entities)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 288, in _set_entities
self._raw_columns = [
^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 289, in <listcomp>
coercions.expect(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py", line 406, in expect
insp._post_inspect
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 1260, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2707, in _post_inspect
self._check_configure()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2386, in _check_configure
_configure_registries({self.registry}, cascade=True)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4199, in _configure_registries
_do_configure_registries(registries, cascade)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4240, in _do_configure_registries
mapper._post_configure_properties()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2403, in _post_configure_properties
prop.init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py", line 579, in init
self.do_init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1637, in do_init
self._setup_join_conditions()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1882, in _setup_join_conditions
self._join_condition = jc = JoinCondition(
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2306, in __init__
self._determine_joins()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2463, in _determine_joins
raise sa_exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:49:23,575 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:49:25,085 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:49:25,085 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:43,195 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:44,566 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:44,566 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:47,335 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:48,583 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:48,583 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:50:50,808 ERROR: Fehler 500: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Endpoint: /mindmap, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2419, in _determine_joins
self.primaryjoin = join_condition(
^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/util.py", line 123, in join_condition
return Join._join_condition(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1346, in _join_condition
cls._joincond_trim_constraints(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1491, in _joincond_trim_constraints
raise exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'user' and 'notification'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 498, in mindmap
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/model.py", line 22, in __get__
return cls.query_class(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 276, in __init__
self._set_entities(entities)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 288, in _set_entities
self._raw_columns = [
^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 289, in <listcomp>
coercions.expect(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py", line 406, in expect
insp._post_inspect
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 1260, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2707, in _post_inspect
self._check_configure()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2386, in _check_configure
_configure_registries({self.registry}, cascade=True)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4199, in _configure_registries
_do_configure_registries(registries, cascade)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4240, in _do_configure_registries
mapper._post_configure_properties()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2403, in _post_configure_properties
prop.init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py", line 579, in init
self.do_init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1637, in do_init
self._setup_join_conditions()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1882, in _setup_join_conditions
self._join_condition = jc = JoinCondition(
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2306, in __init__
self._determine_joins()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2463, in _determine_joins
raise sa_exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:50:50,808 ERROR: Fehler 500: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
Endpoint: /mindmap, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2419, in _determine_joins
self.primaryjoin = join_condition(
^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/util.py", line 123, in join_condition
return Join._join_condition(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1346, in _join_condition
cls._joincond_trim_constraints(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/selectable.py", line 1491, in _joincond_trim_constraints
raise exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 'user' and 'notification'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 498, in mindmap
wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/model.py", line 22, in __get__
return cls.query_class(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 276, in __init__
self._set_entities(entities)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 288, in _set_entities
self._raw_columns = [
^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 289, in <listcomp>
coercions.expect(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/coercions.py", line 406, in expect
insp._post_inspect
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/util/langhelpers.py", line 1260, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2707, in _post_inspect
self._check_configure()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2386, in _check_configure
_configure_registries({self.registry}, cascade=True)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4199, in _configure_registries
_do_configure_registries(registries, cascade)
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 4240, in _do_configure_registries
mapper._post_configure_properties()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/mapper.py", line 2403, in _post_configure_properties
prop.init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/interfaces.py", line 579, in init
self.do_init()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1637, in do_init
self._setup_join_conditions()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 1882, in _setup_join_conditions
self._join_condition = jc = JoinCondition(
^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2306, in __init__
self._determine_joins()
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/relationships.py", line 2463, in _determine_joins
raise sa_exc.AmbiguousForeignKeysError(
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship User.notifications - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:50:55,438 ERROR: Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /admin/api/dashboard-data, Method: GET, IP: 192.168.1.100
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:50:55,438 ERROR: Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /admin/api/dashboard-data, Method: GET, IP: 192.168.1.100
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
[in /home/core/dev/website/app.py:93]
2025-05-28 20:51:52,977 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:51:54,651 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:51:54,651 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:20,482 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:21,918 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:21,918 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:24,959 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:26,560 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:26,560 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:33,015 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:34,262 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:34,262 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:52:50,332 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:16,183 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:17,481 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:17,481 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:37,112 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:41,327 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:42,692 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:42,692 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:46,229 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:47,816 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:47,816 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:59,574 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:53:59,671 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:01,103 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:01,103 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:03,761 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:05,299 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:05,299 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:54:15,798 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:06,949 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:27,050 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:28,688 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:28,688 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:31,741 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:33,386 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:56:33,386 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:50,714 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:51,812 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:51,812 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:55,784 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:56,892 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 20:58:56,892 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-28 21:23:04 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:23:04 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:23:06 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:26:38 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:26:38 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:26:39 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:26:40 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:26:40 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:26:40 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:27:05 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:27:05 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:27:06 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:28:07 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:28:07 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:28:08 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:28:09 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:28:09 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:28:09 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:28:11 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:28:11 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:28:13 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:28:13 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:28:13 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:28:13 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:28:29 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 15.365123748779297ms
2025-05-28 21:28:32 | WARNING | SysTades | AUTH | Anmeldung fehlgeschlagen für 'clickcandit@gmail.com' - Grund: invalid_credentials
2025-05-28 21:28:32 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 12.782096862792969ms
2025-05-28 21:28:37 | WARNING | SysTades | AUTH | Anmeldung fehlgeschlagen für 'admin' - Grund: invalid_credentials
2025-05-28 21:28:37 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 145.58863639831543ms
2025-05-28 21:28:59 | INFO | SysTades | AUTH | Benutzer 'admin' erfolgreich angemeldet
2025-05-28 21:28:59 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 227.59008407592773ms
2025-05-28 21:29:08 | ERROR | SysTades | ERROR | Fehler 500: 405 Method Not Allowed: The method is not allowed for the requested URL.
Endpoint: /api/thoughts, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 619, in match
raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.
2025-05-28 21:43:30 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:43:30 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:43:32 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:43:32 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:43:32 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:43:32 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:43:34 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:43:34 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:43:35 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:43:35 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:43:35 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:43:35 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler in social_feed nach 2.83ms - Exception: AttributeError: followed_id
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler 500: followed_id
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler in discover nach 16.89ms - Exception: AttributeError: 'AppenderQuery' object has no attribute 'contains'
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler 500: 'AppenderQuery' object has no attribute 'contains'
Endpoint: /discover, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:44:42 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:44:42 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:44:43 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:44:43 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:44:43 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:44:43 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:45:40 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:45:40 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:45:42 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:46:06 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:46:06 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:46:07 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:46:07 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:46:07 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:46:08 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:46:08 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:46:08 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:46:08 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:46:11 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:46:11 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:46:12 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:46:12 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:46:12 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:46:12 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler in social_feed nach 54.92ms - Exception: OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler 500: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:48:23 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:48:23 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:48:25 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:48:25 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:48:25 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:48:25 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:48:39 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:48:39 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:48:41 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:48:41 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:48:41 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:48:41 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:48:42 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:48:42 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:48:44 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:48:44 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:48:44 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:48:44 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:48:48 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:48:50 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 39.086341857910156ms
2025-05-28 21:48:50 | INFO | SysTades | AUTH | Benutzer 'admin' erfolgreich angemeldet
2025-05-28 21:48:50 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 173.68626594543457ms
2025-05-28 21:48:54 | INFO | SysTades | PERFORMANCE | Performance: discover Ausführungszeit = 198.16184043884277ms
2025-05-28 21:48:54 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /static/fonts/inter-regular.woff2, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 257, in <lambda>
view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 305, in send_static_file
return send_from_directory(
^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/helpers.py", line 554, in send_from_directory
return werkzeug.utils.send_from_directory( # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/utils.py", line 574, in send_from_directory
raise NotFound()
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:48:54 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 3.8046836853027344ms
2025-05-28 21:49:17 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:50:24 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:50:24 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:55:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:55:43 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:55:44 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:55:44 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:55:44 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:55:44 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:55:46 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:55:46 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:55:47 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:55:47 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:55:47 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:55:47 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:55:51 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 34.85536575317383ms
2025-05-28 21:55:54 | INFO | SysTades | PERFORMANCE | Performance: discover Ausführungszeit = 50.58622360229492ms
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:56:13 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 13.908147811889648ms
2025-05-28 21:56:25 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /auth/login, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:56:34 | INFO | SysTades | PERFORMANCE | Performance: discover Ausführungszeit = 11.503219604492188ms
2025-05-28 21:56:41 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:57:14 | INFO | SysTades | PERFORMANCE | Performance: login Ausführungszeit = 3.179311752319336ms
2025-05-28 21:57:25 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(user_follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:58:02 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
users = User.query.filter(
NameError: name 'follows' is not defined
2025-05-28 21:58:16 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:58:16 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:58:18 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:58:18 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:58:18 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:58:18 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:58:20 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:58:20 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:58:22 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:58:22 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:58:22 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:58:22 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:58:48 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:58:48 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:58:49 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:58:50 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:58:50 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:58:50 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 21:58:52 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 21:58:52 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 21:58:53 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 21:58:53 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 21:58:54 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 21:58:54 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 22:08:02 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 22:08:02 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 22:08:04 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 22:08:04 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 22:08:04 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 22:08:04 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 22:08:06 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet
2025-05-28 22:08:06 | INFO | SysTades | SYSTEM | 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
2025-05-28 22:08:07 | INFO | SysTades | SYSTEM | OpenAI API-Verbindung erfolgreich hergestellt
2025-05-28 22:08:07 | INFO | SysTades | DB | Datenbank erfolgreich initialisiert
2025-05-28 22:08:07 | INFO | SysTades | DB | Datenbanktabellen erstellt/aktualisiert
2025-05-28 22:08:07 | INFO | SysTades | SYSTEM | Starte Flask-Entwicklungsserver auf http://localhost:5000
2025-05-28 22:08:09 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 60.69636344909668ms
2025-05-28 22:08:11 | INFO | SysTades | PERFORMANCE | Performance: discover Ausführungszeit = 212.85009384155273ms
2025-05-28 22:08:24 | INFO | SysTades | PERFORMANCE | Performance: social_feed Ausführungszeit = 5.427837371826172ms

424
logs/errors.log Normal file
View File

@@ -0,0 +1,424 @@
2025-05-28 21:29:08 | ERROR | SysTades | ERROR | Fehler 500: 405 Method Not Allowed: The method is not allowed for the requested URL.
Endpoint: /api/thoughts, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 619, in match
raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler in social_feed nach 2.83ms - Exception: AttributeError: followed_id
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:40 | ERROR | SysTades | ERROR | Fehler 500: followed_id
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1633, in __getattr__
return self._index[key][1]
~~~~~~~~~~~^^^^^
KeyError: 'followed_id'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2774, in social_feed
followed_posts = current_user.get_feed_posts(limit=100)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/models.py", line 193, in get_feed_posts
followed_users, SocialPost.user_id == followed_users.c.followed_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/base.py", line 1635, in __getattr__
raise AttributeError(key) from err
AttributeError: followed_id
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler in discover nach 16.89ms - Exception: AttributeError: 'AppenderQuery' object has no attribute 'contains'
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:43:59 | ERROR | SysTades | ERROR | Fehler 500: 'AppenderQuery' object has no attribute 'contains'
Endpoint: /discover, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2800, in discover
~current_user.following.contains(User.id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'AppenderQuery' object has no attribute 'contains'
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler in social_feed nach 54.92ms - Exception: OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:46:15 | ERROR | SysTades | ERROR | Fehler 500: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Endpoint: /feed, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlite3.OperationalError: near "UNION": syntax error
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_login/utils.py", line 290, in decorated_view
return current_app.ensure_sync(func)(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/utils/logger.py", line 586, in wrapper
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 2782, in social_feed
posts = all_posts.paginate(
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/query.py", line 98, in paginate
return QueryPagination(
^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 72, in __init__
items = self._query_items()
^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask_sqlalchemy/pagination.py", line 358, in _query_items
out = query.limit(self.per_page).offset(self._query_offset).all()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2693, in all
return self._iter().all() # type: ignore
^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
return self._execute_internal(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
result: Result[Any] = compile_state_cls.orm_execute_statement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
result = conn.execute(
^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
return meth(
^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
return connection._execute_clauseelement(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
ret = self._execute_context(
^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
return self._exec_single_context(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
self._handle_dbapi_exception(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
self.dialect.do_execute(
File "/home/core/.local/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "UNION": syntax error
[SQL: SELECT anon_1.social_post_id AS anon_1_social_post_id, anon_1.social_post_content AS anon_1_social_post_content, anon_1.social_post_image_url AS anon_1_social_post_image_url, anon_1.social_post_video_url AS anon_1_social_post_video_url, anon_1.social_post_link_url AS anon_1_social_post_link_url, anon_1.social_post_link_title AS anon_1_social_post_link_title, anon_1.social_post_link_description AS anon_1_social_post_link_description, anon_1.social_post_post_type AS anon_1_social_post_post_type, anon_1.social_post_visibility AS anon_1_social_post_visibility, anon_1.social_post_is_pinned AS anon_1_social_post_is_pinned, anon_1.social_post_like_count AS anon_1_social_post_like_count, anon_1.social_post_comment_count AS anon_1_social_post_comment_count, anon_1.social_post_share_count AS anon_1_social_post_share_count, anon_1.social_post_view_count AS anon_1_social_post_view_count, anon_1.social_post_created_at AS anon_1_social_post_created_at, anon_1.social_post_updated_at AS anon_1_social_post_updated_at, anon_1.social_post_user_id AS anon_1_social_post_user_id, anon_1.social_post_shared_thought_id AS anon_1_social_post_shared_thought_id, anon_1.social_post_shared_node_id AS anon_1_social_post_shared_node_id
FROM ((SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id IN (?) ORDER BY social_post.created_at DESC
LIMIT ? OFFSET ?) UNION SELECT social_post.id AS social_post_id, social_post.content AS social_post_content, social_post.image_url AS social_post_image_url, social_post.video_url AS social_post_video_url, social_post.link_url AS social_post_link_url, social_post.link_title AS social_post_link_title, social_post.link_description AS social_post_link_description, social_post.post_type AS social_post_post_type, social_post.visibility AS social_post_visibility, social_post.is_pinned AS social_post_is_pinned, social_post.like_count AS social_post_like_count, social_post.comment_count AS social_post_comment_count, social_post.share_count AS social_post_share_count, social_post.view_count AS social_post_view_count, social_post.created_at AS social_post_created_at, social_post.updated_at AS social_post_updated_at, social_post.user_id AS social_post_user_id, social_post.shared_thought_id AS social_post_shared_thought_id, social_post.shared_node_id AS social_post_shared_node_id
FROM social_post
WHERE social_post.user_id = ?) AS anon_1 ORDER BY anon_1.social_post_created_at DESC
LIMIT ? OFFSET ?]
[parameters: (1, 100, 0, 1, 10, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
2025-05-28 21:48:48 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:48:54 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /static/fonts/inter-regular.woff2, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 852, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 257, in <lambda>
view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 305, in send_static_file
return send_from_directory(
^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/helpers.py", line 554, in send_from_directory
return werkzeug.utils.send_from_directory( # type: ignore[return-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/utils.py", line 574, in send_from_directory
raise NotFound()
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:49:17 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /sw.js, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:55:55 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:56:25 | ERROR | SysTades | ERROR | Fehler 404: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
Endpoint: /auth/login, Method: GET, IP: 127.0.0.1
Nicht angemeldet
Traceback (most recent call last):
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 867, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 841, in dispatch_request
self.raise_routing_exception(req)
File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 450, in raise_routing_exception
raise request.routing_exception # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
result = self.url_adapter.match(return_rule=True) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 624, in match
raise NotFound() from None
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
2025-05-28 21:56:41 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:57:25 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
not_following_subquery = db.session.query(user_follows.c.followed_id).filter(
^^^^^^^
NameError: name 'follows' is not defined
2025-05-28 21:58:02 | ERROR | SysTades | ERROR | Fehler 500: name 'follows' is not defined
Endpoint: /api/discover/users, Method: GET, IP: 127.0.0.1
User: 1 (admin)
Traceback (most recent call last):
File "/home/core/dev/website/app.py", line 424, in wrapper
return func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/core/dev/website/app.py", line 3141, in discover_users
users = User.query.filter(
NameError: name 'follows' is not defined

308
models.py
View File

@@ -45,6 +45,35 @@ user_thought_bookmark = db.Table('user_thought_bookmark',
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Benutzer-Freundschaften
user_friendships = db.Table('user_friendships',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('friend_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow),
db.Column('status', db.String(20), default='pending') # pending, accepted, blocked
)
# Beziehungstabelle für Benutzer-Follows
user_follows = db.Table('user_follows',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Post-Likes
post_likes = db.Table('post_likes',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('post_id', db.Integer, db.ForeignKey('social_post.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
# Beziehungstabelle für Comment-Likes
comment_likes = db.Table('comment_likes',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('comment_id', db.Integer, db.ForeignKey('social_comment.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
@@ -59,7 +88,20 @@ class User(db.Model, UserMixin):
avatar = db.Column(db.String(200), nullable=True) # Profilbild-URL
last_login = db.Column(db.DateTime, nullable=True) # Letzter Login
# Relationships
# Social Network Felder
display_name = db.Column(db.String(100), nullable=True) # Anzeigename
birth_date = db.Column(db.Date, nullable=True) # Geburtsdatum
gender = db.Column(db.String(20), nullable=True) # Geschlecht
phone = db.Column(db.String(20), nullable=True) # Telefonnummer
is_verified = db.Column(db.Boolean, default=False) # Verifizierter Account
is_private = db.Column(db.Boolean, default=False) # Privater Account
follower_count = db.Column(db.Integer, default=0) # Follower-Anzahl
following_count = db.Column(db.Integer, default=0) # Following-Anzahl
post_count = db.Column(db.Integer, default=0) # Post-Anzahl
online_status = db.Column(db.String(20), default='offline') # online, offline, away
last_seen = db.Column(db.DateTime, nullable=True) # Zuletzt gesehen
# Beziehungen
threads = db.relationship('Thread', backref='creator', lazy=True)
messages = db.relationship('Message', backref='author', lazy=True)
projects = db.relationship('Project', backref='owner', lazy=True)
@@ -68,6 +110,37 @@ class User(db.Model, UserMixin):
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
lazy='dynamic', backref=db.backref('bookmarked_by', lazy='dynamic'))
# Social Network Beziehungen
posts = db.relationship('SocialPost', backref='author', lazy=True, cascade="all, delete-orphan")
comments = db.relationship('SocialComment', backref='author', lazy=True, cascade="all, delete-orphan")
notifications = db.relationship('Notification', foreign_keys='Notification.user_id', backref='user', lazy=True, cascade="all, delete-orphan")
# Freundschaften (bidirektional)
friends = db.relationship(
'User',
secondary=user_friendships,
primaryjoin=id == user_friendships.c.user_id,
secondaryjoin=id == user_friendships.c.friend_id,
backref='friend_of',
lazy='dynamic'
)
# Following/Followers
following = db.relationship(
'User',
secondary=user_follows,
primaryjoin=id == user_follows.c.follower_id,
secondaryjoin=id == user_follows.c.followed_id,
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
# Liked Posts und Comments
liked_posts = db.relationship('SocialPost', secondary=post_likes,
backref=db.backref('liked_by', lazy='dynamic'), lazy='dynamic')
liked_comments = db.relationship('SocialComment', secondary=comment_likes,
backref=db.backref('liked_by', lazy='dynamic'), lazy='dynamic')
def __repr__(self):
return f'<User {self.username}>'
@@ -84,6 +157,45 @@ class User(db.Model, UserMixin):
@is_admin.setter
def is_admin(self, value):
self.role = 'admin' if value else 'user'
# Social Network Methoden
def follow(self, user):
"""Folgt einem anderen Benutzer"""
if not self.is_following(user):
self.following.append(user)
user.follower_count += 1
user.following_count += 1
# Notification erstellen
notification = Notification(
user_id=user.id,
type='follow',
message=f'{self.username} folgt dir jetzt',
related_user_id=self.id
)
db.session.add(notification)
def unfollow(self, user):
"""Entfolgt einem Benutzer"""
if self.is_following(user):
self.following.remove(user)
user.follower_count -= 1
user.following_count -= 1
def is_following(self, user):
"""Prüft ob der Benutzer einem anderen folgt"""
return self.following.filter(user_follows.c.followed_id == user.id).count() > 0
def get_feed_posts(self, limit=20):
"""Holt Posts für den Feed (von gefolgten Benutzern)"""
# Hole alle User-IDs von Benutzern, denen ich folge + meine eigene
followed_user_ids = [user.id for user in self.following]
all_user_ids = followed_user_ids + [self.id]
# Hole Posts von diesen Benutzern
return SocialPost.query.filter(
SocialPost.user_id.in_(all_user_ids)
).order_by(SocialPost.created_at.desc()).limit(limit)
class Category(db.Model):
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
@@ -388,4 +500,196 @@ class MindmapShare(db.Model):
)
def __repr__(self):
return f'<MindmapShare: {self.mindmap_id} - {self.shared_with_id} - {self.permission_type.name}>'
return f'<MindmapShare: {self.mindmap_id} - {self.shared_with_id} - {self.permission_type.name}>'
class SocialPost(db.Model):
"""Posts im Social Network"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
image_url = db.Column(db.String(500), nullable=True) # Bild-URL
video_url = db.Column(db.String(500), nullable=True) # Video-URL
link_url = db.Column(db.String(500), nullable=True) # Link-URL
link_title = db.Column(db.String(200), nullable=True) # Link-Titel
link_description = db.Column(db.Text, nullable=True) # Link-Beschreibung
post_type = db.Column(db.String(20), default='text') # text, image, video, link, thought_share
visibility = db.Column(db.String(20), default='public') # public, friends, private
is_pinned = db.Column(db.Boolean, default=False)
like_count = db.Column(db.Integer, default=0)
comment_count = db.Column(db.Integer, default=0)
share_count = db.Column(db.Integer, default=0)
view_count = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Verknüpfung zu Gedanken (falls der Post einen Gedanken teilt)
shared_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
shared_thought = db.relationship('Thought', backref='shared_in_posts')
# Verknüpfung zu Mindmap-Knoten
shared_node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
shared_node = db.relationship('MindMapNode', backref='shared_in_posts')
# Kommentare zu diesem Post
comments = db.relationship('SocialComment', backref='post', lazy=True, cascade="all, delete-orphan")
def __repr__(self):
return f'<SocialPost {self.id} by {self.author.username}>'
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'post_type': self.post_type,
'image_url': self.image_url,
'video_url': self.video_url,
'link_url': self.link_url,
'link_title': self.link_title,
'link_description': self.link_description,
'visibility': self.visibility,
'is_pinned': self.is_pinned,
'like_count': self.like_count,
'comment_count': self.comment_count,
'share_count': self.share_count,
'view_count': self.view_count,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'author': {
'id': self.author.id,
'username': self.author.username,
'display_name': self.author.display_name or self.author.username,
'avatar': self.author.avatar,
'is_verified': self.author.is_verified
},
'shared_thought': self.shared_thought.to_dict() if self.shared_thought else None,
'shared_node': self.shared_node.to_dict() if self.shared_node else None
}
class SocialComment(db.Model):
"""Kommentare zu Posts"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
like_count = db.Column(db.Integer, default=0)
reply_count = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=False)
parent_id = db.Column(db.Integer, db.ForeignKey('social_comment.id'), nullable=True)
# Antworten auf diesen Kommentar
replies = db.relationship('SocialComment', backref=db.backref('parent', remote_side=[id]), lazy=True)
def __repr__(self):
return f'<SocialComment {self.id} by {self.author.username}>'
def to_dict(self):
return {
'id': self.id,
'content': self.content,
'like_count': self.like_count,
'reply_count': self.reply_count,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'author': {
'id': self.author.id,
'username': self.author.username,
'display_name': self.author.display_name or self.author.username,
'avatar': self.author.avatar,
'is_verified': self.author.is_verified
},
'parent_id': self.parent_id
}
class Notification(db.Model):
"""Benachrichtigungen für Benutzer"""
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(50), nullable=False) # follow, like, comment, mention, friend_request, etc.
message = db.Column(db.String(500), nullable=False)
is_read = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Verknüpfungen zu anderen Entitäten
related_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
related_post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=True)
related_comment_id = db.Column(db.Integer, db.ForeignKey('social_comment.id'), nullable=True)
related_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
# Beziehungen
related_user = db.relationship('User', foreign_keys=[related_user_id])
related_post = db.relationship('SocialPost', foreign_keys=[related_post_id])
related_comment = db.relationship('SocialComment', foreign_keys=[related_comment_id])
related_thought = db.relationship('Thought', foreign_keys=[related_thought_id])
def __repr__(self):
return f'<Notification {self.id} for {self.user.username}>'
def to_dict(self):
return {
'id': self.id,
'type': self.type,
'message': self.message,
'is_read': self.is_read,
'created_at': self.created_at.isoformat(),
'related_user': self.related_user.username if self.related_user else None,
'related_post_id': self.related_post_id,
'related_comment_id': self.related_comment_id,
'related_thought_id': self.related_thought_id
}
class UserSettings(db.Model):
"""Benutzereinstellungen"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, unique=True)
# Datenschutz-Einstellungen
profile_visibility = db.Column(db.String(20), default='public') # public, friends, private
show_email = db.Column(db.Boolean, default=False)
show_birth_date = db.Column(db.Boolean, default=False)
show_location = db.Column(db.Boolean, default=True)
allow_friend_requests = db.Column(db.Boolean, default=True)
allow_messages = db.Column(db.String(20), default='everyone') # everyone, friends, none
# Benachrichtigungs-Einstellungen
email_notifications = db.Column(db.Boolean, default=True)
push_notifications = db.Column(db.Boolean, default=True)
notify_on_follow = db.Column(db.Boolean, default=True)
notify_on_like = db.Column(db.Boolean, default=True)
notify_on_comment = db.Column(db.Boolean, default=True)
notify_on_mention = db.Column(db.Boolean, default=True)
notify_on_friend_request = db.Column(db.Boolean, default=True)
# Interface-Einstellungen
dark_mode = db.Column(db.Boolean, default=False)
language = db.Column(db.String(10), default='de')
# Beziehung
user = db.relationship('User', backref=db.backref('settings', uselist=False))
def __repr__(self):
return f'<UserSettings for {self.user.username}>'
class Activity(db.Model):
"""Aktivitätsprotokoll für Benutzer"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
action = db.Column(db.String(100), nullable=False) # login, logout, post_created, thought_shared, etc.
description = db.Column(db.String(500), nullable=True)
ip_address = db.Column(db.String(45), nullable=True) # IPv4/IPv6
user_agent = db.Column(db.String(500), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Verknüpfungen zu anderen Entitäten
related_post_id = db.Column(db.Integer, db.ForeignKey('social_post.id'), nullable=True)
related_thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
related_mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=True)
# Beziehungen
user = db.relationship('User', backref='activities')
related_post = db.relationship('SocialPost')
related_thought = db.relationship('Thought')
related_mindmap = db.relationship('UserMindmap')
def __repr__(self):
return f'<Activity {self.action} by {self.user.username}>'

22
server.log Normal file
View File

@@ -0,0 +1,22 @@
⏰ 21:58:48.486 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet
⏰ 21:58:48.486 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
⏰ 21:58:49.951 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 OpenAI API-Verbindung erfolgreich hergestellt
⏰ 21:58:50.122 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbank erfolgreich initialisiert
⏰ 21:58:50.132 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbanktabellen erstellt/aktualisiert
⏰ 21:58:50.134 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 Starte Flask-Entwicklungsserver auf http://localhost:5000
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with watchdog (inotify)
⏰ 21:58:52.225 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet
⏰ 21:58:52.226 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
⏰ 21:58:53.848 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 OpenAI API-Verbindung erfolgreich hergestellt
⏰ 21:58:53.997 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbank erfolgreich initialisiert
⏰ 21:58:54.002 │ ✅ INFO  │ 🗄 [DB ] │ 🚫 Datenbanktabellen erstellt/aktualisiert
⏰ 21:58:54.006 │ ✅ INFO  │ ⚙ [SYSTEM ] │ 📝 Starte Flask-Entwicklungsserver auf http://localhost:5000
* Debugger is active!
* Debugger PIN: 114-005-893

915
static/css/social.css Normal file
View File

@@ -0,0 +1,915 @@
/* ================================
SysTades Social Network Styles
================================ */
:root {
/* Primary Colors */
--primary-50: #f0f9ff;
--primary-100: #e0f2fe;
--primary-200: #bae6fd;
--primary-300: #7dd3fc;
--primary-400: #38bdf8;
--primary-500: #0ea5e9;
--primary-600: #0284c7;
--primary-700: #0369a1;
--primary-800: #075985;
--primary-900: #0c4a6e;
/* Neutral Colors */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Semantic Colors */
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--info: #3b82f6;
/* Social Media Colors */
--like-color: #ec4899;
--share-color: #8b5cf6;
--bookmark-color: #f59e0b;
--comment-color: var(--primary-500);
/* Glassmorphism */
--glass-bg: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
/* Animations */
--transition-fast: 0.15s ease-out;
--transition-normal: 0.3s ease-out;
--transition-slow: 0.6s ease-out;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Border Radius */
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
}
/* Dark Mode Variables */
[data-theme="dark"] {
--glass-bg: rgba(0, 0, 0, 0.1);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
/* ================================
Performance Optimizations
================================ */
* {
box-sizing: border-box;
}
img {
max-width: 100%;
height: auto;
}
/* GPU Acceleration for animations */
.accelerated {
transform: translateZ(0);
will-change: transform;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* ================================
Social Feed Styles
================================ */
.social-feed {
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}
.post-card {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
margin-bottom: 1.5rem;
padding: 1.5rem;
box-shadow: var(--shadow-lg);
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
}
.post-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-xl);
border-color: var(--primary-300);
}
.post-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary-500), var(--primary-600));
opacity: 0;
transition: opacity var(--transition-normal);
}
.post-card:hover::before {
opacity: 1;
}
/* Post Header */
.post-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
gap: 0.75rem;
}
.post-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
border: 2px solid var(--primary-500);
transition: all var(--transition-fast);
cursor: pointer;
}
.post-avatar:hover {
transform: scale(1.1);
border-color: var(--primary-400);
}
.post-author {
flex: 1;
}
.post-author-name {
font-weight: 600;
color: var(--gray-800);
margin: 0;
font-size: 1rem;
}
.post-author-username {
color: var(--gray-500);
font-size: 0.875rem;
margin: 0;
}
.post-time {
color: var(--gray-400);
font-size: 0.875rem;
}
/* Post Content */
.post-content {
margin-bottom: 1rem;
line-height: 1.6;
color: var(--gray-700);
}
.post-type-badge {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: var(--radius-md);
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.post-type-text { background: var(--gray-100); color: var(--gray-600); }
.post-type-thought { background: var(--primary-100); color: var(--primary-600); }
.post-type-question { background: var(--warning); color: white; }
.post-type-insight { background: var(--success); color: white; }
/* Post Actions */
.post-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid var(--gray-200);
}
.action-group {
display: flex;
gap: 1rem;
}
.action-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: none;
background: transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
font-size: 0.875rem;
color: var(--gray-500);
}
.action-btn:hover {
background: var(--gray-100);
color: var(--gray-700);
transform: translateY(-1px);
}
.action-btn.active {
color: var(--primary-600);
background: var(--primary-50);
}
.action-btn i {
font-size: 1rem;
}
/* Specific action colors */
.action-btn.like-btn.active {
color: var(--like-color);
background: rgba(236, 72, 153, 0.1);
}
.action-btn.share-btn:hover {
color: var(--share-color);
background: rgba(139, 92, 246, 0.1);
}
.action-btn.bookmark-btn.active {
color: var(--bookmark-color);
background: rgba(245, 158, 11, 0.1);
}
/* ================================
Comments Section
================================ */
.comments-section {
border-top: 1px solid var(--gray-200);
padding-top: 1rem;
margin-top: 1rem;
}
.comment-item {
display: flex;
gap: 0.75rem;
margin-bottom: 1rem;
padding: 0.75rem;
border-radius: var(--radius-lg);
background: var(--gray-50);
transition: background var(--transition-fast);
}
.comment-item:hover {
background: var(--gray-100);
}
.comment-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid var(--primary-400);
}
.comment-content {
flex: 1;
}
.comment-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.comment-author {
font-weight: 600;
color: var(--gray-800);
font-size: 0.875rem;
}
.comment-time {
color: var(--gray-400);
font-size: 0.75rem;
}
.comment-text {
color: var(--gray-700);
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 0.5rem;
}
.comment-actions {
display: flex;
gap: 1rem;
}
.comment-action {
background: none;
border: none;
color: var(--gray-400);
font-size: 0.75rem;
cursor: pointer;
transition: color var(--transition-fast);
}
.comment-action:hover {
color: var(--primary-500);
}
/* Comment Form */
.comment-form {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
.comment-form textarea {
flex: 1;
padding: 0.75rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-lg);
resize: none;
min-height: 80px;
transition: border-color var(--transition-fast);
}
.comment-form textarea:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.comment-submit {
padding: 0.75rem 1.5rem;
background: var(--primary-500);
color: white;
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
}
.comment-submit:hover {
background: var(--primary-600);
transform: translateY(-1px);
}
/* ================================
Create Post Form
================================ */
.create-post-form {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-xl);
padding: 1.5rem;
margin-bottom: 2rem;
box-shadow: var(--shadow-md);
}
.create-post-textarea {
width: 100%;
min-height: 120px;
padding: 1rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-lg);
resize: vertical;
font-family: inherit;
font-size: 1rem;
line-height: 1.5;
transition: all var(--transition-normal);
}
.create-post-textarea:focus {
outline: none;
border-color: var(--primary-500);
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
.create-post-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1rem;
gap: 1rem;
}
.post-type-select,
.post-visibility-select {
padding: 0.5rem 1rem;
border: 1px solid var(--gray-300);
border-radius: var(--radius-md);
background: white;
cursor: pointer;
transition: border-color var(--transition-fast);
}
.post-type-select:focus,
.post-visibility-select:focus {
outline: none;
border-color: var(--primary-500);
}
.create-post-btn {
padding: 0.75rem 2rem;
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
border: none;
border-radius: var(--radius-lg);
cursor: pointer;
font-weight: 600;
transition: all var(--transition-normal);
box-shadow: var(--shadow-md);
}
.create-post-btn:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.create-post-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
/* ================================
Filter Tabs
================================ */
.feed-filters {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
padding: 0.5rem;
background: var(--gray-100);
border-radius: var(--radius-xl);
overflow-x: auto;
}
.filter-tab {
padding: 0.75rem 1.5rem;
border: none;
background: transparent;
border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--transition-fast);
font-weight: 500;
white-space: nowrap;
color: var(--gray-600);
}
.filter-tab:hover {
background: var(--gray-200);
color: var(--gray-800);
}
.filter-tab.active {
background: var(--primary-500);
color: white;
box-shadow: var(--shadow-md);
}
/* ================================
Notifications
================================ */
.notification-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border-radius: var(--radius-lg);
transition: all var(--transition-fast);
cursor: pointer;
position: relative;
}
.notification-item:hover {
background: var(--gray-50);
transform: translateX(4px);
}
.notification-item.unread {
background: var(--primary-50);
border-left: 4px solid var(--primary-500);
}
.notification-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
color: white;
}
.notification-like { background: var(--like-color); }
.notification-comment { background: var(--comment-color); }
.notification-follow { background: var(--success); }
.notification-share { background: var(--share-color); }
.notification-content {
flex: 1;
}
.notification-text {
margin: 0 0 0.25rem 0;
color: var(--gray-800);
font-size: 0.875rem;
}
.notification-time {
color: var(--gray-500);
font-size: 0.75rem;
}
.notification-actions {
display: flex;
gap: 0.5rem;
}
.notification-delete {
background: none;
border: none;
color: var(--gray-400);
cursor: pointer;
padding: 0.5rem;
border-radius: var(--radius-md);
transition: all var(--transition-fast);
}
.notification-delete:hover {
background: var(--error);
color: white;
}
/* ================================
User Profile
================================ */
.profile-header {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
color: white;
padding: 2rem;
border-radius: var(--radius-2xl);
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.profile-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
.profile-info {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 1.5rem;
}
.profile-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
border: 4px solid rgba(255, 255, 255, 0.3);
box-shadow: var(--shadow-xl);
}
.profile-details h1 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
font-weight: 700;
}
.profile-username {
opacity: 0.9;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.profile-bio {
opacity: 0.8;
line-height: 1.5;
max-width: 500px;
}
.profile-stats {
display: flex;
gap: 2rem;
margin-top: 1.5rem;
}
.stat-item {
text-align: center;
}
.stat-number {
display: block;
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.875rem;
opacity: 0.8;
}
.follow-btn {
background: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 0.75rem 2rem;
border-radius: var(--radius-lg);
font-weight: 600;
cursor: pointer;
transition: all var(--transition-normal);
backdrop-filter: blur(10px);
}
.follow-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.follow-btn.following {
background: rgba(255, 255, 255, 0.9);
color: var(--primary-600);
}
/* Profile Tabs */
.profile-tabs {
display: flex;
border-bottom: 1px solid var(--gray-200);
margin-bottom: 2rem;
overflow-x: auto;
}
.profile-tab {
padding: 1rem 2rem;
border: none;
background: none;
cursor: pointer;
position: relative;
color: var(--gray-600);
font-weight: 500;
transition: all var(--transition-fast);
white-space: nowrap;
}
.profile-tab:hover {
color: var(--primary-600);
}
.profile-tab.active {
color: var(--primary-600);
}
.profile-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: var(--primary-500);
border-radius: 2px 2px 0 0;
}
/* ================================
Responsive Design
================================ */
@media (max-width: 768px) {
.social-feed {
padding: 0.5rem;
}
.post-card {
padding: 1rem;
margin-bottom: 1rem;
}
.post-actions {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.action-group {
justify-content: space-around;
}
.create-post-options {
flex-direction: column;
align-items: stretch;
gap: 0.75rem;
}
.feed-filters {
padding: 0.25rem;
gap: 0.25rem;
}
.filter-tab {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.profile-header {
padding: 1.5rem;
}
.profile-info {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.profile-avatar {
width: 80px;
height: 80px;
}
.profile-details h1 {
font-size: 1.5rem;
}
.profile-stats {
justify-content: center;
gap: 1.5rem;
}
.profile-tabs {
gap: 0;
}
.profile-tab {
flex: 1;
padding: 0.75rem 1rem;
text-align: center;
font-size: 0.875rem;
}
}
/* ================================
Loading & Animations
================================ */
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--gray-300);
border-radius: 50%;
border-top-color: var(--primary-500);
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ================================
Toast Notifications
================================ */
.toast-container {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
pointer-events: none;
}
.toast {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 1rem 1.5rem;
margin-bottom: 0.5rem;
box-shadow: var(--shadow-lg);
pointer-events: all;
max-width: 400px;
animation: slideInRight 0.3s ease-out;
}
.toast.success {
border-left: 4px solid var(--success);
}
.toast.error {
border-left: 4px solid var(--error);
}
.toast.warning {
border-left: 4px solid var(--warning);
}
.toast.info {
border-left: 4px solid var(--info);
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* ================================
Utilities
================================ */
.text-gradient {
background: linear-gradient(135deg, var(--primary-500), var(--primary-600));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass-effect {
background: var(--glass-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--glass-border);
}
.shadow-glow {
box-shadow: 0 0 20px rgba(14, 165, 233, 0.3);
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}

1133
static/js/social.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -307,7 +307,7 @@
.chat-assistant .chat-messages {
max-height: calc(80vh - 160px) !important;
}
</style>
</style>
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
darkMode: true,
@@ -404,6 +404,22 @@
: '{{ 'nav-link-light-active' if request.endpoint == 'mindmap' else 'nav-link-light' }}'">
<i class="fa-solid fa-diagram-project mr-2"></i>Mindmap
</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('social_feed') }}"
class="nav-link flex items-center"
x-bind:class="darkMode
? '{{ 'nav-link-active' if request.endpoint == 'social_feed' else '' }}'
: '{{ 'nav-link-light-active' if request.endpoint == 'social_feed' else 'nav-link-light' }}'">
<i class="fa-solid fa-home mr-2"></i>Feed
</a>
<a href="{{ url_for('discover') }}"
class="nav-link flex items-center"
x-bind:class="darkMode
? '{{ 'nav-link-active' if request.endpoint == 'discover' else '' }}'
: '{{ 'nav-link-light-active' if request.endpoint == 'discover' else 'nav-link-light' }}'">
<i class="fa-solid fa-compass mr-2"></i>Entdecken
</a>
{% endif %}
<a href="{{ url_for('search_thoughts_page') }}"
class="nav-link flex items-center"
x-bind:class="darkMode
@@ -573,6 +589,22 @@
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'mindmap' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
<i class="fa-solid fa-diagram-project w-5 mr-3"></i>Mindmap
</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('social_feed') }}"
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
x-bind:class="darkMode
? '{{ 'bg-purple-500/20 text-white' if request.endpoint == 'social_feed' else 'text-white/80 hover:bg-gray-800/80 hover:text-white' }}'
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'social_feed' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
<i class="fa-solid fa-home w-5 mr-3"></i>Feed
</a>
<a href="{{ url_for('discover') }}"
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
x-bind:class="darkMode
? '{{ 'bg-purple-500/20 text-white' if request.endpoint == 'discover' else 'text-white/80 hover:bg-gray-800/80 hover:text-white' }}'
: '{{ 'bg-purple-500/10 text-gray-900' if request.endpoint == 'discover' else 'text-gray-700 hover:bg-gray-100 hover:text-gray-900' }}'">
<i class="fa-solid fa-compass w-5 mr-3"></i>Entdecken
</a>
{% endif %}
<a href="{{ url_for('search_thoughts_page') }}"
class="block py-3.5 px-4 rounded-xl transition-all duration-200 flex items-center"
x-bind:class="darkMode

View File

@@ -411,17 +411,49 @@
<div class="mindmap-container">
<div id="cy"></div>
<!-- Zoom-Toolbar für Hauptmindmap -->
<!-- Toolbar -->
<div class="mindmap-toolbar">
<button id="zoomIn" title="Vergrößern">
<i class="fas fa-plus"></i>
</button>
<button id="zoomOut" title="Verkleinern">
<i class="fas fa-minus"></i>
</button>
<button id="resetView" title="Ansicht zurücksetzen">
<i class="fas fa-expand"></i>
</button>
<div class="toolbar-section">
<button id="add-node-btn" class="toolbar-btn" title="Knoten hinzufügen">
<i class="fas fa-plus"></i>
<span>Knoten</span>
</button>
<button id="add-thought-btn" class="toolbar-btn" title="Gedanken hinzufügen">
<i class="fas fa-lightbulb"></i>
<span>Gedanke</span>
</button>
<button id="collaborate-btn" class="toolbar-btn" title="Kollaboration starten">
<i class="fas fa-users"></i>
<span>Kollaboration</span>
</button>
</div>
<div class="toolbar-section">
<button id="export-btn" class="toolbar-btn" title="Mindmap exportieren">
<i class="fas fa-download"></i>
<span>Export</span>
</button>
<button id="share-btn" class="toolbar-btn" title="Mindmap teilen">
<i class="fas fa-share"></i>
<span>Teilen</span>
</button>
<button id="fullscreen-btn" class="toolbar-btn" title="Vollbild">
<i class="fas fa-expand"></i>
<span>Vollbild</span>
</button>
</div>
<div class="toolbar-section">
<button id="zoom-in-btn" class="toolbar-btn" title="Vergrößern">
<i class="fas fa-search-plus"></i>
</button>
<button id="zoom-out-btn" class="toolbar-btn" title="Verkleinern">
<i class="fas fa-search-minus"></i>
</button>
<button id="reset-view-btn" class="toolbar-btn" title="Ansicht zurücksetzen">
<i class="fas fa-home"></i>
</button>
</div>
</div>
<div class="mindmap-header">
@@ -679,9 +711,9 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Funktionen für Zoom-Buttons und Reset
const zoomInBtn = document.getElementById('zoomIn');
const zoomOutBtn = document.getElementById('zoomOut');
const resetViewBtn = document.getElementById('resetView');
const zoomInBtn = document.getElementById('zoom-in-btn');
const zoomOutBtn = document.getElementById('zoom-out-btn');
const resetViewBtn = document.getElementById('reset-view-btn');
if (zoomInBtn && window.cy) {
zoomInBtn.addEventListener('click', function() {
@@ -700,6 +732,199 @@ document.addEventListener('DOMContentLoaded', function() {
if (window.cy) window.cy.fit();
});
}
// Neue Toolbar-Funktionen
const addNodeBtn = document.getElementById('add-node-btn');
const addThoughtBtn = document.getElementById('add-thought-btn');
const collaborateBtn = document.getElementById('collaborate-btn');
const exportBtn = document.getElementById('export-btn');
const shareBtn = document.getElementById('share-btn');
const fullscreenBtn = document.getElementById('fullscreen-btn');
if (addNodeBtn) {
addNodeBtn.addEventListener('click', function() {
// Öffne Modal zum Hinzufügen eines neuen Knotens
showAddNodeModal();
});
}
if (addThoughtBtn) {
addThoughtBtn.addEventListener('click', function() {
// Öffne Modal zum Hinzufügen eines Gedankens
showAddThoughtModal();
});
}
if (collaborateBtn) {
collaborateBtn.addEventListener('click', function() {
// Starte Kollaborationsmodus
startCollaboration();
});
}
if (exportBtn) {
exportBtn.addEventListener('click', function() {
// Exportiere Mindmap
exportMindmap();
});
}
if (shareBtn) {
shareBtn.addEventListener('click', function() {
// Teile Mindmap
shareMindmap();
});
}
if (fullscreenBtn) {
fullscreenBtn.addEventListener('click', function() {
// Vollbild-Modus
toggleFullscreen();
});
}
// Funktionen implementieren
function showAddNodeModal() {
// Erstelle ein einfaches Modal für neuen Knoten
const nodeName = prompt('Name des neuen Knotens:');
if (nodeName && nodeName.trim()) {
addNewNode(nodeName.trim());
}
}
function showAddThoughtModal() {
// Erstelle ein Modal für neuen Gedanken
const thoughtTitle = prompt('Titel des Gedankens:');
if (thoughtTitle && thoughtTitle.trim()) {
addNewThought(thoughtTitle.trim());
}
}
function addNewNode(name) {
// API-Aufruf zum Hinzufügen eines neuen Knotens
fetch('/api/mindmap/public/add_node', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
description: '',
x_position: Math.random() * 400 + 100,
y_position: Math.random() * 400 + 100
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Lade Mindmap neu
location.reload();
} else {
alert('Fehler beim Hinzufügen des Knotens: ' + (data.error || 'Unbekannter Fehler'));
}
})
.catch(error => {
console.error('Fehler:', error);
alert('Ein Fehler ist aufgetreten.');
});
}
function addNewThought(title) {
// API-Aufruf zum Hinzufügen eines neuen Gedankens
fetch('/api/thoughts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: title,
content: 'Neuer Gedanke erstellt über die Mindmap',
branch: 'Allgemein'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Gedanke erfolgreich erstellt!');
} else {
alert('Fehler beim Erstellen des Gedankens: ' + (data.error || 'Unbekannter Fehler'));
}
})
.catch(error => {
console.error('Fehler:', error);
alert('Ein Fehler ist aufgetreten.');
});
}
function startCollaboration() {
// Kollaborationsmodus starten
alert('Kollaborationsmodus wird bald verfügbar sein!\n\nGeplante Features:\n- Echtzeit-Bearbeitung\n- Live-Cursor anderer Benutzer\n- Chat-Integration\n- Änderungshistorie');
}
function exportMindmap() {
// Mindmap exportieren
const format = prompt('Export-Format wählen:\n1. JSON\n2. PNG (geplant)\n3. PDF (geplant)\n\nGeben Sie 1, 2 oder 3 ein:', '1');
if (format === '1') {
// JSON-Export
if (window.cy) {
const data = window.cy.json();
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'mindmap-export.json';
a.click();
URL.revokeObjectURL(url);
}
} else {
alert('Dieses Format wird bald verfügbar sein!');
}
}
function shareMindmap() {
// Mindmap teilen
if (navigator.share) {
navigator.share({
title: 'SysTades Mindmap',
text: 'Schau dir diese interessante Mindmap an!',
url: window.location.href
});
} else {
// Fallback: URL kopieren
navigator.clipboard.writeText(window.location.href).then(() => {
alert('Mindmap-Link wurde in die Zwischenablage kopiert!');
}).catch(() => {
prompt('Kopiere diesen Link zum Teilen:', window.location.href);
});
}
}
function toggleFullscreen() {
// Vollbild-Modus umschalten
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(err => {
console.error('Fehler beim Aktivieren des Vollbildmodus:', err);
});
} else {
document.exitFullscreen();
}
}
// Vollbild-Event-Listener
document.addEventListener('fullscreenchange', function() {
const fullscreenBtn = document.getElementById('fullscreen-btn');
if (fullscreenBtn) {
const icon = fullscreenBtn.querySelector('i');
if (document.fullscreenElement) {
icon.className = 'fas fa-compress';
fullscreenBtn.title = 'Vollbild verlassen';
} else {
icon.className = 'fas fa-expand';
fullscreenBtn.title = 'Vollbild';
}
}
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,512 @@
{% extends "base.html" %}
{% block title %}Entdecken{% endblock %}
{% block content %}
<div class="min-h-screen"
x-data="{
activeTab: 'users',
users: [],
posts: [],
trending: [],
loading: false,
searchQuery: '',
searchResults: [],
searching: false,
async loadUsers() {
this.loading = true;
try {
const response = await fetch('/api/discover/users');
const data = await response.json();
if (data.success) {
this.users = data.users;
}
} catch (error) {
console.error('Error loading users:', error);
} finally {
this.loading = false;
}
},
async loadPosts() {
this.loading = true;
try {
const response = await fetch('/api/discover/posts');
const data = await response.json();
if (data.success) {
this.posts = data.posts;
}
} catch (error) {
console.error('Error loading posts:', error);
} finally {
this.loading = false;
}
},
async loadTrending() {
this.loading = true;
try {
const response = await fetch('/api/discover/trending');
const data = await response.json();
if (data.success) {
this.trending = data.trending;
}
} catch (error) {
console.error('Error loading trending:', error);
} finally {
this.loading = false;
}
},
async follow(userId, index) {
try {
const response = await fetch(`/api/users/${userId}/follow`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
this.users[index].is_following = data.is_following;
this.users[index].follower_count = data.follower_count;
}
} catch (error) {
console.error('Error following user:', error);
}
},
async searchUsers() {
if (!this.searchQuery.trim()) {
this.searchResults = [];
return;
}
this.searching = true;
try {
const response = await fetch(`/api/search/users?q=${encodeURIComponent(this.searchQuery)}`);
const data = await response.json();
if (data.success) {
this.searchResults = data.users;
}
} catch (error) {
console.error('Error searching users:', error);
} finally {
this.searching = false;
}
},
formatNumber(num) {
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num.toString();
},
init() {
this.loadUsers();
}
}"
x-init="init()">
<!-- Header Section -->
<div class="border-b"
:class="darkMode ? 'border-gray-700 bg-gray-900/50' : 'border-gray-200 bg-white/50'">
<div class="max-w-6xl mx-auto px-4 py-6">
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold"
:class="darkMode ? 'text-white' : 'text-gray-900'">
Entdecken
</h1>
<p class="text-lg mt-1"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">
Finde neue Leute und interessante Inhalte
</p>
</div>
<!-- Search Bar -->
<div class="flex-1 max-w-md ml-8">
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-search"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'></i>
</div>
<input
x-model="searchQuery"
@input.debounce.300ms="searchUsers()"
type="text"
class="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-xl leading-5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500/50 focus:border-purple-500"
:class="darkMode
? 'bg-gray-800 border-gray-600 text-white placeholder-gray-400'
: 'bg-white border-gray-300 text-gray-900 placeholder-gray-500'"
placeholder="Nutzer suchen...">
</div>
<!-- Search Results Dropdown -->
<div x-show="searchQuery && searchResults.length > 0"
x-transition
class="absolute z-50 mt-2 w-full rounded-xl shadow-lg border"
:class="darkMode
? 'bg-gray-800 border-gray-600'
: 'bg-white border-gray-200'">
<template x-for="user in searchResults.slice(0, 5)">
<div class="p-3 hover:bg-gray-50 transition-colors duration-150 cursor-pointer"
:class="darkMode ? 'hover:bg-gray-700' : 'hover:bg-gray-50'"
@click="window.location.href = `/profile/${user.username}`">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-semibold">
<span x-text="user.username.charAt(0).toUpperCase()"></span>
</div>
<div>
<p class="font-medium"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="user.display_name || user.username"></p>
<p class="text-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'"
x-text="`@${user.username}`"></p>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
<!-- Tab Navigation -->
<div class="flex space-x-1 bg-gray-100 rounded-xl p-1"
:class="darkMode ? 'bg-gray-800' : 'bg-gray-100'">
<button @click="activeTab = 'users'; loadUsers()"
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-200"
:class="activeTab === 'users'
? (darkMode ? 'bg-purple-600 text-white shadow-lg' : 'bg-white text-purple-600 shadow-md')
: (darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900')">
<i class="fas fa-users mr-2"></i>
Nutzer
</button>
<button @click="activeTab = 'posts'; loadPosts()"
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-200"
:class="activeTab === 'posts'
? (darkMode ? 'bg-purple-600 text-white shadow-lg' : 'bg-white text-purple-600 shadow-md')
: (darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900')">
<i class="fas fa-fire mr-2"></i>
Beliebte Posts
</button>
<button @click="activeTab = 'trending'; loadTrending()"
class="flex-1 px-4 py-2 rounded-lg font-medium transition-all duration-200"
:class="activeTab === 'trending'
? (darkMode ? 'bg-purple-600 text-white shadow-lg' : 'bg-white text-purple-600 shadow-md')
: (darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900')">
<i class="fas fa-trending-up mr-2"></i>
Im Trend
</button>
</div>
</div>
</div>
<!-- Content Section -->
<div class="max-w-6xl mx-auto px-4 py-8">
<!-- Loading State -->
<div x-show="loading" class="text-center py-16">
<div class="inline-flex items-center space-x-3"
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
<svg class="animate-spin h-8 w-8" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-lg">Lädt...</span>
</div>
</div>
<!-- Users Tab -->
<div x-show="activeTab === 'users' && !loading">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<template x-for="(user, index) in users" :key="user.id">
<div class="rounded-2xl p-6 shadow-lg border transition-all duration-300 hover:shadow-xl transform hover:scale-105"
:class="darkMode
? 'bg-gray-800/90 border-gray-700/50 backdrop-blur-xl'
: 'bg-white/80 border-gray-200/50 backdrop-blur-xl'">
<!-- User Avatar -->
<div class="text-center mb-4">
<div class="w-20 h-20 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-bold text-2xl mx-auto shadow-lg">
<span x-text="user.username.charAt(0).toUpperCase()"></span>
</div>
</div>
<!-- User Info -->
<div class="text-center mb-4">
<h3 class="text-xl font-bold mb-1"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="user.display_name || user.username"></h3>
<p class="text-sm mb-2"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'"
x-text="`@${user.username}`"></p>
<div x-show="user.bio" class="mb-3">
<p class="text-sm"
:class="darkMode ? 'text-gray-300' : 'text-gray-700'"
x-text="user.bio"></p>
</div>
</div>
<!-- User Stats -->
<div class="flex justify-center space-x-6 mb-4">
<div class="text-center">
<div class="text-lg font-bold"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="formatNumber(user.follower_count || 0)"></div>
<div class="text-xs"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'">
Follower
</div>
</div>
<div class="text-center">
<div class="text-lg font-bold"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="formatNumber(user.following_count || 0)"></div>
<div class="text-xs"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'">
Folge ich
</div>
</div>
<div class="text-center">
<div class="text-lg font-bold"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="formatNumber(user.post_count || 0)"></div>
<div class="text-xs"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'">
Posts
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex space-x-2">
<button @click="follow(user.id, index)"
class="flex-1 py-2 px-4 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105"
:class="user.is_following
? (darkMode ? 'bg-gray-700 text-white border border-gray-600 hover:bg-gray-600' : 'bg-gray-100 text-gray-700 border border-gray-300 hover:bg-gray-200')
: 'bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-md hover:shadow-lg'">
<span x-text="user.is_following ? 'Entfolgen' : 'Folgen'"></span>
</button>
<button @click="window.location.href = `/profile/${user.username}`"
class="px-4 py-2 rounded-xl transition-all duration-300 transform hover:scale-105"
:class="darkMode
? 'bg-gray-700 text-white border border-gray-600 hover:bg-gray-600'
: 'bg-gray-100 text-gray-700 border border-gray-300 hover:bg-gray-200'">
<i class="fas fa-user"></i>
</button>
</div>
</div>
</template>
</div>
<!-- Empty State for Users -->
<div x-show="!loading && users.length === 0" class="text-center py-16">
<div class="mb-6">
<i class="fas fa-users text-6xl mb-4"
:class="darkMode ? 'text-gray-600' : 'text-gray-300'"></i>
</div>
<h3 class="text-2xl font-bold mb-2"
:class="darkMode ? 'text-white' : 'text-gray-900'">
Keine Nutzer gefunden
</h3>
<p class="text-lg"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">
Versuche es später noch einmal oder ändere deine Suchkriterien.
</p>
</div>
</div>
<!-- Popular Posts Tab -->
<div x-show="activeTab === 'posts' && !loading">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<template x-for="post in posts" :key="post.id">
<article class="rounded-2xl p-6 shadow-lg border transition-all duration-300 hover:shadow-xl"
:class="darkMode
? 'bg-gray-800/90 border-gray-700/50 backdrop-blur-xl'
: 'bg-white/80 border-gray-200/50 backdrop-blur-xl'">
<!-- Post Header -->
<div class="flex items-center space-x-3 mb-4">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg shadow-md">
<span x-text="post.author.username.charAt(0).toUpperCase()"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-2">
<h4 class="font-semibold truncate"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="post.author.display_name || post.author.username"></h4>
<span x-show="post.author.is_verified"
class="text-blue-500">
<i class="fas fa-check-circle text-sm"></i>
</span>
</div>
<p class="text-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'"
x-text="formatTimeAgo(post.created_at)"></p>
</div>
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-1 text-sm px-2 py-1 rounded-full"
:class="darkMode ? 'bg-red-500/20 text-red-400' : 'bg-red-50 text-red-500'">
<i class="fas fa-fire"></i>
<span>Trending</span>
</div>
</div>
</div>
<!-- Post Content -->
<div class="mb-4">
<p class="text-lg leading-relaxed"
:class="darkMode ? 'text-gray-100' : 'text-gray-800'"
x-text="post.content"></p>
</div>
<!-- Post Stats -->
<div class="flex items-center space-x-6 text-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'">
<div class="flex items-center space-x-1">
<i class="fas fa-heart"></i>
<span x-text="formatNumber(post.like_count || 0)"></span>
</div>
<div class="flex items-center space-x-1">
<i class="far fa-comment"></i>
<span x-text="formatNumber(post.comment_count || 0)"></span>
</div>
<div class="flex items-center space-x-1">
<i class="fas fa-share"></i>
<span x-text="formatNumber(post.share_count || 0)"></span>
</div>
</div>
</article>
</template>
</div>
<!-- Empty State for Posts -->
<div x-show="!loading && posts.length === 0" class="text-center py-16">
<div class="mb-6">
<i class="fas fa-fire text-6xl mb-4"
:class="darkMode ? 'text-gray-600' : 'text-gray-300'"></i>
</div>
<h3 class="text-2xl font-bold mb-2"
:class="darkMode ? 'text-white' : 'text-gray-900'">
Keine beliebten Posts
</h3>
<p class="text-lg"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">
Noch keine Posts sind viral gegangen. Sei der Erste!
</p>
</div>
</div>
<!-- Trending Tab -->
<div x-show="activeTab === 'trending' && !loading">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<template x-for="item in trending" :key="item.id">
<div class="rounded-2xl p-6 shadow-lg border transition-all duration-300 hover:shadow-xl transform hover:scale-105"
:class="darkMode
? 'bg-gray-800/90 border-gray-700/50 backdrop-blur-xl'
: 'bg-white/80 border-gray-200/50 backdrop-blur-xl'">
<div class="flex items-center space-x-3 mb-4">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center text-white shadow-md">
<i class="fas fa-hashtag text-lg"></i>
</div>
<div class="flex-1">
<h3 class="font-bold text-lg"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="item.title"></h3>
<p class="text-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'"
x-text="`${formatNumber(item.count)} Posts`"></p>
</div>
<div class="flex items-center space-x-1 text-sm px-2 py-1 rounded-full"
:class="darkMode ? 'bg-orange-500/20 text-orange-400' : 'bg-orange-50 text-orange-500'">
<i class="fas fa-trending-up"></i>
<span x-text="`+${item.growth}%`"></span>
</div>
</div>
<div x-show="item.description" class="mb-4">
<p class="text-sm"
:class="darkMode ? 'text-gray-300' : 'text-gray-700'"
x-text="item.description"></p>
</div>
<button class="w-full py-2 px-4 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105"
:class="darkMode
? 'bg-gray-700 text-white border border-gray-600 hover:bg-gray-600'
: 'bg-gray-100 text-gray-700 border border-gray-300 hover:bg-gray-200'">
Erkunden
</button>
</div>
</template>
</div>
<!-- Empty State for Trending -->
<div x-show="!loading && trending.length === 0" class="text-center py-16">
<div class="mb-6">
<i class="fas fa-trending-up text-6xl mb-4"
:class="darkMode ? 'text-gray-600' : 'text-gray-300'"></i>
</div>
<h3 class="text-2xl font-bold mb-2"
:class="darkMode ? 'text-white' : 'text-gray-900'">
Noch nichts im Trend
</h3>
<p class="text-lg"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">
Sei der Erste, der etwas Trending macht!
</p>
</div>
</div>
</div>
<!-- Quick Actions Floating Button -->
<div class="fixed bottom-6 right-6 z-40">
<div class="relative group">
<!-- Main FAB -->
<button class="w-14 h-14 rounded-full bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-110 focus:outline-none focus:ring-2 focus:ring-purple-500/50">
<i class="fas fa-plus text-xl"></i>
</button>
<!-- Action Menu -->
<div class="absolute bottom-16 right-0 opacity-0 group-hover:opacity-100 transition-all duration-300 transform scale-95 group-hover:scale-100 space-y-2">
<a href="{{ url_for('social_feed') }}"
class="flex items-center justify-center w-12 h-12 rounded-full bg-blue-500 text-white shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-110"
title="Zum Feed">
<i class="fas fa-stream"></i>
</a>
<button class="flex items-center justify-center w-12 h-12 rounded-full bg-green-500 text-white shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-110"
title="Post erstellen">
<i class="fas fa-edit"></i>
</button>
</div>
</div>
</div>
</div>
<script>
// Helper function for time formatting
function formatTimeAgo(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'vor wenigen Sekunden';
if (diffInSeconds < 3600) return `vor ${Math.floor(diffInSeconds / 60)} Min`;
if (diffInSeconds < 86400) return `vor ${Math.floor(diffInSeconds / 3600)} Std`;
return `vor ${Math.floor(diffInSeconds / 86400)} Tagen`;
}
</script>
{% endblock %}

327
templates/social/feed.html Normal file
View File

@@ -0,0 +1,327 @@
{% extends "base.html" %}
{% block title %}Feed{% endblock %}
{% block content %}
<div class="min-h-screen"
x-data="{
posts: [],
loading: false,
page: 1,
hasMore: true,
newPostContent: '',
isPosting: false,
async loadPosts() {
if (this.loading || !this.hasMore) return;
this.loading = true;
try {
const response = await fetch(`/api/feed?page=${this.page}&per_page=10`);
const data = await response.json();
if (data.success) {
if (this.page === 1) {
this.posts = data.posts;
} else {
this.posts = [...this.posts, ...data.posts];
}
this.hasMore = data.has_next;
this.page++;
}
} catch (error) {
console.error('Error loading posts:', error);
} finally {
this.loading = false;
}
},
async createPost() {
if (!this.newPostContent.trim() || this.isPosting) return;
this.isPosting = true;
try {
const response = await fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: this.newPostContent
})
});
const data = await response.json();
if (data.success) {
this.posts.unshift(data.post);
this.newPostContent = '';
}
} catch (error) {
console.error('Error creating post:', error);
} finally {
this.isPosting = false;
}
},
async toggleLike(postId, index) {
try {
const response = await fetch(`/api/posts/${postId}/like`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
this.posts[index].like_count = data.like_count;
this.posts[index].is_liked = data.is_liked;
}
} catch (error) {
console.error('Error toggling like:', error);
}
},
formatTimeAgo(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'vor wenigen Sekunden';
if (diffInSeconds < 3600) return `vor ${Math.floor(diffInSeconds / 60)} Min`;
if (diffInSeconds < 86400) return `vor ${Math.floor(diffInSeconds / 3600)} Std`;
return `vor ${Math.floor(diffInSeconds / 86400)} Tagen`;
},
init() {
this.loadPosts();
}
}"
x-init="init()">
<!-- Main Container -->
<div class="max-w-2xl mx-auto px-4 py-6 space-y-6">
<!-- Post Composer -->
<div class="rounded-2xl p-6 shadow-lg border transition-all duration-300"
:class="darkMode
? 'bg-gray-800/90 border-gray-700/50 backdrop-blur-xl'
: 'bg-white/80 border-gray-200/50 backdrop-blur-xl'">
<!-- User Avatar and Input -->
<div class="flex items-start space-x-4">
<div class="flex-shrink-0">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg shadow-md">
{{ current_user.username[0].upper() }}
</div>
</div>
<div class="flex-1">
<textarea
x-model="newPostContent"
@keydown.meta.enter="createPost()"
@keydown.ctrl.enter="createPost()"
class="w-full resize-none border-0 focus:ring-0 text-lg placeholder-gray-400 transition-all duration-200"
:class="darkMode
? 'bg-transparent text-white'
: 'bg-transparent text-gray-900'"
placeholder="Was beschäftigt dich heute?"
rows="3"></textarea>
</div>
</div>
<!-- Post Actions -->
<div class="mt-4 pt-4 border-t flex items-center justify-between"
:class="darkMode ? 'border-gray-700' : 'border-gray-200'">
<div class="flex items-center space-x-4">
<button class="flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-300 hover:bg-gray-700/50'
: 'text-gray-500 hover:bg-gray-100'">
<i class="fas fa-image text-lg"></i>
<span class="text-sm font-medium">Foto</span>
</button>
<button class="flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-300 hover:bg-gray-700/50'
: 'text-gray-500 hover:bg-gray-100'">
<i class="fas fa-brain text-lg"></i>
<span class="text-sm font-medium">Gedanken</span>
</button>
</div>
<button @click="createPost()"
:disabled="!newPostContent.trim() || isPosting"
class="px-6 py-2.5 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
:class="darkMode
? 'bg-gradient-to-r from-purple-600 to-indigo-600 text-white shadow-lg hover:shadow-xl'
: 'bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-md hover:shadow-lg'">
<span x-show="!isPosting">Teilen</span>
<span x-show="isPosting" class="flex items-center space-x-2">
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Poste...</span>
</span>
</button>
</div>
</div>
<!-- Posts Feed -->
<div class="space-y-6">
<!-- Loading State -->
<div x-show="loading && posts.length === 0"
class="text-center py-12">
<div class="inline-flex items-center space-x-3"
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
<svg class="animate-spin h-6 w-6" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-lg">Lade Posts...</span>
</div>
</div>
<!-- Empty State -->
<div x-show="!loading && posts.length === 0"
class="text-center py-16">
<div class="mb-6">
<i class="fas fa-stream text-6xl mb-4"
:class="darkMode ? 'text-gray-600' : 'text-gray-300'"></i>
</div>
<h3 class="text-2xl font-bold mb-2"
:class="darkMode ? 'text-white' : 'text-gray-900'">
Noch keine Posts
</h3>
<p class="text-lg mb-6"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">
Folge anderen Nutzern oder erstelle deinen ersten Post!
</p>
<a href="{{ url_for('discover') }}"
class="inline-flex items-center space-x-2 px-6 py-3 rounded-xl font-semibold transition-all duration-300 bg-gradient-to-r from-purple-500 to-indigo-500 text-white shadow-lg hover:shadow-xl transform hover:scale-105">
<i class="fas fa-compass"></i>
<span>Entdecken</span>
</a>
</div>
<!-- Posts -->
<template x-for="(post, index) in posts" :key="post.id">
<article class="rounded-2xl p-6 shadow-lg border transition-all duration-300 hover:shadow-xl"
:class="darkMode
? 'bg-gray-800/90 border-gray-700/50 backdrop-blur-xl'
: 'bg-white/80 border-gray-200/50 backdrop-blur-xl'">
<!-- Post Header -->
<div class="flex items-center space-x-3 mb-4">
<div class="w-12 h-12 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg shadow-md">
<span x-text="post.author.username.charAt(0).toUpperCase()"></span>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-center space-x-2">
<h4 class="font-semibold truncate"
:class="darkMode ? 'text-white' : 'text-gray-900'"
x-text="post.author.display_name || post.author.username"></h4>
<span x-show="post.author.is_verified"
class="text-blue-500">
<i class="fas fa-check-circle text-sm"></i>
</span>
</div>
<p class="text-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-500'"
x-text="formatTimeAgo(post.created_at)"></p>
</div>
<button class="p-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-400 hover:bg-gray-700/50'
: 'text-gray-400 hover:bg-gray-100'">
<i class="fas fa-ellipsis-h"></i>
</button>
</div>
<!-- Post Content -->
<div class="mb-4">
<p class="text-lg leading-relaxed whitespace-pre-wrap"
:class="darkMode ? 'text-gray-100' : 'text-gray-800'"
x-text="post.content"></p>
</div>
<!-- Post Actions -->
<div class="flex items-center justify-between pt-4 border-t"
:class="darkMode ? 'border-gray-700' : 'border-gray-200'">
<div class="flex items-center space-x-6">
<button @click="toggleLike(post.id, index)"
class="flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200 group"
:class="post.is_liked
? (darkMode ? 'text-red-400 bg-red-500/10' : 'text-red-500 bg-red-50')
: (darkMode ? 'text-gray-400 hover:bg-gray-700/50' : 'text-gray-500 hover:bg-gray-100')">
<i class="fas fa-heart transition-transform duration-200 group-hover:scale-110"
:class="post.is_liked ? 'fas' : 'far'"></i>
<span class="text-sm font-medium" x-text="post.like_count || 0"></span>
</button>
<button class="flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-400 hover:bg-gray-700/50'
: 'text-gray-500 hover:bg-gray-100'">
<i class="far fa-comment"></i>
<span class="text-sm font-medium" x-text="post.comment_count || 0"></span>
</button>
<button class="flex items-center space-x-2 px-3 py-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-400 hover:bg-gray-700/50'
: 'text-gray-500 hover:bg-gray-100'">
<i class="fas fa-share"></i>
<span class="text-sm font-medium" x-text="post.share_count || 0"></span>
</button>
</div>
<button class="p-2 rounded-lg transition-all duration-200"
:class="darkMode
? 'text-gray-400 hover:bg-gray-700/50'
: 'text-gray-400 hover:bg-gray-100'">
<i class="far fa-bookmark"></i>
</button>
</div>
</article>
</template>
</div>
<!-- Load More Button -->
<div x-show="hasMore && !loading && posts.length > 0"
class="text-center py-6">
<button @click="loadPosts()"
class="px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105"
:class="darkMode
? 'bg-gray-800 text-white border border-gray-700 hover:bg-gray-700'
: 'bg-white text-gray-700 border border-gray-200 hover:bg-gray-50'"
:disabled="loading">
<span x-show="!loading">Mehr laden</span>
<span x-show="loading" class="flex items-center space-x-2">
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Lade...</span>
</span>
</button>
</div>
<!-- Loading More Posts -->
<div x-show="loading && posts.length > 0"
class="text-center py-6">
<div class="inline-flex items-center space-x-3"
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
<svg class="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>Lade weitere Posts...</span>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,381 @@
{% extends "base.html" %}
{% block title %}Benachrichtigungen - SysTades{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-6">
<div class="flex items-center justify-between">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">🔔 Benachrichtigungen</h1>
<button id="mark-all-read" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
Alle als gelesen markieren
</button>
</div>
</div>
<!-- Filter Tabs -->
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg mb-6">
<div class="border-b border-gray-200 dark:border-gray-600">
<nav class="flex space-x-8 px-6">
<button class="filter-btn active py-4 px-2 border-b-2 border-blue-500 font-medium text-blue-600 dark:text-blue-400" data-filter="all">
📥 Alle
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="unread">
🔴 Ungelesen
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="likes">
❤️ Likes
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="comments">
💬 Kommentare
</button>
<button class="filter-btn py-4 px-2 border-b-2 border-transparent font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200" data-filter="follows">
👥 Follows
</button>
</nav>
</div>
</div>
<!-- Notifications Container -->
<div id="notifications-container" class="space-y-4">
<!-- Notifications werden hier geladen -->
</div>
<!-- Load More Button -->
<div class="text-center mt-8">
<button id="load-more" class="px-6 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors">
Mehr laden
</button>
</div>
</div>
<script>
class NotificationCenter {
constructor() {
this.currentFilter = 'all';
this.currentPage = 1;
this.isLoading = false;
this.hasMore = true;
this.initializeEventListeners();
this.loadNotifications();
}
initializeEventListeners() {
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const filter = e.target.dataset.filter;
this.switchFilter(filter);
});
});
// Mark all as read
document.getElementById('mark-all-read').addEventListener('click', () => {
this.markAllAsRead();
});
// Load more
document.getElementById('load-more').addEventListener('click', () => {
this.loadMoreNotifications();
});
}
switchFilter(filter) {
this.currentFilter = filter;
this.currentPage = 1;
this.hasMore = true;
// Update filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
btn.classList.add('border-transparent', 'text-gray-500', 'dark:text-gray-400');
});
const activeBtn = document.querySelector(`[data-filter="${filter}"]`);
activeBtn.classList.add('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
activeBtn.classList.remove('border-transparent', 'text-gray-500', 'dark:text-gray-400');
this.loadNotifications();
}
async loadNotifications() {
if (this.isLoading) return;
this.isLoading = true;
try {
const params = new URLSearchParams({
page: this.currentPage,
per_page: 20,
filter: this.currentFilter
});
const response = await fetch(`/api/social/notifications?${params}`);
const result = await response.json();
if (result.success) {
if (this.currentPage === 1) {
document.getElementById('notifications-container').innerHTML = '';
}
this.renderNotifications(result.notifications);
this.hasMore = result.has_more;
this.updateLoadMoreButton();
} else {
this.showMessage('Fehler beim Laden der Benachrichtigungen', 'error');
}
} catch (error) {
console.error('Error loading notifications:', error);
this.showMessage('Fehler beim Laden der Benachrichtigungen', 'error');
} finally {
this.isLoading = false;
}
}
async loadMoreNotifications() {
if (!this.hasMore || this.isLoading) return;
this.currentPage++;
await this.loadNotifications();
}
renderNotifications(notifications) {
const container = document.getElementById('notifications-container');
if (notifications.length === 0 && this.currentPage === 1) {
container.innerHTML = `
<div class="text-center py-12">
<div class="text-6xl mb-4">📭</div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Keine Benachrichtigungen</h3>
<p class="text-gray-600 dark:text-gray-300">
${this.currentFilter === 'unread' ? 'Alle Benachrichtigungen sind gelesen!' : 'Hier werden deine Benachrichtigungen angezeigt.'}
</p>
</div>
`;
return;
}
notifications.forEach(notification => {
const notificationElement = this.createNotificationElement(notification);
container.appendChild(notificationElement);
});
}
createNotificationElement(notification) {
const element = document.createElement('div');
element.className = `notification-item bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 ${
!notification.is_read ? 'border-l-4 border-blue-500' : ''
}`;
element.dataset.notificationId = notification.id;
const typeIcons = {
'like': '❤️',
'comment': '💬',
'follow': '👥',
'mention': '📢',
'system': '🔔'
};
element.innerHTML = `
<div class="flex items-start space-x-4">
<div class="flex-shrink-0">
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white text-xl">
${typeIcons[notification.type] || '🔔'}
</div>
</div>
<div class="flex-1 min-w-0">
<div class="flex items-start justify-between">
<div class="flex-1">
<p class="text-gray-900 dark:text-white font-medium">
${notification.message}
</p>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
${this.formatDate(notification.created_at)}
</p>
</div>
<div class="flex items-center space-x-2 ml-4">
${!notification.is_read ? `
<button class="mark-read-btn px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
data-notification-id="${notification.id}">
Als gelesen markieren
</button>
` : ''}
<div class="relative">
<button class="notification-menu-btn p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
data-notification-id="${notification.id}">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="notification-menu hidden absolute right-0 mt-2 w-48 bg-white dark:bg-gray-700 rounded-md shadow-lg z-10">
<button class="delete-notification-btn block w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600"
data-notification-id="${notification.id}">
Löschen
</button>
</div>
</div>
</div>
</div>
</div>
</div>
`;
// Event listeners für die Buttons
const markReadBtn = element.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.markAsRead(notification.id);
});
}
const menuBtn = element.querySelector('.notification-menu-btn');
const menu = element.querySelector('.notification-menu');
menuBtn.addEventListener('click', (e) => {
e.stopPropagation();
menu.classList.toggle('hidden');
});
const deleteBtn = element.querySelector('.delete-notification-btn');
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.deleteNotification(notification.id);
});
// Click outside to close menu
document.addEventListener('click', () => {
menu.classList.add('hidden');
});
return element;
}
async markAsRead(notificationId) {
try {
const response = await fetch(`/api/social/notifications/${notificationId}/read`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
const element = document.querySelector(`[data-notification-id="${notificationId}"]`);
element.classList.remove('border-l-4', 'border-blue-500');
const markReadBtn = element.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.remove();
}
this.showMessage('Als gelesen markiert', 'success');
} else {
this.showMessage('Fehler beim Markieren', 'error');
}
} catch (error) {
console.error('Error marking as read:', error);
this.showMessage('Fehler beim Markieren', 'error');
}
}
async markAllAsRead() {
try {
const response = await fetch('/api/social/notifications/mark-all-read', {
method: 'POST'
});
const result = await response.json();
if (result.success) {
// Remove all unread indicators
document.querySelectorAll('.notification-item').forEach(item => {
item.classList.remove('border-l-4', 'border-blue-500');
const markReadBtn = item.querySelector('.mark-read-btn');
if (markReadBtn) {
markReadBtn.remove();
}
});
this.showMessage('Alle Benachrichtigungen als gelesen markiert', 'success');
} else {
this.showMessage('Fehler beim Markieren aller Benachrichtigungen', 'error');
}
} catch (error) {
console.error('Error marking all as read:', error);
this.showMessage('Fehler beim Markieren aller Benachrichtigungen', 'error');
}
}
async deleteNotification(notificationId) {
if (!confirm('Diese Benachrichtigung wirklich löschen?')) return;
try {
const response = await fetch(`/api/social/notifications/${notificationId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
const element = document.querySelector(`[data-notification-id="${notificationId}"]`);
element.remove();
this.showMessage('Benachrichtigung gelöscht', 'success');
} else {
this.showMessage('Fehler beim Löschen', 'error');
}
} catch (error) {
console.error('Error deleting notification:', error);
this.showMessage('Fehler beim Löschen', 'error');
}
}
updateLoadMoreButton() {
const loadMoreBtn = document.getElementById('load-more');
if (this.hasMore) {
loadMoreBtn.style.display = 'block';
loadMoreBtn.textContent = this.isLoading ? 'Lädt...' : 'Mehr laden';
loadMoreBtn.disabled = this.isLoading;
} else {
loadMoreBtn.style.display = 'none';
}
}
formatDate(dateString) {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return 'Gerade eben';
if (diffInSeconds < 3600) return `vor ${Math.floor(diffInSeconds / 60)} Min`;
if (diffInSeconds < 86400) return `vor ${Math.floor(diffInSeconds / 3600)} Std`;
if (diffInSeconds < 2592000) return `vor ${Math.floor(diffInSeconds / 86400)} Tagen`;
return date.toLocaleDateString('de-DE');
}
showMessage(message, type) {
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' : 'bg-blue-500 text-white'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
}
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new NotificationCenter();
});
</script>
{% endblock %}

View File

@@ -0,0 +1,668 @@
{% extends "base.html" %}
{% block title %}{{ user.display_name or user.username }} - SysTades{% endblock %}
{% block additional_css %}
<style>
.profile-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
.profile-header {
background: white;
border-radius: 16px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e1e8ed;
position: relative;
overflow: hidden;
}
.profile-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 120px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
z-index: 1;
}
.profile-content {
position: relative;
z-index: 2;
}
.profile-avatar {
width: 120px;
height: 120px;
border-radius: 50%;
background: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
font-weight: bold;
color: #667eea;
margin: 60px auto 20px auto;
border: 4px solid white;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.profile-info {
text-align: center;
color: white;
margin-bottom: 30px;
}
.profile-name {
font-size: 28px;
font-weight: 700;
margin: 0 0 8px 0;
}
.profile-username {
font-size: 18px;
opacity: 0.9;
margin: 0 0 15px 0;
}
.profile-bio {
font-size: 16px;
line-height: 1.5;
opacity: 0.95;
max-width: 600px;
margin: 0 auto;
}
.profile-stats {
display: flex;
justify-content: center;
gap: 40px;
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
margin-top: 25px;
}
.stat-item {
text-align: center;
color: white;
}
.stat-number {
font-size: 24px;
font-weight: 700;
display: block;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
margin-top: 4px;
}
.profile-actions {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 25px;
}
.action-btn {
background: white;
color: #667eea;
border: none;
padding: 12px 24px;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.action-btn:hover {
background: #f0f2f5;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.action-btn.primary {
background: #1877f2;
color: white;
}
.action-btn.primary:hover {
background: #166fe5;
}
.profile-navigation {
background: white;
border-radius: 12px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e1e8ed;
overflow: hidden;
}
.nav-tabs {
display: flex;
border-bottom: 1px solid #e1e8ed;
}
.nav-tab {
flex: 1;
background: none;
border: none;
padding: 15px 20px;
cursor: pointer;
font-weight: 500;
color: #8a8a8a;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.nav-tab:hover {
background: #f8f9fa;
color: #1d2129;
}
.nav-tab.active {
color: #1877f2;
border-bottom: 2px solid #1877f2;
background: #f0f2f5;
}
.profile-content-area {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #e1e8ed;
min-height: 400px;
}
.posts-grid {
padding: 20px;
display: grid;
gap: 20px;
}
.post-card {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
border: 1px solid #e1e8ed;
transition: all 0.3s ease;
}
.post-card:hover {
background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.post-meta {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
color: #8a8a8a;
font-size: 14px;
}
.post-content {
color: #1d2129;
line-height: 1.6;
margin-bottom: 15px;
}
.post-stats {
display: flex;
gap: 20px;
color: #8a8a8a;
font-size: 14px;
}
.post-stat {
display: flex;
align-items: center;
gap: 6px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #8a8a8a;
}
.empty-state h3 {
margin-bottom: 12px;
color: #1d2129;
}
.about-grid {
padding: 20px;
display: grid;
gap: 20px;
}
.about-section {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
border: 1px solid #e1e8ed;
}
.about-section h3 {
margin: 0 0 15px 0;
color: #1d2129;
font-size: 18px;
}
.info-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
color: #1d2129;
}
.info-row i {
color: #1877f2;
width: 20px;
}
/* Responsive */
@media (max-width: 768px) {
.profile-stats {
gap: 20px;
}
.profile-actions {
flex-direction: column;
align-items: center;
}
.nav-tabs {
overflow-x: auto;
}
}
/* Dark Mode */
[data-theme="dark"] .profile-header,
[data-theme="dark"] .profile-navigation,
[data-theme="dark"] .profile-content-area,
[data-theme="dark"] .about-section {
background: #242526;
border-color: #3a3b3c;
color: #e4e6ea;
}
[data-theme="dark"] .post-card {
background: #3a3b3c;
border-color: #4e4f50;
}
[data-theme="dark"] .post-card:hover {
background: #4e4f50;
}
[data-theme="dark"] .nav-tab {
color: #b0b3b8;
}
[data-theme="dark"] .nav-tab:hover {
background: #3a3b3c;
color: #e4e6ea;
}
[data-theme="dark"] .action-btn {
background: #3a3b3c;
color: #e4e6ea;
}
</style>
{% endblock %}
{% block content %}
<div class="profile-container">
<!-- Profile Header -->
<div class="profile-header">
<div class="profile-content">
<div class="profile-avatar">
{{ user.username[0].upper() }}
</div>
<div class="profile-info">
<h1 class="profile-name">{{ user.display_name or user.username }}</h1>
<p class="profile-username">@{{ user.username }}</p>
{% if user.bio %}
<p class="profile-bio">{{ user.bio }}</p>
{% endif %}
</div>
<div class="profile-stats">
<div class="stat-item">
<span class="stat-number">{{ user.post_count }}</span>
<div class="stat-label">Posts</div>
</div>
<div class="stat-item">
<span class="stat-number">{{ user.follower_count }}</span>
<div class="stat-label">Follower</div>
</div>
<div class="stat-item">
<span class="stat-number">{{ user.following_count }}</span>
<div class="stat-label">Folgt</div>
</div>
<div class="stat-item">
<span class="stat-number">{{ user.mindmaps|length if user.mindmaps else 0 }}</span>
<div class="stat-label">Mindmaps</div>
</div>
</div>
{% if user != current_user %}
<div class="profile-actions">
<button
class="action-btn primary"
onclick="followUser({{ user.id }})"
id="followBtn"
>
<i class="fas fa-user-plus"></i>
{% if is_following %}Gefolgt{% else %}Folgen{% endif %}
</button>
<button class="action-btn" onclick="sendMessage({{ user.id }})">
<i class="fas fa-envelope"></i>
Nachricht
</button>
</div>
{% else %}
<div class="profile-actions">
<a href="{{ url_for('settings') }}" class="action-btn">
<i class="fas fa-cog"></i>
Profil bearbeiten
</a>
<a href="{{ url_for('create_mindmap') }}" class="action-btn primary">
<i class="fas fa-plus"></i>
Neue Mindmap
</a>
</div>
{% endif %}
</div>
</div>
<!-- Profile Navigation -->
<div class="profile-navigation">
<div class="nav-tabs">
<button class="nav-tab active" onclick="switchTab('posts')">
<i class="fas fa-th-large"></i>
Posts
</button>
<button class="nav-tab" onclick="switchTab('mindmaps')">
<i class="fas fa-project-diagram"></i>
Mindmaps
</button>
<button class="nav-tab" onclick="switchTab('thoughts')">
<i class="fas fa-lightbulb"></i>
Gedanken
</button>
<button class="nav-tab" onclick="switchTab('about')">
<i class="fas fa-info-circle"></i>
Über
</button>
</div>
</div>
<!-- Profile Content Area -->
<div class="profile-content-area">
<!-- Posts Tab -->
<div id="posts-tab" class="tab-content">
<div class="posts-grid">
{% if posts %}
{% for post in posts %}
<div class="post-card">
<div class="post-meta">
<span><i class="fas fa-clock"></i> {{ post.created_at.strftime('%d.%m.%Y') }}</span>
<span><i class="fas fa-eye"></i> {{ post.view_count }} Aufrufe</span>
</div>
<div class="post-content">
{{ post.content[:300] }}{% if post.content|length > 300 %}...{% endif %}
</div>
<div class="post-stats">
<div class="post-stat">
<i class="fas fa-heart"></i>
<span>{{ post.like_count }}</span>
</div>
<div class="post-stat">
<i class="fas fa-comment"></i>
<span>{{ post.comment_count }}</span>
</div>
<div class="post-stat">
<i class="fas fa-share"></i>
<span>{{ post.share_count or 0 }}</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<h3>Noch keine Posts</h3>
<p>{% if user == current_user %}Du hast noch keine Posts erstellt.{% else %}{{ user.username }} hat noch keine Posts veröffentlicht.{% endif %}</p>
{% if user == current_user %}
<a href="{{ url_for('social_feed') }}" class="action-btn primary" style="margin-top: 15px;">
<i class="fas fa-plus"></i>
Ersten Post erstellen
</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<!-- Mindmaps Tab -->
<div id="mindmaps-tab" class="tab-content" style="display: none;">
<div class="posts-grid">
{% if user.mindmaps %}
{% for mindmap in user.mindmaps %}
<div class="post-card">
<div class="post-meta">
<span><i class="fas fa-clock"></i> {{ mindmap.created_at.strftime('%d.%m.%Y') }}</span>
<span><i class="fas fa-nodes"></i> {{ mindmap.public_nodes|length }} Knoten</span>
</div>
<div class="post-content">
<h4 style="margin: 0 0 10px 0; color: #1877f2;">{{ mindmap.name }}</h4>
<p>{{ mindmap.description or 'Keine Beschreibung verfügbar' }}</p>
</div>
<div class="post-stats">
<a href="{{ url_for('user_mindmap', mindmap_id=mindmap.id) }}" class="action-btn">
<i class="fas fa-eye"></i>
Anzeigen
</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<h3>Keine Mindmaps</h3>
<p>{% if user == current_user %}Du hast noch keine Mindmaps erstellt.{% else %}{{ user.username }} hat noch keine öffentlichen Mindmaps.{% endif %}</p>
{% if user == current_user %}
<a href="{{ url_for('create_mindmap') }}" class="action-btn primary" style="margin-top: 15px;">
<i class="fas fa-plus"></i>
Erste Mindmap erstellen
</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<!-- Thoughts Tab -->
<div id="thoughts-tab" class="tab-content" style="display: none;">
<div class="posts-grid">
{% if user.thoughts %}
{% for thought in user.thoughts[:10] %}
<div class="post-card">
<div class="post-meta">
<span><i class="fas fa-clock"></i> {{ thought.created_at.strftime('%d.%m.%Y') }}</span>
<span><i class="fas fa-tag"></i> {{ thought.branch }}</span>
</div>
<div class="post-content">
<h4 style="margin: 0 0 10px 0; color: #1877f2;">{{ thought.title }}</h4>
<p>{{ thought.abstract or thought.content[:200] }}{% if (thought.abstract or thought.content)|length > 200 %}...{% endif %}</p>
</div>
<div class="post-stats">
<div class="post-stat">
<i class="fas fa-star"></i>
<span>{{ thought.average_rating or 0 }}</span>
</div>
<div class="post-stat">
<i class="fas fa-comment"></i>
<span>{{ thought.comments|length }}</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<h3>Keine Gedanken</h3>
<p>{% if user == current_user %}Du hast noch keine Gedanken geteilt.{% else %}{{ user.username }} hat noch keine Gedanken veröffentlicht.{% endif %}</p>
</div>
{% endif %}
</div>
</div>
<!-- About Tab -->
<div id="about-tab" class="tab-content" style="display: none;">
<div class="about-grid">
<div class="about-section">
<h3>Grundlegende Informationen</h3>
<div class="info-row">
<i class="fas fa-user"></i>
<span>Mitglied seit {{ user.created_at.strftime('%B %Y') }}</span>
</div>
{% if user.location %}
<div class="info-row">
<i class="fas fa-map-marker-alt"></i>
<span>{{ user.location }}</span>
</div>
{% endif %}
{% if user.website %}
<div class="info-row">
<i class="fas fa-globe"></i>
<a href="{{ user.website }}" target="_blank" rel="noopener">{{ user.website }}</a>
</div>
{% endif %}
{% if user.last_login %}
<div class="info-row">
<i class="fas fa-clock"></i>
<span>Zuletzt aktiv: {{ user.last_login.strftime('%d.%m.%Y') }}</span>
</div>
{% endif %}
</div>
<div class="about-section">
<h3>Aktivitätsstatistiken</h3>
<div class="info-row">
<i class="fas fa-chart-line"></i>
<span>{{ user.post_count }} Posts erstellt</span>
</div>
<div class="info-row">
<i class="fas fa-lightbulb"></i>
<span>{{ user.thoughts|length if user.thoughts else 0 }} Gedanken geteilt</span>
</div>
<div class="info-row">
<i class="fas fa-project-diagram"></i>
<span>{{ user.mindmaps|length if user.mindmaps else 0 }} Mindmaps erstellt</span>
</div>
<div class="info-row">
<i class="fas fa-comments"></i>
<span>Aktives Community-Mitglied</span>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block additional_js %}
<script>
// Tab Navigation
function switchTab(tabName) {
// Alle Tabs verstecken
document.querySelectorAll('.tab-content').forEach(tab => {
tab.style.display = 'none';
});
// Alle Tab-Buttons deaktivieren
document.querySelectorAll('.nav-tab').forEach(btn => {
btn.classList.remove('active');
});
// Gewählten Tab anzeigen
document.getElementById(tabName + '-tab').style.display = 'block';
// Gewählten Tab-Button aktivieren
event.target.classList.add('active');
}
// Follow/Unfollow Funktionalität
async function followUser(userId) {
const button = document.getElementById('followBtn');
try {
const response = await fetch('/api/users/' + userId + '/follow', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
if (data.success) {
if (data.action === 'followed') {
button.innerHTML = '<i class="fas fa-user-check"></i> Gefolgt';
button.classList.add('following');
} else {
button.innerHTML = '<i class="fas fa-user-plus"></i> Folgen';
button.classList.remove('following');
}
} else {
alert(data.error && data.error.message ? data.error.message : 'Fehler beim Folgen');
}
} catch (error) {
console.error('Fehler beim Folgen:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuche es erneut.');
}
}
// Nachricht senden (Platzhalter)
function sendMessage(userId) {
alert('Nachrichten-Feature wird bald verfügbar sein!');
}
// URL-Parameter für Tab-Navigation
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const tab = urlParams.get('tab');
if (tab) {
// Tab aus URL aktivieren
const tabButton = document.querySelector('[onclick="switchTab(\'' + tab + '\')"]');
if (tabButton) {
tabButton.click();
}
}
});
</script>
{% endblock %}

View File

@@ -29,8 +29,8 @@ __all__ = [
'delete_user',
'create_admin_user',
# Server management
'run_development_server',
# Server management (imported separately to avoid circular imports)
# 'run_development_server' - available in utils.server module
]
# Import remaining modules that might depend on app
@@ -38,4 +38,4 @@ from .db_fix import fix_database_schema
from .db_rebuild import rebuild_database
from .db_test import test_database_connection, test_models, print_database_stats, run_all_tests
from .user_manager import list_users, create_user, reset_password, delete_user, create_admin_user
from .server import run_development_server
# Removed server import to prevent circular import - access via utils.server directly

Binary file not shown.

View File

@@ -1,17 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import current_app
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import text
import time
def check_db_connection(db):
def check_db_connection(db, app=None):
"""
Überprüft die Datenbankverbindung und versucht ggf. die Verbindung wiederherzustellen
Args:
db: SQLAlchemy-Instanz
app: Flask-App-Instanz (optional, falls nicht im App-Kontext)
Returns:
bool: True, wenn die Verbindung erfolgreich ist, sonst False
@@ -22,7 +22,11 @@ def check_db_connection(db):
while retry_count < max_retries:
try:
# Führe eine einfache Abfrage durch, um die Verbindung zu testen
with current_app.app_context():
if app:
with app.app_context():
db.session.execute(text('SELECT 1'))
else:
# Versuche ohne expliziten App-Kontext (falls bereits im Kontext)
db.session.execute(text('SELECT 1'))
return True
except SQLAlchemyError as e:
@@ -38,42 +42,60 @@ def check_db_connection(db):
db.session.rollback()
except:
pass
except Exception as e:
print(f"Allgemeiner Fehler bei DB-Check: {str(e)}")
retry_count += 1
if retry_count < max_retries:
time.sleep(1)
return False
def initialize_db_if_needed(db, initialize_function=None):
def initialize_db_if_needed(db, initialize_function=None, app=None):
"""
Initialisiert die Datenbank, falls erforderlich
Args:
db: SQLAlchemy-Instanz
initialize_function: Funktion, die aufgerufen wird, um die Datenbank zu initialisieren
app: Flask-App-Instanz (optional, falls nicht im App-Kontext)
Returns:
bool: True, wenn die Datenbank bereit ist, sonst False
"""
# Prüfe die Verbindung
if not check_db_connection(db):
if not check_db_connection(db, app):
return False
# Prüfe, ob die Tabellen existieren
try:
with current_app.app_context():
# Führe eine Testabfrage auf einer Tabelle durch
if app:
with app.app_context():
# Führe eine Testabfrage auf einer Tabelle durch
db.session.execute(text('SELECT COUNT(*) FROM user'))
else:
# Versuche ohne expliziten App-Kontext
db.session.execute(text('SELECT COUNT(*) FROM user'))
except SQLAlchemyError:
# Tabellen existieren nicht, erstelle sie
try:
with current_app.app_context():
if app:
with app.app_context():
db.create_all()
# Rufe die Initialisierungsfunktion auf, falls vorhanden
if initialize_function and callable(initialize_function):
initialize_function()
else:
db.create_all()
# Rufe die Initialisierungsfunktion auf, falls vorhanden
if initialize_function and callable(initialize_function):
initialize_function()
return True
return True
except Exception as e:
print(f"Fehler bei DB-Initialisierung: {str(e)}")
return False
except Exception as e:
print(f"Fehler beim Prüfen der Datenbank-Tabellen: {str(e)}")
return False
return True

792
utils/logger.py Normal file
View File

@@ -0,0 +1,792 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import os
import sys
from datetime import datetime
from functools import wraps
from flask import request, g, current_app
from flask_login import current_user
import traceback
import json
import time
# ANSI Color Codes für farbige Terminal-Ausgabe
class Colors:
# Standard Colors
RESET = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'
# Foreground Colors
BLACK = '\033[30m'
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'
# Bright Colors
BRIGHT_RED = '\033[91m'
BRIGHT_GREEN = '\033[92m'
BRIGHT_YELLOW = '\033[93m'
BRIGHT_BLUE = '\033[94m'
BRIGHT_MAGENTA = '\033[95m'
BRIGHT_CYAN = '\033[96m'
BRIGHT_WHITE = '\033[97m'
# Background Colors
BG_RED = '\033[41m'
BG_GREEN = '\033[42m'
BG_YELLOW = '\033[43m'
BG_BLUE = '\033[44m'
BG_MAGENTA = '\033[45m'
BG_CYAN = '\033[46m'
class LoggerConfig:
"""Konfiguration für das Logging-System"""
LOG_DIR = 'logs'
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB
BACKUP_COUNT = 5
LOG_FORMAT = '%(asctime)s | %(levelname)s | %(name)s | %(message)s'
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
class ColoredFormatter(logging.Formatter):
"""Custom Formatter für farbige Log-Ausgaben mit schönen Emojis"""
LEVEL_COLORS = {
'DEBUG': Colors.BRIGHT_CYAN,
'INFO': Colors.BRIGHT_GREEN,
'WARNING': Colors.BRIGHT_YELLOW,
'ERROR': Colors.BRIGHT_RED,
'CRITICAL': Colors.BG_RED + Colors.BRIGHT_WHITE
}
COMPONENT_COLORS = {
'AUTH': Colors.BLUE,
'API': Colors.GREEN,
'DB': Colors.MAGENTA,
'SOCIAL': Colors.CYAN,
'SYSTEM': Colors.YELLOW,
'ERROR': Colors.RED,
'SECURITY': Colors.BRIGHT_MAGENTA,
'PERFORMANCE': Colors.BRIGHT_BLUE,
'ACTIVITY': Colors.BRIGHT_CYAN
}
# Erweiterte Emoji-Mappings für verschiedene Komponenten und Aktionen
COMPONENT_EMOJIS = {
'AUTH': '🔐',
'API': '🌐',
'DB': '🗄️',
'SOCIAL': '👥',
'SYSTEM': '⚙️',
'ERROR': '💥',
'SECURITY': '🛡️',
'PERFORMANCE': '',
'ACTIVITY': '🎯'
}
# Spezielle Emojis für verschiedene Log-Level
LEVEL_EMOJIS = {
'DEBUG': '🔍',
'INFO': '',
'WARNING': '⚠️',
'ERROR': '',
'CRITICAL': '🚨'
}
# Action-spezifische Emojis
ACTION_EMOJIS = {
'login': '🚪',
'logout': '🚪',
'register': '📝',
'like': '❤️',
'unlike': '💔',
'comment': '💬',
'share': '🔄',
'follow': '',
'unfollow': '',
'bookmark': '🔖',
'unbookmark': '📑',
'post_created': '📝',
'post_deleted': '🗑️',
'upload': '📤',
'download': '📥',
'search': '🔍',
'notification': '🔔',
'message': '💌',
'profile_update': '👤',
'settings': '⚙️',
'admin': '👑',
'backup': '💾',
'restore': '🔄',
'migration': '🚚',
'cache': '',
'email': '📧',
'password_reset': '🔑',
'verification': '',
'ban': '🚫',
'unban': '',
'report': '🚩',
'moderate': '🛡️'
}
def _get_action_emoji(self, message: str) -> str:
"""Ermittelt das passende Emoji basierend auf der Nachricht"""
message_lower = message.lower()
for action, emoji in self.ACTION_EMOJIS.items():
if action in message_lower:
return emoji
return '📝'
def format(self, record):
# Zeitstempel mit schöner Formatierung
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
colored_timestamp = f"{Colors.DIM}{timestamp}{Colors.RESET}"
# Level mit Farbe und Emoji
level_color = self.LEVEL_COLORS.get(record.levelname, Colors.WHITE)
level_emoji = self.LEVEL_EMOJIS.get(record.levelname, '📝')
colored_level = f"{level_color}{level_emoji} {record.levelname:<8}{Colors.RESET}"
# Component mit Farbe und Emoji
component = getattr(record, 'component', 'SYSTEM')
component_color = self.COMPONENT_COLORS.get(component, Colors.WHITE)
component_emoji = self.COMPONENT_EMOJIS.get(component, '📝')
colored_component = f"{component_color}{component_emoji} [{component:<11}]{Colors.RESET}"
# Message mit Action-spezifischem Emoji
message = record.getMessage()
action_emoji = self._get_action_emoji(message)
# User-Info hinzufügen falls verfügbar
user_info = ""
if hasattr(record, 'user') and record.user:
user_info = f" {Colors.BRIGHT_BLUE}👤 {record.user}{Colors.RESET}"
# IP-Info hinzufügen falls verfügbar
ip_info = ""
if hasattr(record, 'ip') and record.ip:
ip_info = f" {Colors.DIM}🌍 {record.ip}{Colors.RESET}"
# Duration-Info hinzufügen falls verfügbar
duration_info = ""
if hasattr(record, 'duration') and record.duration:
if record.duration > 1000:
duration_color = Colors.BRIGHT_RED
duration_emoji = "🐌"
elif record.duration > 500:
duration_color = Colors.BRIGHT_YELLOW
duration_emoji = "⏱️"
else:
duration_color = Colors.BRIGHT_GREEN
duration_emoji = ""
duration_info = f" {duration_color}{duration_emoji} {record.duration:.2f}ms{Colors.RESET}"
# Separator für bessere Lesbarkeit
separator = f"{Colors.DIM}{Colors.RESET}"
# Finale formatierte Nachricht mit schöner Struktur
formatted_message = (
f"{colored_timestamp} {separator} "
f"{colored_level} {separator} "
f"{colored_component} {separator} "
f"{action_emoji} {message}"
f"{user_info}{ip_info}{duration_info}"
)
return formatted_message
class JSONFormatter(logging.Formatter):
"""JSON-Formatter für strukturierte Logs"""
def format(self, record):
log_entry = {
'timestamp': datetime.now().isoformat(),
'level': record.levelname,
'module': record.module,
'function': record.funcName,
'line': record.lineno,
'message': record.getMessage(),
'logger': record.name
}
# Benutzerinformationen hinzufügen - nur im Application Context
try:
from flask import has_app_context, g, current_user
if has_app_context():
if hasattr(g, 'user_id'):
log_entry['user_id'] = g.user_id
elif current_user and hasattr(current_user, 'id') and current_user.is_authenticated:
log_entry['user_id'] = current_user.id
except (ImportError, RuntimeError):
# Flask ist nicht verfügbar oder kein App-Context
pass
# Request-Informationen hinzufügen - nur im Request Context
try:
from flask import has_request_context, request
if has_request_context() and request:
log_entry['request'] = {
'method': getattr(request, 'method', None),
'path': getattr(request, 'path', None),
'remote_addr': getattr(request, 'remote_addr', None),
'user_agent': str(getattr(request, 'user_agent', ''))
}
except (ImportError, RuntimeError):
# Flask ist nicht verfügbar oder kein Request-Context
pass
# Performance-Informationen hinzufügen - nur im Application Context
try:
from flask import has_app_context, g
if has_app_context() and hasattr(g, 'start_time'):
duration = (datetime.now() - g.start_time).total_seconds() * 1000
log_entry['duration_ms'] = round(duration, 2)
except (ImportError, RuntimeError):
# Flask ist nicht verfügbar oder kein App-Context
pass
# Exception-Informationen hinzufügen
if record.exc_info:
log_entry['exception'] = {
'type': record.exc_info[0].__name__,
'message': str(record.exc_info[1]),
'traceback': self.formatException(record.exc_info)
}
return json.dumps(log_entry)
class SocialNetworkLogger:
"""Hauptklasse für das Social Network Logging"""
def __init__(self, name: str = 'SysTades'):
self.name = name
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG)
# Log-Verzeichnis erstellen
os.makedirs(LoggerConfig.LOG_DIR, exist_ok=True)
# Handler nur einmal hinzufügen
if not self.logger.handlers:
self._setup_handlers()
def _setup_handlers(self):
"""Setup für verschiedene Log-Handler"""
# Console Handler mit Farben
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(ColoredFormatter())
self.logger.addHandler(console_handler)
# File Handler für alle Logs
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler(
os.path.join(LoggerConfig.LOG_DIR, 'app.log'),
maxBytes=LoggerConfig.MAX_LOG_SIZE,
backupCount=LoggerConfig.BACKUP_COUNT,
encoding='utf-8'
)
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(
'%(asctime)s | %(levelname)s | %(name)s | %(component)s | %(message)s',
datefmt=LoggerConfig.DATE_FORMAT
)
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)
# Error Handler für nur Fehler
error_handler = RotatingFileHandler(
os.path.join(LoggerConfig.LOG_DIR, 'errors.log'),
maxBytes=LoggerConfig.MAX_LOG_SIZE,
backupCount=LoggerConfig.BACKUP_COUNT,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(file_formatter)
self.logger.addHandler(error_handler)
# API Handler für API-spezifische Logs
api_handler = RotatingFileHandler(
os.path.join(LoggerConfig.LOG_DIR, 'api.log'),
maxBytes=LoggerConfig.MAX_LOG_SIZE,
backupCount=LoggerConfig.BACKUP_COUNT,
encoding='utf-8'
)
api_handler.setLevel(logging.INFO)
api_handler.addFilter(lambda record: hasattr(record, 'component') and record.component == 'API')
api_handler.setFormatter(file_formatter)
self.logger.addHandler(api_handler)
def _log_with_context(self, level: str, message: str, component: str = 'SYSTEM', **kwargs):
"""Log mit erweiterten Kontext-Informationen"""
extra = {'component': component}
# User-Info hinzufügen
if 'user' in kwargs:
extra['user'] = kwargs['user']
# IP-Info hinzufügen
if 'ip' in kwargs:
extra['ip'] = kwargs['ip']
# Duration-Info hinzufügen
if 'duration' in kwargs:
extra['duration'] = kwargs['duration']
# Weitere Context-Daten
extra.update({k: v for k, v in kwargs.items() if k not in ['user', 'ip', 'duration']})
getattr(self.logger, level.lower())(message, extra=extra)
def debug(self, message: str, component: str = 'SYSTEM', **kwargs):
"""Debug-Level Logging mit erweiterten Infos"""
self._log_with_context('DEBUG', message, component, **kwargs)
def info(self, message: str, component: str = 'SYSTEM', **kwargs):
"""Info-Level Logging mit erweiterten Infos"""
self._log_with_context('INFO', message, component, **kwargs)
def warning(self, message: str, component: str = 'SYSTEM', **kwargs):
"""Warning-Level Logging mit erweiterten Infos"""
self._log_with_context('WARNING', message, component, **kwargs)
def error(self, message: str, component: str = 'ERROR', **kwargs):
"""Error-Level Logging mit erweiterten Infos"""
self._log_with_context('ERROR', message, component, **kwargs)
def critical(self, message: str, component: str = 'ERROR', **kwargs):
"""Critical-Level Logging mit erweiterten Infos"""
self._log_with_context('CRITICAL', message, component, **kwargs)
# Erweiterte spezielle Logging-Methoden für Social Network
def auth_success(self, username: str, ip: str = None, method: str = 'password'):
"""Erfolgreiche Authentifizierung mit Details"""
message = f"Benutzer '{username}' erfolgreich angemeldet"
if method != 'password':
message += f" (Methode: {method})"
self.info(message, 'AUTH', user=username, ip=ip)
def auth_failure(self, username: str, ip: str = None, reason: str = None, method: str = 'password'):
"""Fehlgeschlagene Authentifizierung mit Details"""
message = f"Anmeldung fehlgeschlagen für '{username}'"
if reason:
message += f" - Grund: {reason}"
if method != 'password':
message += f" (Methode: {method})"
self.warning(message, 'AUTH', user=username, ip=ip)
def user_action(self, username: str, action: str, details: str = None, target: str = None):
"""Erweiterte Benutzer-Aktion mit mehr Details"""
message = f"{username}: {action}"
if target:
message += f"{target}"
if details:
message += f" ({details})"
self.info(message, 'ACTIVITY', user=username)
def api_request(self, method: str, endpoint: str, user: str = None, status: int = None, duration: float = None, size: int = None):
"""Erweiterte API Request Logging"""
message = f"{method} {endpoint}"
# Status-spezifische Emojis und Farben
if status:
if status >= 500:
message = f"Server Error: {message}"
component = 'ERROR'
elif status >= 400:
message = f"Client Error: {message}"
component = 'API'
elif status >= 300:
message = f"Redirect: {message}"
component = 'API'
else:
message = f"Success: {message}"
component = 'API'
else:
component = 'API'
# Zusätzliche Infos
extras = {}
if user:
extras['user'] = user
if duration:
extras['duration'] = duration * 1000 # Convert to ms
if size:
message += f" ({self._format_bytes(size)})"
if status and status >= 400:
self.warning(message, component, **extras)
else:
self.info(message, component, **extras)
def database_operation(self, operation: str, table: str, success: bool = True, details: str = None, affected_rows: int = None):
"""Erweiterte Datenbank-Operation Logging"""
message = f"DB {operation.upper()} auf '{table}'"
if affected_rows is not None:
message += f" ({affected_rows} Zeilen)"
if details:
message += f" - {details}"
if success:
self.info(message, 'DB')
else:
self.error(message, 'DB')
def security_event(self, event: str, user: str = None, ip: str = None, severity: str = 'warning', details: str = None):
"""Erweiterte Sicherheitsereignis Logging"""
message = f"Security Event: {event}"
if details:
message += f" - {details}"
extras = {}
if user:
extras['user'] = user
if ip:
extras['ip'] = ip
if severity == 'critical':
self.critical(message, 'SECURITY', **extras)
elif severity == 'error':
self.error(message, 'SECURITY', **extras)
else:
self.warning(message, 'SECURITY', **extras)
def performance_metric(self, metric_name: str, value: float, unit: str = 'ms', threshold: dict = None):
"""Erweiterte Performance-Metrik Logging"""
message = f"Performance: {metric_name} = {value}{unit}"
# Threshold-basierte Bewertung
if threshold and unit == 'ms':
if value > threshold.get('critical', 2000):
self.critical(message, 'PERFORMANCE', duration=value)
elif value > threshold.get('warning', 1000):
self.warning(message, 'PERFORMANCE', duration=value)
else:
self.info(message, 'PERFORMANCE', duration=value)
else:
self.info(message, 'PERFORMANCE')
def social_interaction(self, user: str, action: str, target: str, target_type: str = 'post', target_user: str = None):
"""Erweiterte Social Media Interaktion Logging"""
message = f"{user} {action} {target_type}"
if target_user and target_user != user:
message += f" von {target_user}"
message += f" (ID: {target})"
self.info(message, 'SOCIAL', user=user)
def system_startup(self, version: str = None, environment: str = None, port: int = None):
"""Erweiterte System-Start Logging"""
message = "🚀 SysTades Social Network gestartet"
if version:
message += f" (v{version})"
if environment:
message += f" in {environment} Umgebung"
if port:
message += f" auf Port {port}"
self.info(message, 'SYSTEM')
def system_shutdown(self, reason: str = None, uptime: float = None):
"""Erweiterte System-Shutdown Logging"""
message = "🛑 SysTades Social Network beendet"
if uptime:
message += f" (Laufzeit: {self._format_duration(uptime)})"
if reason:
message += f" - Grund: {reason}"
self.info(message, 'SYSTEM')
def file_operation(self, operation: str, filename: str, success: bool = True, size: int = None, user: str = None):
"""Datei-Operation Logging"""
message = f"File {operation.upper()}: {filename}"
if size:
message += f" ({self._format_bytes(size)})"
extras = {}
if user:
extras['user'] = user
if success:
self.info(message, 'SYSTEM', **extras)
else:
self.error(message, 'SYSTEM', **extras)
def cache_operation(self, operation: str, key: str, hit: bool = None, size: int = None):
"""Cache-Operation Logging"""
message = f"Cache {operation.upper()}: {key}"
if hit is not None:
message += f" ({'HIT' if hit else 'MISS'})"
if size:
message += f" ({self._format_bytes(size)})"
self.debug(message, 'SYSTEM')
def email_sent(self, recipient: str, subject: str, success: bool = True, error: str = None):
"""E-Mail Versand Logging"""
message = f"E-Mail an {recipient}: '{subject}'"
if not success and error:
message += f" - Fehler: {error}"
if success:
self.info(message, 'SYSTEM')
else:
self.error(message, 'SYSTEM')
def _format_bytes(self, bytes_count: int) -> str:
"""Formatiert Byte-Anzahl in lesbare Form"""
for unit in ['B', 'KB', 'MB', 'GB']:
if bytes_count < 1024.0:
return f"{bytes_count:.1f}{unit}"
bytes_count /= 1024.0
return f"{bytes_count:.1f}TB"
def _format_duration(self, seconds: float) -> str:
"""Formatiert Dauer in lesbare Form"""
if seconds < 60:
return f"{seconds:.1f}s"
elif seconds < 3600:
return f"{seconds/60:.1f}min"
else:
return f"{seconds/3600:.1f}h"
def exception(self, exc: Exception, context: str = None, user: str = None):
"""Erweiterte Exception Logging mit mehr Details"""
message = f"Exception: {type(exc).__name__}: {str(exc)}"
if context:
message = f"{context} - {message}"
# Stack-Trace hinzufügen
stack_trace = traceback.format_exc()
message += f"\n{stack_trace}"
extras = {}
if user:
extras['user'] = user
self.error(message, 'ERROR', **extras)
def log_execution_time(component: str = 'SYSTEM'):
"""Decorator für Ausführungszeit-Logging"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger = SocialNetworkLogger()
start_time = time.time()
try:
result = func(*args, **kwargs)
execution_time = (time.time() - start_time) * 1000
logger.performance_metric(f"{func.__name__} Ausführungszeit", execution_time, 'ms')
return result
except Exception as e:
execution_time = (time.time() - start_time) * 1000
logger.exception(e, f"Fehler in {func.__name__} nach {execution_time:.2f}ms")
raise
return wrapper
return decorator
def log_api_call(func):
"""Decorator für API-Call Logging"""
@wraps(func)
def wrapper(*args, **kwargs):
from flask import request, current_user
logger = SocialNetworkLogger()
start_time = time.time()
# Request-Informationen sammeln
method = request.method
endpoint = request.endpoint or request.path
user = current_user.username if hasattr(current_user, 'username') and current_user.is_authenticated else 'Anonymous'
try:
result = func(*args, **kwargs)
duration = time.time() - start_time
# Status-Code ermitteln
status = getattr(result, 'status_code', 200) if hasattr(result, 'status_code') else 200
logger.api_request(method, endpoint, user, status, duration)
return result
except Exception as e:
duration = time.time() - start_time
logger.api_request(method, endpoint, user, 500, duration)
logger.exception(e, f"API-Fehler in {endpoint}")
raise
return wrapper
def performance_monitor(operation_name: str = None):
"""Erweiterte Decorator für Performance-Monitoring mit schönen Logs"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
logger = SocialNetworkLogger()
start_time = time.time()
op_name = operation_name or func.__name__
# User-Info ermitteln falls verfügbar
user = None
try:
from flask import current_user
if hasattr(current_user, 'username') and current_user.is_authenticated:
user = current_user.username
except:
pass
try:
result = func(*args, **kwargs)
duration = (time.time() - start_time) * 1000
# Performance-Kategorisierung mit Emojis
if duration > 2000:
logger.critical(f"Kritisch langsame Operation: {op_name}", 'PERFORMANCE',
user=user, duration=duration)
elif duration > 1000:
logger.warning(f"Langsame Operation: {op_name}", 'PERFORMANCE',
user=user, duration=duration)
elif duration > 500:
logger.info(f"Mäßige Operation: {op_name}", 'PERFORMANCE',
user=user, duration=duration)
else:
logger.debug(f"Schnelle Operation: {op_name}", 'PERFORMANCE',
user=user, duration=duration)
return result
except Exception as e:
duration = (time.time() - start_time) * 1000
logger.error(f"Fehler in Operation: {op_name} nach {duration:.2f}ms", 'PERFORMANCE',
user=user, duration=duration)
logger.exception(e, f"Performance Monitor - {op_name}", user=user)
raise
return wrapper
return decorator
def log_user_activity(activity_name: str):
"""Erweiterte Decorator für User-Activity Logging mit Details"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
from flask import current_user, request
logger = SocialNetworkLogger()
start_time = time.time()
# User und Request-Info sammeln
username = 'Anonymous'
ip = None
user_agent = None
try:
if hasattr(current_user, 'username') and current_user.is_authenticated:
username = current_user.username
if request:
ip = request.remote_addr
user_agent = str(request.user_agent)[:100] # Begrenzen
except:
pass
try:
result = func(*args, **kwargs)
duration = (time.time() - start_time) * 1000
# Erfolgreiche Aktivität loggen
details = f"Erfolgreich in {duration:.2f}ms"
if user_agent:
details += f" (Browser: {user_agent.split('/')[0] if '/' in user_agent else user_agent})"
logger.user_action(username, activity_name, details=details)
return result
except Exception as e:
duration = (time.time() - start_time) * 1000
logger.error(f"Fehler in User-Activity '{activity_name}' für {username} nach {duration:.2f}ms: {str(e)}",
'ACTIVITY', user=username, ip=ip, duration=duration)
logger.exception(e, f"User Activity - {activity_name}", user=username)
raise
return wrapper
return decorator
# Globale Logger-Instanz
social_logger = SocialNetworkLogger()
def get_logger(name: str = None) -> SocialNetworkLogger:
"""Factory-Funktion für Logger-Instanzen"""
if name:
return SocialNetworkLogger(name)
return social_logger
# Convenience-Funktionen für häufige Log-Operationen
def log_user_login(username: str, ip: str = None, success: bool = True):
"""Shortcut für Login-Logging"""
if success:
social_logger.auth_success(username, ip)
else:
social_logger.auth_failure(username, ip)
def log_user_action(username: str, action: str, details: str = None):
"""Shortcut für Benutzer-Aktionen"""
social_logger.user_action(username, action, details)
def log_social_action(user: str, action: str, target: str, target_type: str = 'post'):
"""Shortcut für Social Media Aktionen"""
social_logger.social_interaction(user, action, target, target_type)
def log_error(message: str, exception: Exception = None):
"""Shortcut für Error-Logging"""
if exception:
social_logger.exception(exception, message)
else:
social_logger.error(message)
def log_performance(metric_name: str, value: float, unit: str = 'ms'):
"""Shortcut für Performance-Logging"""
social_logger.performance_metric(metric_name, value, unit)
# Setup-Funktion für initiale Konfiguration
def setup_logging(app=None, log_level: str = 'INFO'):
"""Setup-Funktion für die Flask-App"""
if app:
# Flask App Logging konfigurieren
app.logger.handlers.clear()
app.logger.addHandler(social_logger.logger.handlers[0]) # Console Handler
app.logger.setLevel(getattr(logging, log_level.upper()))
# System-Start loggen
social_logger.system_startup()
return social_logger
if __name__ == "__main__":
# Test des Logging-Systems
logger = SocialNetworkLogger()
logger.info("🧪 Teste das Logging-System")
logger.auth_success("testuser", "192.168.1.1")
logger.user_action("testuser", "Post erstellt", "Neuer Gedanke geteilt")
logger.social_interaction("user1", "like", "post_123")
logger.api_request("GET", "/api/social/posts", "testuser", 200, 0.045)
logger.database_operation("INSERT", "social_posts", True, "Neuer Post gespeichert")
logger.performance_metric("Seitenladezeit", 1234.5)
logger.warning("⚠️ Test-Warnung")
logger.error("❌ Test-Fehler")
logger.debug("🔍 Debug-Information")
print(f"\n{Colors.BRIGHT_GREEN}✅ Logging-System erfolgreich getestet!{Colors.RESET}")
print(f"{Colors.CYAN}📁 Logs gespeichert in: {LoggerConfig.LOG_DIR}/{Colors.RESET}")

View File

@@ -9,11 +9,22 @@ from datetime import datetime
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, parent_dir)
from app import app
# Import models direkt, app wird lazy geladen
from models import db, User
def get_app():
"""Lazy loading der Flask app um zirkuläre Imports zu vermeiden"""
try:
from flask import current_app
return current_app
except RuntimeError:
# Fallback wenn kein app context existiert
from app import app
return app
def list_users():
"""List all users in the database."""
app = get_app()
with app.app_context():
try:
users = User.query.all()
@@ -37,6 +48,7 @@ def list_users():
def create_user(username, email, password, is_admin=False):
"""Create a new user in the database."""
app = get_app()
with app.app_context():
try:
# Check if user already exists
@@ -73,6 +85,7 @@ def create_user(username, email, password, is_admin=False):
def reset_password(username, new_password):
"""Reset password for a user."""
app = get_app()
with app.app_context():
try:
user = User.query.filter_by(username=username).first()
@@ -93,6 +106,7 @@ def reset_password(username, new_password):
def delete_user(username):
"""Delete a user from the database."""
app = get_app()
with app.app_context():
try:
user = User.query.filter_by(username=username).first()