✨ 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:
1037
COMMON_ERRORS.md
1037
COMMON_ERRORS.md
File diff suppressed because it is too large
Load Diff
552
ROADMAP.md
552
ROADMAP.md
@@ -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.
5
cookies.txt
Normal file
5
cookies.txt
Normal 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
0
instance/logs/app.log
Normal file
0
instance/logs/errors.log
Normal file
0
instance/logs/errors.log
Normal file
0
instance/logs/social.log
Normal file
0
instance/logs/social.log
Normal file
0
logs/api.log
Normal file
0
logs/api.log
Normal file
964
logs/app.log
964
logs/app.log
@@ -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
424
logs/errors.log
Normal 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
308
models.py
@@ -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
22
server.log
Normal file
@@ -0,0 +1,22 @@
|
||||
[2m⏰ 21:58:48.486[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 🚀 SysTades Social Network gestartet
|
||||
[2m⏰ 21:58:48.486[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
|
||||
[2m⏰ 21:58:49.951[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 OpenAI API-Verbindung erfolgreich hergestellt
|
||||
[2m⏰ 21:58:50.122[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [35m🗄️ [DB ][0m [2m│[0m 🚫 Datenbank erfolgreich initialisiert
|
||||
[2m⏰ 21:58:50.132[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [35m🗄️ [DB ][0m [2m│[0m 🚫 Datenbanktabellen erstellt/aktualisiert
|
||||
[2m⏰ 21:58:50.134[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 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)
|
||||
[2m⏰ 21:58:52.225[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 🚀 SysTades Social Network gestartet
|
||||
[2m⏰ 21:58:52.226[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 🚀 SysTades Social Network gestartet (v1.0.0) in development Umgebung auf Port 5000
|
||||
[2m⏰ 21:58:53.848[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 OpenAI API-Verbindung erfolgreich hergestellt
|
||||
[2m⏰ 21:58:53.997[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [35m🗄️ [DB ][0m [2m│[0m 🚫 Datenbank erfolgreich initialisiert
|
||||
[2m⏰ 21:58:54.002[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [35m🗄️ [DB ][0m [2m│[0m 🚫 Datenbanktabellen erstellt/aktualisiert
|
||||
[2m⏰ 21:58:54.006[0m [2m│[0m [92m✅ INFO [0m [2m│[0m [33m⚙️ [SYSTEM ][0m [2m│[0m 📝 Starte Flask-Entwicklungsserver auf http://localhost:5000
|
||||
* Debugger is active!
|
||||
* Debugger PIN: 114-005-893
|
||||
915
static/css/social.css
Normal file
915
static/css/social.css
Normal 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
1133
static/js/social.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
512
templates/social/discover.html
Normal file
512
templates/social/discover.html
Normal 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
327
templates/social/feed.html
Normal 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 %}
|
||||
381
templates/social/notifications.html
Normal file
381
templates/social/notifications.html
Normal 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 %}
|
||||
668
templates/social/profile.html
Normal file
668
templates/social/profile.html
Normal 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 %}
|
||||
@@ -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.
Binary file not shown.
BIN
utils/__pycache__/logger.cpython-311.pyc
Normal file
BIN
utils/__pycache__/logger.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -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
792
utils/logger.py
Normal 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}")
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user