187 lines
5.9 KiB
Python
187 lines
5.9 KiB
Python
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
|
||
import mysql.connector
|
||
import os
|
||
import re
|
||
import requests
|
||
import random
|
||
import string
|
||
from datetime import datetime
|
||
from functools import wraps
|
||
|
||
app = Flask(__name__)
|
||
app.secret_key = os.environ.get('SECRET_KEY', 'skyevouchers-secret-key-2024')
|
||
|
||
# Config
|
||
DB_CONFIG = {
|
||
'host': os.environ.get('DB_HOST', 'localhost'),
|
||
'port': int(os.environ.get('DB_PORT', 3306)),
|
||
'user': os.environ.get('DB_USER', 'root'),
|
||
'password': os.environ.get('DB_PASSWORD', ''),
|
||
'database': os.environ.get('DB_NAME', 'skyevouchers'),
|
||
}
|
||
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'admin123')
|
||
GOTIFY_URL = os.environ.get('GOTIFY_URL', 'http://localhost:8080')
|
||
GOTIFY_TOKEN = os.environ.get('GOTIFY_TOKEN', '')
|
||
LOW_VOUCHER_THRESHOLD = 2
|
||
|
||
|
||
def get_db():
|
||
return mysql.connector.connect(**DB_CONFIG)
|
||
|
||
|
||
def init_db():
|
||
conn = get_db()
|
||
c = conn.cursor()
|
||
c.execute('''CREATE TABLE IF NOT EXISTS vouchers (
|
||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||
code VARCHAR(20) NOT NULL UNIQUE,
|
||
used BOOLEAN DEFAULT FALSE,
|
||
used_at DATETIME NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)''')
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
|
||
def send_gotify_notification(remaining):
|
||
if not GOTIFY_TOKEN:
|
||
return
|
||
try:
|
||
requests.post(
|
||
f"{GOTIFY_URL}/message",
|
||
json={
|
||
"title": "⚠️ Skye Vouchers – Niedriger Bestand",
|
||
"message": f"Nur noch {remaining} Voucher(s) verfügbar! Bitte neue Vouchers hinzufügen.",
|
||
"priority": 8
|
||
},
|
||
params={"token": GOTIFY_TOKEN},
|
||
timeout=5
|
||
)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
def login_required(f):
|
||
@wraps(f)
|
||
def decorated(*args, **kwargs):
|
||
if not session.get('admin_logged_in'):
|
||
return redirect(url_for('admin_login'))
|
||
return f(*args, **kwargs)
|
||
return decorated
|
||
|
||
|
||
# ── Frontend ──────────────────────────────────────────────────────────────────
|
||
|
||
@app.route('/')
|
||
def index():
|
||
return render_template('index.html')
|
||
|
||
|
||
@app.route('/api/get-voucher', methods=['POST'])
|
||
def get_voucher():
|
||
conn = get_db()
|
||
c = conn.cursor(dictionary=True)
|
||
c.execute('SELECT id, code FROM vouchers WHERE used = FALSE ORDER BY RAND() LIMIT 1')
|
||
voucher = c.fetchone()
|
||
if not voucher:
|
||
conn.close()
|
||
return jsonify({'error': 'Keine Vouchers mehr verfügbar'}), 404
|
||
|
||
c.execute('UPDATE vouchers SET used = TRUE, used_at = %s WHERE id = %s',
|
||
(datetime.now(), voucher['id']))
|
||
conn.commit()
|
||
|
||
# Check remaining
|
||
c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = FALSE')
|
||
remaining = c.fetchone()['cnt']
|
||
conn.close()
|
||
|
||
if remaining <= LOW_VOUCHER_THRESHOLD:
|
||
send_gotify_notification(remaining)
|
||
|
||
return jsonify({'code': voucher['code']})
|
||
|
||
|
||
# ── Admin ─────────────────────────────────────────────────────────────────────
|
||
|
||
@app.route('/admin/login', methods=['GET', 'POST'])
|
||
def admin_login():
|
||
if request.method == 'POST':
|
||
if request.form.get('password') == ADMIN_PASSWORD:
|
||
session['admin_logged_in'] = True
|
||
return redirect(url_for('admin_dashboard'))
|
||
flash('Falsches Passwort', 'error')
|
||
return render_template('admin_login.html')
|
||
|
||
|
||
@app.route('/admin/logout')
|
||
def admin_logout():
|
||
session.clear()
|
||
return redirect(url_for('admin_login'))
|
||
|
||
|
||
@app.route('/admin')
|
||
@login_required
|
||
def admin_dashboard():
|
||
conn = get_db()
|
||
c = conn.cursor(dictionary=True)
|
||
c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = FALSE')
|
||
available = c.fetchone()['cnt']
|
||
c.execute('SELECT COUNT(*) AS cnt FROM vouchers WHERE used = TRUE')
|
||
used_count = c.fetchone()['cnt']
|
||
c.execute('SELECT COUNT(*) AS cnt FROM vouchers')
|
||
total = c.fetchone()['cnt']
|
||
c.execute('SELECT code, used_at FROM vouchers WHERE used = TRUE ORDER BY used_at DESC LIMIT 20')
|
||
recent_used = c.fetchall()
|
||
c.execute('SELECT code, created_at FROM vouchers WHERE used = FALSE ORDER BY created_at DESC LIMIT 20')
|
||
available_vouchers = c.fetchall()
|
||
conn.close()
|
||
return render_template('admin_dashboard.html',
|
||
available=available,
|
||
used_count=used_count,
|
||
total=total,
|
||
recent_used=recent_used,
|
||
available_vouchers=available_vouchers)
|
||
|
||
|
||
@app.route('/admin/import', methods=['POST'])
|
||
@login_required
|
||
def admin_import():
|
||
text = request.form.get('voucher_text', '')
|
||
# Extract codes matching xxxxx-xxxxx (5 digits, dash, 5 digits)
|
||
codes = re.findall(r'\b(\d{5}-\d{5})\b', text)
|
||
if not codes:
|
||
flash('Keine gültigen Voucher-Codes gefunden.', 'error')
|
||
return redirect(url_for('admin_dashboard'))
|
||
|
||
conn = get_db()
|
||
c = conn.cursor()
|
||
imported = 0
|
||
skipped = 0
|
||
for code in codes:
|
||
try:
|
||
c.execute('INSERT INTO vouchers (code) VALUES (%s)', (code,))
|
||
imported += 1
|
||
except mysql.connector.IntegrityError:
|
||
skipped += 1
|
||
conn.commit()
|
||
conn.close()
|
||
flash(f'{imported} Voucher(s) importiert, {skipped} bereits vorhanden.', 'success')
|
||
return redirect(url_for('admin_dashboard'))
|
||
|
||
|
||
@app.route('/admin/delete/<int:vid>', methods=['POST'])
|
||
@login_required
|
||
def admin_delete(vid):
|
||
conn = get_db()
|
||
c = conn.cursor()
|
||
c.execute('DELETE FROM vouchers WHERE id = %s', (vid,))
|
||
conn.commit()
|
||
conn.close()
|
||
return jsonify({'ok': True})
|
||
|
||
|
||
if __name__ == '__main__':
|
||
init_db()
|
||
app.run(debug=True, host='0.0.0.0', port=5000)
|