complete p3.14 rewrite
This commit is contained in:
@@ -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
|
||||
)
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
flask
|
||||
flask_sqlalchemy
|
||||
asyncio
|
||||
aiohttp
|
||||
apns2
|
||||
configparser
|
||||
flask
|
||||
flask_sqlalchemy
|
||||
aiohttp
|
||||
pyjwt
|
||||
cryptography
|
||||
Reference in New Issue
Block a user