This commit is contained in:
root 2025-03-12 13:58:24 +01:00
parent ad5bd4367e
commit 190794b2c1
2 changed files with 87 additions and 3 deletions

25
CLAUDE.md Normal file
View File

@ -0,0 +1,25 @@
# MYP Project Development Guidelines
## Build/Run Commands
- Backend: `cd backend && source venv/bin/activate && python app.py`
- Frontend: `cd packages/reservation-platform && pnpm dev`
- Run tests: `cd backend && python -m unittest development/tests/tests.py`
- Run single test: `cd backend && python -m unittest development.tests.tests.MYPBackendTestCase.test_name`
- Check jobs manually: `cd backend && source venv/bin/activate && flask check-jobs`
- Lint frontend: `cd packages/reservation-platform && pnpm lint`
- Format frontend: `cd packages/reservation-platform && npx @biomejs/biome format --write ./src`
## Code Style
- **Python Backend**:
- Use PEP 8 conventions, 4-space indentation
- Line width: 100 characters max
- Add docstrings to functions and classes
- Error handling: Use try/except with specific exceptions
- Naming: snake_case for functions/variables, PascalCase for classes
- **Frontend (Next.js/TypeScript)**:
- Use Biome for formatting and linting (line width: 120 chars)
- Organize imports automatically with Biome
- Use TypeScript types for all code
- Use React hooks for state management
- Naming: camelCase for functions/variables, PascalCase for components

View File

@ -9,6 +9,8 @@ import json
import logging import logging
import uuid import uuid
import sqlite3 import sqlite3
import threading
import time
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from datetime import timedelta from datetime import timedelta
from PyP100 import PyP100 from PyP100 import PyP100
@ -28,6 +30,7 @@ app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = os.environ.get('FLASK_ENV') == 'production' app.config['SESSION_COOKIE_SECURE'] = os.environ.get('FLASK_ENV') == 'production'
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
app.config['JOB_CHECK_INTERVAL'] = int(os.environ.get('JOB_CHECK_INTERVAL', '60')) # Sekunden
# Steckdosen-Konfiguration # Steckdosen-Konfiguration
TAPO_USERNAME = os.environ.get('TAPO_USERNAME') TAPO_USERNAME = os.environ.get('TAPO_USERNAME')
@ -124,10 +127,19 @@ def init_printers():
if ip_address in existing_ips: if ip_address in existing_ips:
app.logger.info(f"Drucker mit IP {ip_address} existiert bereits in der Datenbank") app.logger.info(f"Drucker mit IP {ip_address} existiert bereits in der Datenbank")
# Setze den Status des existierenden Druckers auf 0 (verfügbar)
socket_id = existing_ips[ip_address]
update_socket(socket_id, status=0)
# Stelle sicher, dass die Steckdose wirklich ausgeschaltet ist
turn_off_socket(ip_address)
app.logger.info(f"Steckdose mit IP {ip_address} wurde beim Start ausgeschaltet")
else: else:
# Neuen Drucker eintragen # Neuen Drucker eintragen
create_socket(name=printer_name, description=description, ip_address=ip_address, status=0) new_socket = create_socket(name=printer_name, description=description, ip_address=ip_address, status=0)
app.logger.info(f"Neuer Drucker angelegt: {printer_name} mit IP {ip_address}") app.logger.info(f"Neuer Drucker angelegt: {printer_name} mit IP {ip_address}")
# Stelle sicher, dass die Steckdose wirklich ausgeschaltet ist
turn_off_socket(ip_address)
app.logger.info(f"Neue Steckdose mit IP {ip_address} wurde beim Start ausgeschaltet")
# Benutzerverwaltung # Benutzerverwaltung
def get_user_by_id(user_id): def get_user_by_id(user_id):
@ -1101,7 +1113,6 @@ def stats():
}) })
# Regelmäßige Überprüfung der Jobs und automatische Abschaltung der Steckdosen # Regelmäßige Überprüfung der Jobs und automatische Abschaltung der Steckdosen
@app.cli.command("check-jobs")
def check_jobs(): def check_jobs():
"""Überprüft abgelaufene Jobs und schaltet Steckdosen automatisch aus.""" """Überprüft abgelaufene Jobs und schaltet Steckdosen automatisch aus."""
with app.app_context(): with app.app_context():
@ -1132,11 +1143,29 @@ def check_jobs():
# Nur wenn es nicht der letzte Versuch war, kurz warten und neu versuchen # Nur wenn es nicht der letzte Versuch war, kurz warten und neu versuchen
if attempt < max_attempts: if attempt < max_attempts:
import time
time.sleep(1) time.sleep(1)
app.logger.info(f"{len(expired_jobs)} abgelaufene Jobs überprüft, {handled_jobs} Steckdosen aktualisiert.") app.logger.info(f"{len(expired_jobs)} abgelaufene Jobs überprüft, {handled_jobs} Steckdosen aktualisiert.")
# Hintergrund-Thread für das Job-Polling
def background_job_checker():
"""Hintergrund-Thread, der regelmäßig abgelaufene Jobs überprüft."""
app.logger.info("Starte Hintergrund-Thread für Job-Überprüfung")
while True:
try:
check_jobs()
except Exception as e:
app.logger.error(f"Fehler im Hintergrund-Thread für Job-Überprüfung: {e}")
# Pause zwischen den Überprüfungen
time.sleep(app.config['JOB_CHECK_INTERVAL'])
# CLI-Befehl für manuelle Ausführung
@app.cli.command("check-jobs")
def cli_check_jobs():
"""CLI-Befehl zur manuellen Überprüfung abgelaufener Jobs."""
check_jobs()
@app.route('/api/job/<job_id>/status', methods=['GET']) @app.route('/api/job/<job_id>/status', methods=['GET'])
def job_status(job_id): def job_status(job_id):
"""Endpunkt zum Überprüfen des Status eines Jobs für Frontend-Polling.""" """Endpunkt zum Überprüfen des Status eines Jobs für Frontend-Polling."""
@ -1280,10 +1309,40 @@ def stats_page():
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('stats.html', current_user=current_user, active_page='stats') return render_template('stats.html', current_user=current_user, active_page='stats')
# Initialisierung und Start des Hintergrund-Threads beim ersten Request
with app.app_context():
# Diese Funktion wird nach dem App-Start aber vor dem ersten Request ausgeführt
@app.before_request
def initialize_background_tasks():
"""Startet den Hintergrund-Thread für Job-Überprüfung beim ersten Request."""
# Überprüfung, ob dieser Handler bereits ausgeführt wurde
if getattr(app, '_job_thread_initialized', False):
return
# Starte den Hintergrund-Thread nur, wenn er noch nicht läuft
for thread in threading.enumerate():
if thread.name == 'job_checker_thread':
app.logger.info("Hintergrund-Thread für Job-Überprüfung läuft bereits")
app._job_thread_initialized = True
return
# Thread starten
job_thread = threading.Thread(target=background_job_checker, daemon=True, name='job_checker_thread')
job_thread.start()
app.logger.info("Hintergrund-Thread für Job-Überprüfung beim ersten Request gestartet")
app._job_thread_initialized = True
# Server starten # Server starten
if __name__ == '__main__': if __name__ == '__main__':
with app.app_context(): with app.app_context():
init_db() init_db()
if PRINTERS: if PRINTERS:
# Initialisiere Drucker und schalte alle Steckdosen beim Start aus
init_printers() init_printers()
# Starte den Hintergrund-Thread für die Job-Überprüfung
job_thread = threading.Thread(target=background_job_checker, daemon=True, name='job_checker_thread')
job_thread.start()
app.logger.info("Hintergrund-Thread für Job-Überprüfung gestartet")
app.run(debug=True, host='0.0.0.0') app.run(debug=True, host='0.0.0.0')