complete p3.14 rewrite
This commit is contained in:
@@ -1,174 +1,204 @@
|
|||||||
import asyncio
|
import time
|
||||||
|
import jwt
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
import configparser
|
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 import Flask, request, jsonify
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read('config.ini')
|
config.read("config.ini")
|
||||||
|
|
||||||
REGISTER_AUTH = config.get('AUTH', 'REGISTER_AUTH', fallback=None)
|
REGISTER_AUTH = config.get("AUTH", "REGISTER_AUTH", fallback=None)
|
||||||
MANAGE_AUTH = config.get('AUTH', 'MANAGE_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')
|
|
||||||
|
|
||||||
|
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!")
|
raise ValueError("Fehlende Pflichtfelder in der Konfiguration!")
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
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)
|
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):
|
class DeviceToken(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
device_token = db.Column(db.String(256), nullable=False, unique=True)
|
|
||||||
|
|
||||||
class NotificationLog(db.Model):
|
device_token = db.Column(
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
db.String(256),
|
||||||
result_uuid = db.Column(db.String(256), nullable=False, unique=True)
|
nullable=False,
|
||||||
sent_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
unique=True
|
||||||
|
|
||||||
|
|
||||||
def create_apns_client():
|
|
||||||
token_credentials = TokenCredentials(
|
|
||||||
auth_key_path=auth_key_path,
|
|
||||||
auth_key_id=auth_key_id,
|
|
||||||
team_id=team_id
|
|
||||||
)
|
)
|
||||||
return APNsClient(credentials=token_credentials, use_sandbox=False)
|
|
||||||
|
|
||||||
def send_notification(device_token, title, subtitle, text, uuid=None):
|
def authenticate(req, required_token):
|
||||||
try:
|
auth_header = req.headers.get("Authorization")
|
||||||
apns_client = create_apns_client()
|
return auth_header == f"Bearer {required_token}"
|
||||||
custom_data = {
|
|
||||||
"subtitle": subtitle,
|
with open(auth_key_path, "r") as f:
|
||||||
"text": text
|
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:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(results_api) as response:
|
jwt_token = generate_apns_token()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"aps": {
|
||||||
|
"alert": {
|
||||||
|
"title": title,
|
||||||
|
"body": body
|
||||||
|
},
|
||||||
|
"sound": "default",
|
||||||
|
"badge": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
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()
|
error_text = await response.text()
|
||||||
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"
|
|
||||||
|
|
||||||
if NotificationLog.query.filter_by(result_uuid=result['uuid']).first():
|
print(
|
||||||
continue
|
f"APNS Fehler {response.status} "
|
||||||
|
f"für {device_token}: {error_text}"
|
||||||
|
)
|
||||||
|
|
||||||
for token in device_tokens:
|
|
||||||
send_notification(token.device_token, title, subtitle, text)
|
|
||||||
|
|
||||||
new_log = NotificationLog(result_uuid=result['uuid'])
|
|
||||||
db.session.add(new_log)
|
|
||||||
db.session.commit()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler beim Abrufen und Senden von Benachrichtigungen: {e}")
|
print(f"Fehler beim Senden: {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
|
|
||||||
|
|
||||||
@app.route("/api/registerDeviceToken", methods=["POST"])
|
@app.route("/api/registerDeviceToken", methods=["POST"])
|
||||||
def register_device_token():
|
def register_device_token():
|
||||||
|
|
||||||
if not authenticate(request, REGISTER_AUTH):
|
if not authenticate(request, REGISTER_AUTH):
|
||||||
return jsonify({"error": "Unauthorized"}), 401
|
return jsonify({
|
||||||
|
"error": "Unauthorized"
|
||||||
|
}), 401
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
data = request.get_json()
|
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'])
|
if not data or "device_token" not in data:
|
||||||
db.session.add(new_device_token)
|
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()
|
db.session.commit()
|
||||||
return jsonify({'message': 'Device Token erfolgreich registriert'}), 200
|
|
||||||
|
return jsonify({
|
||||||
|
"message": "Device Token registriert"
|
||||||
|
}), 200
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': 'Interner Serverfehler', 'details': str(e)}), 500
|
|
||||||
|
|
||||||
@app.route("/showdevicetokens", methods=["GET"])
|
db.session.rollback()
|
||||||
def show_device_tokens():
|
|
||||||
tokens = DeviceToken.query.all()
|
|
||||||
return {"device_tokens": [token.device_token for token in tokens]}
|
|
||||||
|
|
||||||
@app.route("/shownotificationlog", methods=["GET"])
|
return jsonify({
|
||||||
def show_notification_log():
|
"error": "Interner Serverfehler",
|
||||||
logs = NotificationLog.query.all()
|
"details": str(e)
|
||||||
log_data = [{
|
}), 500
|
||||||
'result_uuid': log.result_uuid,
|
|
||||||
'sent_at': log.sent_at.strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
} for log in logs]
|
@app.route("/api/notify", methods=["POST"])
|
||||||
return jsonify({'notification_logs': log_data}), 200
|
def notify():
|
||||||
|
|
||||||
@app.route("/api/deleteAllDeviceTokens", methods=["POST"])
|
|
||||||
def delete_all_device_tokens():
|
|
||||||
if not authenticate(request, MANAGE_AUTH):
|
if not authenticate(request, MANAGE_AUTH):
|
||||||
return jsonify({"error": "Unauthorized"}), 401
|
return jsonify({
|
||||||
try:
|
"error": "Unauthorized"
|
||||||
db.session.query(DeviceToken).delete()
|
}), 401
|
||||||
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
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
severity = data.get("severity")
|
severity = data.get("severity")
|
||||||
notification_text = data.get("notification")
|
notification_text = data.get("notification")
|
||||||
|
|
||||||
if not severity or not notification_text:
|
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 = {
|
severity_icons = {
|
||||||
"info": "",
|
"info": "",
|
||||||
@@ -178,28 +208,98 @@ def custom_notify():
|
|||||||
}
|
}
|
||||||
|
|
||||||
if severity not in severity_icons:
|
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}"
|
title = (
|
||||||
subtitle = "ignore"
|
f"{severity_icons[severity]}"
|
||||||
text = "ignore"
|
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']
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return jsonify({"message": "Benachrichtigungen gesendet"}), 200
|
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
|
||||||
|
|
||||||
except Exception as e:
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
app.run(host='0.0.0.0', port=3000)
|
|
||||||
|
app.run(
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=3000
|
||||||
|
)
|
||||||
+5
-3
@@ -1,6 +1,8 @@
|
|||||||
flask
|
|
||||||
flask_sqlalchemy
|
|
||||||
asyncio
|
asyncio
|
||||||
aiohttp
|
|
||||||
apns2
|
apns2
|
||||||
configparser
|
configparser
|
||||||
|
flask
|
||||||
|
flask_sqlalchemy
|
||||||
|
aiohttp
|
||||||
|
pyjwt
|
||||||
|
cryptography
|
||||||
Reference in New Issue
Block a user