initial commit
This commit is contained in:
174
main.py
Normal file
174
main.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import time
|
||||
import smtplib
|
||||
import yaml
|
||||
import requests
|
||||
import csv
|
||||
import qrcode
|
||||
import io
|
||||
from email.message import EmailMessage
|
||||
from flask import Flask, request, jsonify, abort
|
||||
from jinja2 import Template
|
||||
from functools import wraps
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
with open("config.yaml", "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
MAIL_CONFIG = config["mails"]
|
||||
MEALS = config["meals"]
|
||||
TYPES = config["types"]
|
||||
PRETIX = config["pretix"]
|
||||
AUTH_TOKEN = config.get("auth", {}).get("token")
|
||||
|
||||
|
||||
with open("template.txt", "r") as f:
|
||||
MAIL_TEMPLATE = Template(f.read())
|
||||
|
||||
|
||||
def require_auth(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
token = request.headers.get("Authorization")
|
||||
if not token or token != f"Bearer {AUTH_TOKEN}":
|
||||
abort(401, description="Unauthorized: Invalid or missing token")
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
def send_email(to_email, subject, body, attachment_bytes, filename):
|
||||
print(f"[DEBUG] Sending email to {to_email} with attachment {filename}")
|
||||
msg = EmailMessage()
|
||||
msg["From"] = MAIL_CONFIG["sender"]
|
||||
msg["To"] = to_email
|
||||
msg["Subject"] = subject
|
||||
msg.set_content(body)
|
||||
msg.add_attachment(attachment_bytes, maintype="application", subtype="pdf", filename=filename)
|
||||
|
||||
server = smtplib.SMTP(MAIL_CONFIG["server"], MAIL_CONFIG["port"])
|
||||
if MAIL_CONFIG.get("starttls"):
|
||||
server.starttls()
|
||||
server.login(MAIL_CONFIG["user"], MAIL_CONFIG["password"])
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
print("[DEBUG] Email sent successfully")
|
||||
|
||||
def get_meal_times(meal_key):
|
||||
if meal_key.endswith("mittag"):
|
||||
return "11:30 - 13:30"
|
||||
elif meal_key.endswith("abend"):
|
||||
return "17:30 - 19:30"
|
||||
return ""
|
||||
|
||||
def log_printed(email, meal_key):
|
||||
location = email.split("@")[0]
|
||||
now = datetime.now()
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
time_str = now.strftime("%H:%M:%S")
|
||||
with open("printed.csv", "a", newline="") as csvfile:
|
||||
writer = csv.writer(csvfile, delimiter=";")
|
||||
writer.writerow([date_str, time_str, meal_key, location])
|
||||
print(f"[DEBUG] Logged printed PDF: {location} - {meal_key}")
|
||||
|
||||
def generate_qr(secret):
|
||||
qr = qrcode.QRCode(box_size=10, border=4)
|
||||
qr.add_data(secret)
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
buf = io.BytesIO()
|
||||
img.save(buf, format="PNG")
|
||||
buf.seek(0)
|
||||
return buf.getvalue()
|
||||
|
||||
@app.route("/order", methods=["POST"])
|
||||
@require_auth
|
||||
def order():
|
||||
data = request.get_json()
|
||||
print(f"[DEBUG] Incoming request JSON: {data}")
|
||||
|
||||
email = data.get("email")
|
||||
typ = data.get("type")
|
||||
meal_key = data.get("meal")
|
||||
|
||||
if not email or not typ or not meal_key:
|
||||
return jsonify({"error": "Missing field"}), 400
|
||||
|
||||
meal_info = MEALS.get(meal_key)
|
||||
if not meal_info:
|
||||
return jsonify({"error": "Meal not found in config"}), 400
|
||||
|
||||
position = {
|
||||
"positionid": 1,
|
||||
"item": TYPES.get(typ),
|
||||
"variation": None,
|
||||
"price": "0",
|
||||
"attendee_email": None,
|
||||
"addon_to": None,
|
||||
"subevent": meal_info["subevent"]
|
||||
}
|
||||
|
||||
pretix_body = {
|
||||
"email": "pretixfood@td00.de",
|
||||
"locale": "en",
|
||||
"sales_channel": "web",
|
||||
"payment_provider": "manual",
|
||||
"positions": [position]
|
||||
}
|
||||
|
||||
url = f"{PRETIX['host']}/api/v1/organizers/{PRETIX['organizer']}/events/{PRETIX['event']}/orders/"
|
||||
headers = {"Authorization": f"Token {PRETIX['token']}"}
|
||||
|
||||
print(f"[DEBUG] Sending POST to Pretix: {url} with body {pretix_body}")
|
||||
resp = requests.post(url, json=pretix_body, headers=headers)
|
||||
print(f"[DEBUG] Pretix response status: {resp.status_code}")
|
||||
print(f"[DEBUG] Pretix response text: {resp.text}")
|
||||
|
||||
try:
|
||||
resp_json = resp.json()
|
||||
print(f"[DEBUG] Pretix response JSON: {resp_json}")
|
||||
except ValueError:
|
||||
print("[DEBUG] Pretix returned no JSON")
|
||||
return jsonify({"error": "Pretix returned no JSON", "status_code": resp.status_code, "text": resp.text}), 502
|
||||
|
||||
|
||||
if "positions" in resp_json and "item" in resp_json["positions"][0] and isinstance(resp_json["positions"][0]["item"], list):
|
||||
if isinstance(resp_json["positions"][0]["item"][0], str):
|
||||
print("[DEBUG] Not enough quota available")
|
||||
return "Essen ist bereits alle", 418
|
||||
|
||||
|
||||
try:
|
||||
secret = resp_json["positions"][0]["secret"]
|
||||
print(f"[DEBUG] Secret: {secret}")
|
||||
except (KeyError, IndexError):
|
||||
return jsonify({"error": "Secret not found in Pretix response", "resp_json": resp_json}), 500
|
||||
|
||||
qr_bytes = generate_qr(secret)
|
||||
meal_times = get_meal_times(meal_key)
|
||||
|
||||
|
||||
mail_body = MAIL_TEMPLATE.render(
|
||||
meal_name=meal_info["name"],
|
||||
meal_name_en=meal_info["name_en"],
|
||||
meal_date=meal_info["date"],
|
||||
meal_times=meal_times
|
||||
)
|
||||
|
||||
|
||||
if email.endswith("@printme.local"):
|
||||
log_printed(email, meal_key)
|
||||
response = app.response_class(
|
||||
response=qr_bytes,
|
||||
status=200,
|
||||
mimetype='image/png'
|
||||
)
|
||||
response.headers["Content-Disposition"] = f"inline; filename=ticket.png"
|
||||
return response
|
||||
|
||||
|
||||
send_email(email, "Dein Engelessen / Your Angel Meal", mail_body, qr_bytes, "ticket.png")
|
||||
|
||||
return "Token gesendet", 201
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
Reference in New Issue
Block a user