initial commit
This commit is contained in:
42
server/Dockerfile
Normal file
42
server/Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# server/Dockerfile
|
||||
# Produktions-Dockerfile für die Flask-API mit Gunicorn
|
||||
|
||||
# --- Basisimage ---
|
||||
FROM python:3.13-slim
|
||||
|
||||
# --- Arbeitsverzeichnis ---
|
||||
WORKDIR /app
|
||||
|
||||
# --- Systemabhängigkeiten für MariaDB-Client & Locale ---
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
libmariadb-dev-compat libmariadb-dev locales \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# --- Locale konfigurieren ---
|
||||
RUN sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen \
|
||||
&& locale-gen
|
||||
ENV LANG=de_DE.UTF-8 \
|
||||
LC_ALL=de_DE.UTF-8
|
||||
|
||||
# --- Python-Abhängigkeiten ---
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# --- Applikationscode ---
|
||||
COPY server/ /app
|
||||
|
||||
# --- Non-Root User anlegen und Rechte setzen ---
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
RUN groupadd -g ${GROUP_ID} infoscreen_taa \
|
||||
&& useradd -u ${USER_ID} -g ${GROUP_ID} \
|
||||
--shell /bin/bash --create-home infoscreen_taa \
|
||||
&& chown -R infoscreen_taa:infoscreen_taa /app
|
||||
USER infoscreen_taa
|
||||
|
||||
# --- Port für die API exposed ---
|
||||
EXPOSE 8000
|
||||
|
||||
# --- Startbefehl für Gunicorn ---
|
||||
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]
|
||||
46
server/Dockerfile.dev
Normal file
46
server/Dockerfile.dev
Normal file
@@ -0,0 +1,46 @@
|
||||
# Datei: server/Dockerfile.dev
|
||||
# Entwicklungs-Dockerfile für die API (Flask + SQLAlchemy)
|
||||
|
||||
FROM python:3.13-slim
|
||||
|
||||
# Build args für UID/GID
|
||||
ARG USER_ID=1000
|
||||
ARG GROUP_ID=1000
|
||||
|
||||
# Erstelle non-root User
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends locales curl \
|
||||
&& groupadd -g ${GROUP_ID} infoscreen_taa \
|
||||
&& useradd -u ${USER_ID} -g ${GROUP_ID} --shell /bin/bash --create-home infoscreen_taa \
|
||||
&& sed -i 's/# de_DE.UTF-8 UTF-8/de_DE.UTF-8 UTF-8/' /etc/locale.gen \
|
||||
&& locale-gen \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV LANG=de_DE.UTF-8 \
|
||||
LANGUAGE=de_DE:de \
|
||||
LC_ALL=de_DE.UTF-8
|
||||
|
||||
# Arbeitsverzeichnis
|
||||
WORKDIR /app
|
||||
|
||||
# Kopiere nur Requirements für schnellen Rebuild
|
||||
COPY requirements.txt ./
|
||||
COPY requirements-dev.txt ./
|
||||
|
||||
# Installiere Python-Abhängigkeiten (Prod + Dev)
|
||||
RUN pip install --upgrade pip \
|
||||
&& pip install --no-cache-dir -r requirements.txt \
|
||||
&& pip install --no-cache-dir -r requirements-dev.txt
|
||||
|
||||
# Expose Ports für Flask API
|
||||
EXPOSE 8000
|
||||
|
||||
# Setze Env für Dev
|
||||
ENV FLASK_ENV=development
|
||||
ENV ENV_FILE=.env
|
||||
|
||||
# Wechsle zum non-root User
|
||||
USER infoscreen_taa
|
||||
|
||||
# Default Command: Flask Development Server
|
||||
CMD ["flask", "run", "--host=0.0.0.0", "--port=8000"]
|
||||
0
server/__init__.py
Normal file
0
server/__init__.py
Normal file
67
server/init_database.py
Normal file
67
server/init_database.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from sqlalchemy import create_engine, Column, Integer, String, Enum, TIMESTAMP, func, text
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
import enum
|
||||
import bcrypt
|
||||
|
||||
# Basis-Klasse für unsere Modelle
|
||||
Base = declarative_base()
|
||||
|
||||
# Enum zur Definition der möglichen Rollen
|
||||
class UserRole(enum.Enum):
|
||||
user = "user"
|
||||
admin = "admin"
|
||||
superadmin = "superadmin"
|
||||
|
||||
# Definition des User Models (Tabelle)
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(String(50), unique=True, nullable=False)
|
||||
password_hash = Column(String(60), nullable=False) # bcrypt erzeugt einen 60-Zeichen-Hash
|
||||
role = Column(Enum(UserRole), nullable=False, default=UserRole.user)
|
||||
created_at = Column(TIMESTAMP, server_default=func.current_timestamp())
|
||||
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp())
|
||||
|
||||
# Definition des Client Models (Tabelle)
|
||||
class Client(Base):
|
||||
__tablename__ = 'clients'
|
||||
|
||||
# Die UUID wird vom Client erzeugt und muss daher übermittelt werden (kein Default)
|
||||
uuid = Column(String(36), primary_key=True, nullable=False)
|
||||
# Der Hardware-Hash wird ebenfalls direkt vom Client geliefert
|
||||
hardware_hash = Column(String(64), nullable=False)
|
||||
# Spalte für den Standort, der vom Benutzer bei der Anmeldung eingegeben wird
|
||||
location = Column(String(100), nullable=True)
|
||||
# Spalte für die IP-Adresse, die vom Server beim Kontakt ermittelt wird
|
||||
ip_address = Column(String(45), nullable=True)
|
||||
# Speicherung des Registrierungszeitpunkts, wird automatisch gesetzt
|
||||
registration_time = Column(TIMESTAMP, server_default=func.current_timestamp(), nullable=False)
|
||||
# Speicherung des Zeitpunkts der letzten Rückmeldung (Alive-Signal)
|
||||
last_alive = Column(
|
||||
TIMESTAMP,
|
||||
server_default=func.current_timestamp(),
|
||||
onupdate=func.current_timestamp(),
|
||||
nullable=False
|
||||
)
|
||||
|
||||
def main():
|
||||
# Zuerst Verbindung zur MariaDB ohne spezifische Datenbank, um die Datenbank anzulegen.
|
||||
admin_connection_str = 'mysql+pymysql://infoscreen_admin:KqtpM7wmNd$M1Da&mFKs@infoscreen-db/infoscreen_by_taa'
|
||||
admin_engine = create_engine(admin_connection_str, echo=True)
|
||||
|
||||
# Datenbank "infoscreen_by_taa" anlegen, falls sie noch nicht existiert.
|
||||
with admin_engine.connect() as conn:
|
||||
conn.execute(text("CREATE DATABASE IF NOT EXISTS infoscreen_by_taa CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"))
|
||||
|
||||
# Verbindung zur spezifischen Datenbank herstellen.
|
||||
db_connection_str = 'mysql+pymysql://infoscreen_admin:KqtpM7wmNd$M1Da&mFKs@infoscreen-db/infoscreen_by_taa'
|
||||
engine = create_engine(db_connection_str, echo=True)
|
||||
|
||||
# Erstelle die Tabellen 'users' und 'clients'
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
print("Die Tabellen 'users' und 'clients' wurden in der Datenbank 'infoscreen_by_taa' erstellt.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
57
server/init_db.py
Normal file
57
server/init_db.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from sqlalchemy import create_engine, text
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from models import Base, User, UserRole
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import bcrypt
|
||||
|
||||
# .env laden
|
||||
load_dotenv()
|
||||
|
||||
# Konfiguration aus .env
|
||||
DB_USER = os.getenv("DB_USER")
|
||||
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
||||
DB_HOST = os.getenv("DB_HOST")
|
||||
DB_NAME = os.getenv("DB_NAME")
|
||||
|
||||
DEFAULT_ADMIN_USERNAME = os.getenv("DEFAULT_ADMIN_USERNAME")
|
||||
DEFAULT_ADMIN_PASSWORD = os.getenv("DEFAULT_ADMIN_PASSWORD")
|
||||
|
||||
# Erst ohne DB verbinden, um sie ggf. zu erstellen
|
||||
admin_conn_str = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/"
|
||||
admin_engine = create_engine(admin_conn_str, echo=True)
|
||||
|
||||
with admin_engine.connect() as conn:
|
||||
conn.execute(text(f"CREATE DATABASE IF NOT EXISTS {DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"))
|
||||
|
||||
# Jetzt mit Datenbank verbinden
|
||||
db_conn_str = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
|
||||
engine = create_engine(db_conn_str, echo=True)
|
||||
|
||||
# Tabellen anlegen
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
# Session erstellen
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
# Prüfen, ob der User bereits existiert
|
||||
existing_user = session.query(User).filter_by(username=DEFAULT_ADMIN_USERNAME).first()
|
||||
|
||||
if not existing_user:
|
||||
# Passwort hashen
|
||||
hashed_pw = bcrypt.hashpw(DEFAULT_ADMIN_PASSWORD.encode('utf-8'), bcrypt.gensalt())
|
||||
|
||||
# Neuen User anlegen
|
||||
admin_user = User(
|
||||
username=DEFAULT_ADMIN_USERNAME,
|
||||
password_hash=hashed_pw.decode('utf-8'),
|
||||
role=UserRole.admin
|
||||
)
|
||||
session.add(admin_user)
|
||||
session.commit()
|
||||
print(f"Admin-User '{DEFAULT_ADMIN_USERNAME}' wurde erfolgreich angelegt.")
|
||||
else:
|
||||
print(f"Admin-User '{DEFAULT_ADMIN_USERNAME}' existiert bereits.")
|
||||
|
||||
session.close()
|
||||
36
server/init_mariadb.py
Normal file
36
server/init_mariadb.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from sqlalchemy import create_engine, text
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# .env-Datei laden
|
||||
load_dotenv()
|
||||
|
||||
# Schritt 1: Verbindung zur MariaDB herstellen, OHNE eine bestimmte Datenbank
|
||||
DATABASE_URL = f"mysql+pymysql://root:{os.getenv('DB_ROOT_PASSWORD')}@{os.getenv('DB_HOST')}:3306"
|
||||
|
||||
engine = create_engine(DATABASE_URL, isolation_level="AUTOCOMMIT", echo=True)
|
||||
|
||||
db_name = os.getenv("DB_NAME")
|
||||
db_user = os.getenv("DB_USER")
|
||||
db_password = os.getenv("DB_PASSWORD")
|
||||
|
||||
with engine.connect() as connection:
|
||||
# Datenbank erstellen
|
||||
connection.execute(
|
||||
text(f"CREATE DATABASE IF NOT EXISTS `{db_name}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
|
||||
)
|
||||
|
||||
# Benutzer erstellen
|
||||
connection.execute(
|
||||
text(f"CREATE USER IF NOT EXISTS '{db_user}'@'%' IDENTIFIED BY '{db_password}'")
|
||||
)
|
||||
|
||||
# Berechtigungen vergeben
|
||||
connection.execute(
|
||||
text(f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{db_user}'@'%'")
|
||||
)
|
||||
|
||||
# Berechtigungen neu laden
|
||||
connection.execute(text("FLUSH PRIVILEGES"))
|
||||
|
||||
print("✅ Datenbank und Benutzer erfolgreich erstellt.")
|
||||
28
server/models.py
Normal file
28
server/models.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from sqlalchemy import Column, Integer, String, Enum, TIMESTAMP, func
|
||||
from sqlalchemy.orm import declarative_base
|
||||
import enum
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class UserRole(enum.Enum):
|
||||
user = "user"
|
||||
admin = "admin"
|
||||
superadmin = "superadmin"
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(String(50), unique=True, nullable=False)
|
||||
password_hash = Column(String(60), nullable=False)
|
||||
role = Column(Enum(UserRole), nullable=False, default=UserRole.user)
|
||||
created_at = Column(TIMESTAMP, server_default=func.current_timestamp())
|
||||
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp())
|
||||
|
||||
class Client(Base):
|
||||
__tablename__ = 'clients'
|
||||
uuid = Column(String(36), primary_key=True, nullable=False)
|
||||
hardware_hash = Column(String(64), nullable=False)
|
||||
location = Column(String(100), nullable=True)
|
||||
ip_address = Column(String(45), nullable=True)
|
||||
registration_time = Column(TIMESTAMP, server_default=func.current_timestamp(), nullable=False)
|
||||
last_alive = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), nullable=False)
|
||||
148
server/mqtt_multitopic_receiver.py
Normal file
148
server/mqtt_multitopic_receiver.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
import glob
|
||||
from datetime import datetime, timezone
|
||||
# import paho.mqtt.client as mqtt
|
||||
from paho.mqtt import client as mqtt_client
|
||||
import pytz
|
||||
from sqlalchemy import create_engine, func
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from models import Client, Base
|
||||
from helpers.check_folder import ensure_folder_exists
|
||||
|
||||
# Konfiguration
|
||||
MQTT_BROKER = os.getenv("MQTT_BROKER_HOST", "localhost")
|
||||
MQTT_PORT = int(os.getenv("MQTT_BROKER_PORT", 1883))
|
||||
MQTT_USER = os.getenv("MQTT_USER")
|
||||
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD")
|
||||
MQTT_KEEPALIVE = int(os.getenv("MQTT_KEEPALIVE"))
|
||||
DB_USER = os.getenv("DB_USER")
|
||||
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
||||
DB_HOST = os.getenv("DB_HOST")
|
||||
DB_NAME = os.getenv("DB_NAME")
|
||||
|
||||
topics = [
|
||||
("infoscreen/screenshot", 0),
|
||||
("infoscreen/heartbeat", 0),
|
||||
# ... weitere Topics hier
|
||||
]
|
||||
SAVE_DIR = "received_screenshots"
|
||||
MAX_PER_CLIENT = 20
|
||||
|
||||
# Ordner für empfangene Screenshots anlegen
|
||||
ensure_folder_exists(SAVE_DIR)
|
||||
|
||||
# Datenbank konfigurieren (MariaDB)
|
||||
# Ersetze user, password, host und datenbankname entsprechend.
|
||||
DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
|
||||
engine = create_engine(DB_URL, echo=False)
|
||||
Session = sessionmaker(bind=engine)
|
||||
# Falls Tabellen noch nicht existieren
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
def prune_old_screenshots(client_id: str):
|
||||
"""Löscht alte Screenshots, wenn mehr als MAX_PER_CLIENT vorhanden sind."""
|
||||
pattern = os.path.join(SAVE_DIR, f"{client_id}_*.jpg")
|
||||
files = sorted(glob.glob(pattern), key=os.path.getmtime)
|
||||
while len(files) > MAX_PER_CLIENT:
|
||||
oldest = files.pop(0)
|
||||
try:
|
||||
os.remove(oldest)
|
||||
print(f"Altes Bild gelöscht: {oldest}")
|
||||
except OSError as e:
|
||||
print(f"Fehler beim Löschen von {oldest}: {e}")
|
||||
|
||||
|
||||
def handle_screenshot(msg):
|
||||
"""Verarbeitet eingehende Screenshot-Payloads."""
|
||||
try:
|
||||
payload = json.loads(msg.payload.decode("utf-8"))
|
||||
client_id = payload.get("client_id", "unknown")
|
||||
ts = datetime.fromtimestamp(
|
||||
payload.get("timestamp", datetime.now().timestamp())
|
||||
)
|
||||
b64_str = payload["screenshot"]
|
||||
img_data = base64.b64decode(b64_str)
|
||||
|
||||
# Dateiname mit Client-ID und Zeitstempel
|
||||
filename = ts.strftime(f"{client_id}_%Y%m%d_%H%M%S.jpg")
|
||||
filepath = os.path.join(SAVE_DIR, filename)
|
||||
|
||||
# Bild speichern
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(img_data)
|
||||
print(f"Bild gespeichert: {filepath}")
|
||||
|
||||
# Alte Screenshots beschneiden
|
||||
prune_old_screenshots(client_id)
|
||||
|
||||
except Exception as e:
|
||||
print("Fehler beim Verarbeiten der Screenshot-Nachricht:", e)
|
||||
|
||||
def handle_heartbeat(msg):
|
||||
"""Verarbeitet Heartbeat und aktualisiert oder legt Clients an."""
|
||||
session = Session()
|
||||
try:
|
||||
payload = json.loads(msg.payload.decode("utf-8"))
|
||||
uuid = payload.get("client_id")
|
||||
hardware_hash = payload.get("hardware_hash")
|
||||
ip_address = payload.get("ip_address")
|
||||
# Versuche, Client zu finden
|
||||
client = session.query(Client).filter_by(uuid=uuid).first()
|
||||
if client:
|
||||
# Bekannter Client: last_alive und IP aktualisieren
|
||||
client.ip_address = ip_address
|
||||
client.last_alive = func.now()
|
||||
session.commit()
|
||||
print(f"Heartbeat aktualisiert für Client {uuid}")
|
||||
else:
|
||||
# Neuer Client: Location per input abfragen
|
||||
location = input(f"Neuer Client {uuid} gefunden. Bitte Standort eingeben: ")
|
||||
# ip_address = msg._sock.getpeername()[0]
|
||||
new_client = Client(
|
||||
uuid=uuid,
|
||||
hardware_hash=hardware_hash,
|
||||
location=location,
|
||||
ip_address=ip_address
|
||||
)
|
||||
session.add(new_client)
|
||||
session.commit()
|
||||
print(f"Neuer Client {uuid} angelegt mit Standort {location}")
|
||||
except Exception as e:
|
||||
print("Fehler beim Verarbeiten der Heartbeat-Nachricht:", e)
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
# Mapping von Topics auf Handler-Funktionen
|
||||
handlers = {
|
||||
"infoscreen/screenshot": handle_screenshot,
|
||||
"infoscreen/heartbeat": handle_heartbeat,
|
||||
# ... weitere Zuordnungen hier
|
||||
}
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc, properties):
|
||||
print("Verbunden mit Code:", rc)
|
||||
client.subscribe(topics)
|
||||
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
topic = msg.topic
|
||||
if topic in handlers:
|
||||
handlers[topic](msg)
|
||||
else:
|
||||
print(f"Unbekanntes Topic '{topic}', keine Verarbeitung definiert.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = mqtt_client.Client(callback_api_version=mqtt_client.CallbackAPIVersion.VERSION2)
|
||||
client.username_pw_set(MQTT_USER, MQTT_PASSWORD) # <<<< AUTHENTIFIZIERUNG
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
|
||||
client.connect(MQTT_BROKER, MQTT_PORT, keepalive=MQTT_KEEPALIVE)
|
||||
client.loop_forever()
|
||||
57
server/mqtt_receiver.py
Normal file
57
server/mqtt_receiver.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import base64
|
||||
import json
|
||||
from datetime import datetime
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
# MQTT-Konfiguration
|
||||
MQTT_BROKER = "mqtt_broker"
|
||||
MQTT_PORT = 1883
|
||||
MQTT_USER = "infoscreen_taa_user"
|
||||
MQTT_PASSWORD = "infoscreen_taa_MQTT25!"
|
||||
TOPIC_SCREENSHOTS = "infoscreen/screenshot"
|
||||
SAVE_DIR = "received_screenshots"
|
||||
topics = [
|
||||
("infoscreen/screenshot", 0),
|
||||
("infoscreen/heartbeat", 0),
|
||||
# ... weitere Topics hier
|
||||
]
|
||||
|
||||
# Ordner für empfangene Screenshots anlegen
|
||||
os.makedirs(SAVE_DIR, exist_ok=True)
|
||||
|
||||
# Callback, wenn eine Nachricht eintrifft
|
||||
def on_message(client, userdata, msg):
|
||||
try:
|
||||
payload = json.loads(msg.payload.decode('utf-8'))
|
||||
b64_str = payload["screenshot"]
|
||||
img_data = base64.b64decode(b64_str)
|
||||
|
||||
# Dateiname mit Zeitstempel
|
||||
ts = datetime.fromtimestamp(payload.get("timestamp", datetime.now().timestamp()))
|
||||
filename = ts.strftime("screenshot_%Y%m%d_%H%M%S.jpg")
|
||||
filepath = os.path.join(SAVE_DIR, filename)
|
||||
|
||||
# Bild speichern
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(img_data)
|
||||
print(f"Bild gespeichert: {filepath}")
|
||||
except Exception as e:
|
||||
print("Fehler beim Verarbeiten der Nachricht:", e)
|
||||
|
||||
# Callback bei erfolgreicher Verbindung
|
||||
def on_connect(client, userdata, flags, rc, properties):
|
||||
if rc == 0:
|
||||
print("Mit MQTT-Server verbunden.")
|
||||
client.subscribe(TOPIC_SCREENSHOTS, qos=1)
|
||||
else:
|
||||
print(f"Verbindung fehlgeschlagen (Code {rc})")
|
||||
|
||||
if __name__ == "__main__":
|
||||
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||||
client.username_pw_set(MQTT_USER, MQTT_PASSWORD) # <<<< AUTHENTIFIZIERUNG
|
||||
client.on_connect = on_connect
|
||||
client.on_message = on_message
|
||||
|
||||
client.connect(MQTT_BROKER, MQTT_PORT, keepalive=60)
|
||||
client.loop_forever()
|
||||
16
server/nano.7258.save
Normal file
16
server/nano.7258.save
Normal file
@@ -0,0 +1,16 @@
|
||||
# server/wsgi.py
|
||||
|
||||
from flask import Flask, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return jsonify(status="ok")
|
||||
|
||||
# Optional: Test-Route
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello from Infoscreen‐API!"
|
||||
|
||||
# (Weitere Endpunkte, Blueprints, Datenbank-Initialisierung usw. kommen hierher)
|
||||
1
server/requirements-dev.txt
Normal file
1
server/requirements-dev.txt
Normal file
@@ -0,0 +1 @@
|
||||
python-dotenv>=1.1.0
|
||||
6
server/requirements.txt
Normal file
6
server/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
bcrypt>=4.3.0
|
||||
paho-mqtt>=2.1.0
|
||||
PyMySQL>=1.1.1
|
||||
python-dotenv>=1.1.0
|
||||
SQLAlchemy>=2.0.41
|
||||
flask
|
||||
26
server/test_sql.py
Normal file
26
server/test_sql.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import pymysql
|
||||
|
||||
load_dotenv()
|
||||
|
||||
try:
|
||||
connection = pymysql.connect(
|
||||
host='localhost',
|
||||
port=3306,
|
||||
user=os.getenv('DB_USER'),
|
||||
password=os.getenv('DB_PASSWORD'),
|
||||
database=os.getenv('DB_NAME')
|
||||
)
|
||||
print("✅ Verbindung erfolgreich!")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT VERSION()")
|
||||
version = cursor.fetchone()
|
||||
print(f"MariaDB Version: {version[0]}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Verbindungsfehler: {e}")
|
||||
finally:
|
||||
if 'connection' in locals():
|
||||
connection.close()
|
||||
16
server/wsgi.py
Normal file
16
server/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# server/wsgi.py
|
||||
|
||||
from flask import Flask, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/health")
|
||||
def health():
|
||||
return jsonify(status="ok")
|
||||
|
||||
# Optional: Test-Route
|
||||
@app.route("/")
|
||||
def index():
|
||||
return "Hello from Infoscreen‐API!"
|
||||
|
||||
# (Weitere Endpunkte, Blueprints, Datenbank-Initialisierung usw. kommen hierher)
|
||||
Reference in New Issue
Block a user