complete p3.14 rewrite

This commit is contained in:
Thies Mueller
2026-06-12 17:08:19 +02:00
parent db270a4e07
commit 8513224a04
2 changed files with 243 additions and 141 deletions
+238 -138
View File
@@ -1,174 +1,204 @@
import asyncio
import time
import jwt
import aiohttp
import asyncio
import configparser
from apns2.client import APNsClient
from apns2.payload import Payload
from apns2.credentials import TokenCredentials
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
config = configparser.ConfigParser()
config.read('config.ini')
config.read("config.ini")
REGISTER_AUTH = config.get('AUTH', 'REGISTER_AUTH', fallback=None)
MANAGE_AUTH = config.get('AUTH', 'MANAGE_AUTH', fallback=None)
SQLALCHEMY_DATABASE_URI = config.get('DATABASE', 'SQLALCHEMY_DATABASE_URI', fallback='sqlite:///device_tokens.db')
auth_key_path = config.get('APNS', 'auth_key_path', fallback='./APNSAuthKey.p8')
auth_key_id = config.get('APNS', 'auth_key_id', fallback=None)
team_id = config.get('APNS', 'team_id', fallback=None)
topic = config.get('APNS', 'topic', fallback=None)
results_api = config.get('API', 'resultsapi', fallback='https://sat-api.tservic.es/api/v1/results')
REGISTER_AUTH = config.get("AUTH", "REGISTER_AUTH", fallback=None)
MANAGE_AUTH = config.get("AUTH", "MANAGE_AUTH", fallback=None)
SQLALCHEMY_DATABASE_URI = config.get(
"DATABASE",
"SQLALCHEMY_DATABASE_URI",
fallback="sqlite:///device_tokens.db"
)
if not all([REGISTER_AUTH, MANAGE_AUTH, auth_key_id, team_id, topic, results_api]):
auth_key_path = config.get(
"APNS",
"auth_key_path",
fallback="./APNSAuthKey.p8"
)
auth_key_id = config.get("APNS", "auth_key_id", fallback=None)
team_id = config.get("APNS", "team_id", fallback=None)
topic = config.get("APNS", "topic", fallback=None)
APNS_URL = "https://api.push.apple.com"
if not all([
REGISTER_AUTH,
MANAGE_AUTH,
auth_key_id,
team_id,
topic
]):
raise ValueError("Fehlende Pflichtfelder in der Konfiguration!")
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
def authenticate(request, required_token):
auth_header = request.headers.get("Authorization")
return auth_header == f"Bearer {required_token}"
class DeviceToken(db.Model):
id = db.Column(db.Integer, primary_key=True)
device_token = db.Column(db.String(256), nullable=False, unique=True)
class NotificationLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
result_uuid = db.Column(db.String(256), nullable=False, unique=True)
sent_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
def create_apns_client():
token_credentials = TokenCredentials(
auth_key_path=auth_key_path,
auth_key_id=auth_key_id,
team_id=team_id
device_token = db.Column(
db.String(256),
nullable=False,
unique=True
)
return APNsClient(credentials=token_credentials, use_sandbox=False)
def send_notification(device_token, title, subtitle, text, uuid=None):
try:
apns_client = create_apns_client()
custom_data = {
"subtitle": subtitle,
"text": text
def authenticate(req, required_token):
auth_header = req.headers.get("Authorization")
return auth_header == f"Bearer {required_token}"
with open(auth_key_path, "r") as f:
APNS_PRIVATE_KEY = f.read()
def generate_apns_token():
return jwt.encode(
{
"iss": team_id,
"iat": int(time.time())
},
APNS_PRIVATE_KEY,
algorithm="ES256",
headers={
"alg": "ES256",
"kid": auth_key_id
}
if uuid:
custom_data["uuid"] = uuid
)
payload = Payload(
alert={"title": title, "body": text},
sound="default",
badge=1,
category="RACE_RESULT",
custom=custom_data
)
apns_client.send_notification(device_token, payload, topic)
except Exception as e:
print(f"Fehler beim Senden der Benachrichtigung: {e}")
async def fetch_results_and_send_notifications():
async def send_notification(
session,
device_token,
title,
body
):
try:
async with aiohttp.ClientSession() as session:
async with session.get(results_api) as response:
if response.status != 200:
raise Exception(f"API Fehler: {response.status}")
results = await response.json()
results = results.get('ergebnisse', {})
if not results:
raise ValueError("Keine Ergebnisse gefunden.")
device_tokens = DeviceToken.query.all()
for result in results.values():
winner_bahn = result[result['winner']]
title = f"{result['title']} Ergebnis: {winner_bahn['boot']} gewinnt mit {winner_bahn['zeit']}"
subtitle = f"{winner_bahn['boot']} gewinnt mit {winner_bahn['zeit']}"
text = "Klicke hier, um die Ergebnisse anzusehen"
jwt_token = generate_apns_token()
if NotificationLog.query.filter_by(result_uuid=result['uuid']).first():
continue
payload = {
"aps": {
"alert": {
"title": title,
"body": body
},
"sound": "default",
"badge": 1
}
}
for token in device_tokens:
send_notification(token.device_token, title, subtitle, text)
headers = {
"authorization": f"bearer {jwt_token}",
"apns-topic": topic,
"apns-push-type": "alert",
"content-type": "application/json"
}
url = f"{APNS_URL}/3/device/{device_token}"
async with session.post(
url,
json=payload,
headers=headers
) as response:
if response.status != 200:
error_text = await response.text()
print(
f"APNS Fehler {response.status} "
f"für {device_token}: {error_text}"
)
new_log = NotificationLog(result_uuid=result['uuid'])
db.session.add(new_log)
db.session.commit()
except Exception as e:
print(f"Fehler beim Abrufen und Senden von Benachrichtigungen: {e}")
@app.route("/api/getresults", methods=["POST"])
def get_results():
if not authenticate(request, MANAGE_AUTH):
return jsonify({"error": "Unauthorized"}), 401
try:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(fetch_results_and_send_notifications())
return jsonify({"message": "Benachrichtigungen gesendet"}), 200
except Exception as e:
return jsonify({'error': 'Interner Serverfehler', 'details': str(e)}), 500
print(f"Fehler beim Senden: {e}")
@app.route("/api/registerDeviceToken", methods=["POST"])
def register_device_token():
if not authenticate(request, REGISTER_AUTH):
return jsonify({"error": "Unauthorized"}), 401
return jsonify({
"error": "Unauthorized"
}), 401
try:
data = request.get_json()
if 'device_token' not in data:
return jsonify({'error': 'device_token fehlt'}), 400
new_device_token = DeviceToken(device_token=data['device_token'])
db.session.add(new_device_token)
if not data or "device_token" not in data:
return jsonify({
"error": "device_token fehlt"
}), 400
device_token = data["device_token"]
existing = DeviceToken.query.filter_by(
device_token=device_token
).first()
if existing:
return jsonify({
"message": "Bereits registriert"
}), 200
db.session.add(
DeviceToken(device_token=device_token)
)
db.session.commit()
return jsonify({'message': 'Device Token erfolgreich registriert'}), 200
return jsonify({
"message": "Device Token registriert"
}), 200
except Exception as e:
return jsonify({'error': 'Interner Serverfehler', 'details': str(e)}), 500
@app.route("/showdevicetokens", methods=["GET"])
def show_device_tokens():
tokens = DeviceToken.query.all()
return {"device_tokens": [token.device_token for token in tokens]}
db.session.rollback()
@app.route("/shownotificationlog", methods=["GET"])
def show_notification_log():
logs = NotificationLog.query.all()
log_data = [{
'result_uuid': log.result_uuid,
'sent_at': log.sent_at.strftime('%Y-%m-%d %H:%M:%S')
} for log in logs]
return jsonify({'notification_logs': log_data}), 200
return jsonify({
"error": "Interner Serverfehler",
"details": str(e)
}), 500
@app.route("/api/notify", methods=["POST"])
def notify():
@app.route("/api/deleteAllDeviceTokens", methods=["POST"])
def delete_all_device_tokens():
if not authenticate(request, MANAGE_AUTH):
return jsonify({"error": "Unauthorized"}), 401
try:
db.session.query(DeviceToken).delete()
db.session.commit()
return jsonify({'message': 'Alle Device Tokens wurden gelöscht'}), 200
except Exception as e:
return jsonify({'error': 'Interner Serverfehler', 'details': str(e)}), 500
@app.route("/api/customnotify", methods=["POST"])
def custom_notify():
if not authenticate(request, MANAGE_AUTH):
return jsonify({"error": "Unauthorized"}), 401
return jsonify({
"error": "Unauthorized"
}), 401
try:
data = request.get_json()
severity = data.get("severity")
notification_text = data.get("notification")
if not severity or not notification_text:
return jsonify({"error": "severity und notification sind erforderlich"}), 400
return jsonify({
"error": (
"severity und notification "
"sind erforderlich"
)
}), 400
severity_icons = {
"info": "",
@@ -178,28 +208,98 @@ def custom_notify():
}
if severity not in severity_icons:
return jsonify({"error": "Ungültige severity"}), 400
return jsonify({
"error": "Ungültige severity"
}), 400
title = f"{severity_icons[severity]}{notification_text}"
subtitle = "ignore"
text = "ignore"
title = (
f"{severity_icons[severity]}"
f"{notification_text}"
)
device_tokens = DeviceToken.query.all()
for token in device_tokens:
send_notification(
token.device_token,
title=title,
subtitle=subtitle,
text=text,
uuid=result['uuid']
)
async def send_all():
async with aiohttp.ClientSession() as session:
tasks = []
for token in DeviceToken.query.all():
tasks.append(
send_notification(
session=session,
device_token=token.device_token,
title=title,
body=notification_text
)
)
await asyncio.gather(*tasks)
asyncio.run(send_all())
return jsonify({
"message": "Benachrichtigungen gesendet"
}), 200
return jsonify({"message": "Benachrichtigungen gesendet"}), 200
except Exception as e:
return jsonify({'error': 'Interner Serverfehler', 'details': str(e)}), 500
return jsonify({
"error": "Interner Serverfehler",
"details": str(e)
}), 500
@app.route("/showdevicetokens", methods=["GET"])
def show_device_tokens():
tokens = DeviceToken.query.all()
return jsonify({
"device_tokens": [
token.device_token
for token in tokens
]
})
@app.route(
"/api/deleteAllDeviceTokens",
methods=["POST"]
)
def delete_all_device_tokens():
if not authenticate(request, MANAGE_AUTH):
return jsonify({
"error": "Unauthorized"
}), 401
try:
db.session.query(DeviceToken).delete()
db.session.commit()
return jsonify({
"message": "Alle Device Tokens gelöscht"
}), 200
except Exception as e:
db.session.rollback()
return jsonify({
"error": "Interner Serverfehler",
"details": str(e)
}), 500
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=3000)
app.run(
host="0.0.0.0",
port=3000
)