seems to be the last RC - this app is done - so am I

This commit is contained in:
nocci 2025-05-09 14:34:33 +02:00
parent 1506201913
commit f5b184fe54
37 changed files with 1932 additions and 661 deletions

150
setup.sh
View file

@ -79,13 +79,28 @@ PROJECT_DIR="steam-gift-manager"
TRANSLATIONS_DIR="$PWD/$PROJECT_DIR/translations"
DATA_DIR="$PWD/data"
# 1. Create folders
# Create folders
mkdir -p "$PROJECT_DIR"
mkdir -p "$PROJECT_DIR"/{templates,static,translations}
mkdir -p "$DATA_DIR"
chmod -R a+rwX "$TRANSLATIONS_DIR" "$DATA_DIR"
echo -e "\n\033[1;32m✅ Downloading assets - Please wait!\033[0m"
# Download Pictures from my server
cd "$PROJECT_DIR/static"
wget -O logo.webp "https://drop.nocadmin.net/logo.webp" > /dev/null 2>&1
wget -O logo_small.webp "https://drop.nocadmin.net/logo_small.webp" > /dev/null 2>&1
wget -O forgejo.webp "https://drop.nocadmin.net/forgejo.webp" > /dev/null 2>&1
wget -O gog_logo.webp "https://drop.nocadmin.net/gog_logo.webp" > /dev/null 2>&1
wget -O logo_small_maskable.webp "https://drop.nocadmin.net/logo_small_maskable.webp" > /dev/null 2>&1
wget -O favicon.ico "https://drop.nocadmin.net/favicon.ico" > /dev/null 2>&1
wget -O apple-touch-icon.png "https://drop.nocadmin.net/apple-touch-icon.png" > /dev/null 2>&1
wget -O web-app-manifest-192x192.png "https://drop.nocadmin.net/web-app-manifest-192x192.png" > /dev/null 2>&1
wget -O web-app-manifest-512x512.png "https://drop.nocadmin.net/web-app-manifest-512x512.png" > /dev/null 2>&1
cd ../..
cd $PROJECT_DIR
# requirements.txt
@ -135,6 +150,7 @@ TZ=Europe/Berlin
FORCE_HTTPS=False
SESSION_COOKIE_SECURE=auto
CSRF_ENABLED="True"
# Account registration
REGISTRATION_ENABLED="True"
@ -158,7 +174,7 @@ REDIS_URL=redis://redis:6379/0
# Enable Debug (e.g. for VS Code)
FLASK_DEBUG=1
DEBUGPY=1
DEBUGPY=0
EOL
# app.py (the main app)
@ -167,19 +183,19 @@ cat <<'PYTHON_END' > app.py
import atexit
import csv
import io
import locale # Note: locale was in your imports but not standard for typical web apps unless specific use.
import locale
import logging
import os
import random
import re
import secrets
import sqlite3 # Note: direct sqlite3 import is unusual if you're using SQLAlchemy for all DB ops.
import sqlite3
import time
import traceback
from datetime import datetime, timedelta
from functools import wraps
from io import BytesIO # Note: io.BytesIO is good, no need for direct BytesIO import if io is already imported.
from time import sleep # Note: time.sleep is fine, no need for direct 'sleep' import if 'time' is imported.
from io import BytesIO
from time import sleep
from urllib.parse import urlparse
from zoneinfo import ZoneInfo
import warnings
@ -261,6 +277,11 @@ os.environ['TZ'] = TZ
app = Flask(__name__)
app.jinja_env.globals['getattr'] = getattr
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404
# UNIX-Systems (Linux, Docker)
try:
time.tzset()
@ -878,12 +899,12 @@ def edit_game(game_id):
if request.method == 'POST':
try:
# Validierung
# Validation
if not request.form.get('name') or not request.form.get('steam_key'):
flash(translate('Name and Steam Key are required'), 'error')
return redirect(url_for('edit_game', game_id=game_id))
# Dublettenprüfung
# Duplicate check
existing = Game.query.filter(
Game.steam_key == request.form['steam_key'],
Game.id != game.id,
@ -893,7 +914,7 @@ def edit_game(game_id):
flash(translate('Steam Key already exists'), 'error')
return redirect(url_for('edit_game', game_id=game_id))
# Felder aktualisieren
# Update fields
game.name = request.form['name']
game.steam_key = request.form['steam_key']
game.status = request.form['status']
@ -904,12 +925,12 @@ def edit_game(game_id):
game.steam_appid = request.form.get('steam_appid', '')
game.redeem_date = safe_parse_date(request.form.get('redeem_date', ''))
# Token-Logik
# Token-Logic
if game.status == 'geschenkt':
# Vorhandene Tokens löschen
RedeemToken.query.filter_by(game_id=game.id).delete()
# Neuen Token generieren
# Generate new Token
token = secrets.token_urlsafe(12)[:17]
expires = datetime.now(local_tz) + timedelta(hours=24)
new_token = RedeemToken(
@ -927,11 +948,11 @@ def edit_game(game_id):
except IntegrityError as e:
db.session.rollback()
app.logger.error(f"IntegrityError: {traceback.format_exc()}")
flash(translate('Database error: {error}', error=str(e.orig)), 'error') # Platzhalter korrigiert
flash(translate('Database error: {error}', error=str(e.orig)), 'error')
except Exception as e:
db.session.rollback()
app.logger.error(f"Unexpected error: {traceback.format_exc()}")
flash(translate('Unexpected error: {error}', error=str(e)), 'error') # Platzhalter korrigiert
flash(translate('Unexpected error: {error}', error=str(e)), 'error')
return render_template(
'edit_game.html',
@ -1006,14 +1027,14 @@ def export_pdf():
elements = []
img_height = 2*cm
# Titel
# Title
elements.append(Paragraph(
translate("Game List (without Keys)", lang=session.get('lang', 'en')),
styles['Title']
))
elements.append(Spacer(1, 12))
# Tabellenkopf
# Table header
col_widths = [
5*cm, 10*cm, 6*cm, 3*cm
]
@ -1047,7 +1068,7 @@ def export_pdf():
game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else ''
])
# Table format (korrekte Einrückung)
# Table format
table = Table(data, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
@ -1287,7 +1308,7 @@ def update_game_data(game_id):
flash(translate('Steam-AppID missing, no Steam Data transferred'), 'warning')
# ITAD-Slug donings and such
# ITAD-Slug doings and such
itad_slug = fetch_itad_slug(steam_appid)
if itad_slug:
game.itad_slug = itad_slug
@ -1422,7 +1443,7 @@ def cleanup_expired_tokens_job():
with app.app_context():
cleanup_expired_tokens()
# Jobs hinzufügen
# Add Jobs
scheduler.add_job(
check_expiring_keys_job,
'interval',
@ -1440,7 +1461,7 @@ def update_prices_job():
with app.app_context():
games = Game.query.filter(Game.steam_appid.isnot(None)).all()
for game in games:
# Nur Preise aktualisieren
# just update prices
itad_data = fetch_itad_data(f"app/{game.steam_appid}")
if itad_data:
game.current_price = itad_data.get('price_new')
@ -1488,7 +1509,7 @@ if __name__ == '__main__':
PYTHON_END
# 9. Templates
# Templates - this was the "fun" part
mkdir -p templates static
# Base Template
@ -2037,16 +2058,16 @@ cat <<HTML_END > templates/edit_game.html
<textarea id="game_notes" name="notes" class="form-control" rows="3">{{ game.notes }}</textarea>
</div>
<!-- Externe Daten Anzeige -->
<!-- Show External Data -->
<div class="col-12">
<div class="card mb-4">
<div class="card-header">
<span>🔄 {{ _('Externe Daten') }}</span>
<span>🔄 {{ _('External Data') }}</span>
</div>
<div class="card-body">
{% if game.release_date %}
<div class="mb-2">
<strong>{{ _('Veröffentlichung:') }}</strong>
<strong>{{ _('Release Date:') }}</strong>
{{ game.release_date|strftime('%d.%m.%Y') }}
</div>
{% endif %}
@ -2079,7 +2100,7 @@ cat <<HTML_END > templates/edit_game.html
{% if game.status == 'geschenkt' %}
<div class="col-12">
<div class="card mb-3">
<div class="card-header">{{ _('Einlöse-Links') }}</div>
<div class="card-header">{{ _('Redeem-Link') }}</div>
<div class="card-body">
{% for token in game.redeem_tokens if not token.is_expired() %}
<div class="input-group mb-3">
@ -2532,7 +2553,7 @@ cat <<HTML_END > templates/admin_audit_logs.html
HTML_END
# Error Site
# Error Sites
cat <<HTML_END > templates/403.html
{% extends "base.html" %}
{% block content %}
@ -2561,6 +2582,34 @@ cat <<HTML_END > templates/403.html
{% endblock %}
HTML_END
cat <<HTML_END > templates/404.html
{% extends "base.html" %}
{% block content %}
<div class="d-flex flex-column align-items-center justify-content-center" style="min-height:60vh;">
<div class="text-center">
<img src="{{ url_for('static', filename='logo.webp') }}"
alt="Forbidden"
class="img-fluid rounded shadow mb-4"
style="max-width: 160px;">
<h1 class="display-3 fw-bold text-danger mb-3">404</h1>
<h2 class="mb-4">{{ _('Access Forbidden') }}</h2>
<p class="lead mb-4">
<span class="d-block mb-2">{{ _('Sorry, you are not allowed to access this page.') }}</span>
<span class="text-muted">({{ _('Registration is currently disabled.') }})</span>
</p>
<a href="{{ url_for('index') }}" class="btn btn-lg btn-primary shadow">
🏠 {{ _('Back to Home') }}
</a>
<div class="mt-4 text-muted">
<small>
<span>Sorry, you haven't unlocked this area yet. Grind some more XP or check your DLC entitlements.<br>Maybe try again after the next patch?</span>
</small>
</div>
</div>
</div>
{% endblock %}
HTML_END
# CSS
cat <<CSS_END > static/style.css
:root {
@ -2774,7 +2823,7 @@ APP_DIR="steam-gift-manager"
TRANSLATION_DIR="$APP_DIR/translations"
LANGS=("de" "en")
# Prüfe jq
# check jq
if ! command -v jq &>/dev/null; then
echo "❌ jq is required. Install with: sudo apt-get install jq"
exit 1
@ -2810,12 +2859,12 @@ SCRIPT_END
chmod +x ../translate.sh
# SOFORT AUSFÜHREN, um Basis-JSONs zu erstellen
# EXECUTE IMMEDIATELY to create basic JSONs
cd ..
./translate.sh
cd $PROJECT_DIR
# 5. Dockerfile
# Dockerfile
cat <<'DOCKER_END' > Dockerfile
FROM python:3.10-slim
@ -2824,12 +2873,6 @@ SHELL ["/bin/bash", "-c"]
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
wget \
&& mkdir -p /app/static \
&& wget -O /app/static/logo.webp "https://drop.nocadmin.net/logo.webp" \
&& wget -O /app/static/logo_small.webp "https://drop.nocadmin.net/logo_small.webp" \
&& wget -O /app/static/forgejo.webp "https://drop.nocadmin.net/forgejo.webp" \
&& wget -O /app/static/gog_logo.webp "https://drop.nocadmin.net/gog_logo.webp" \
&& wget -O /app/static/logo_small_maskable.webp "https://drop.nocadmin.net/logo_small_maskable.webp" \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y locales && \
@ -2896,6 +2939,7 @@ services:
volumes:
- ../data:/app/data
- ./translations:/app/translations:rw
- ./static:/app/static:rw
user: "${UID:-1000}:${GID:-1000}"
restart: unless-stopped
command: ["/app/entrypoint.sh"]
@ -2924,7 +2968,7 @@ set -e
# Set the working directory to the project directory
cd "$(dirname "$0")/steam-gift-manager"
# Setze FLASK_APP, falls nötig
# set FLASK_APP, if needed
export FLASK_APP=app.py
# Initialize migrations, if not yet available
@ -2960,28 +3004,30 @@ cat <<MANIFEST_END > static/manifest.json
},
"icons": [
{
"src": "/static/logo_small.webp",
"src": "/static/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/webp",
"purpose": "any"
},
{
"src": "/static/logo_small_maskable.webp",
"src": "/static/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/webp",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/logo.webp",
"src": "/static/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/webp",
"purpose": "any maskable"
"type": "image/png",
"purpose": "maskable"
}
]
],
"theme_color": "#ffffff",
"background_color": "#3f3a3a",
"display": "standalone"
}
MANIFEST_END
# Service Worker
cat <<SW_END > static/serviceworker.js
const CACHE_NAME = 'game-key-manager-v2';
@ -2989,6 +3035,8 @@ const ASSETS = [
'/',
'/static/style.css',
'/static/logo.webp',
'/static/web-app-manifest-512x512.png',
'/static/web-app-manifest-192x192.png',
'/static/logo_small.webp',
'/static/gog_logo.webp',
'/static/forgejo.webp'
@ -3018,6 +3066,22 @@ self.addEventListener('activate', (event) => {
});
SW_END
# Download German Translation from my server
cd "$TRANSLATIONS_DIR"
read -p "Do you want to download German translations from the dev-server (could cause trouble, but normally it is safe)? [y/N]: " download_de
if [[ "$download_de" =~ ^[YyJj]$ ]]; then
wget -O de.json "https://drop.nocadmin.net/de.json" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✅ German translations downloaded successfully."
else
echo "❌ Download failed. Keeping existing de.json."
fi
else
echo "⏩ Skipped downloading German translations."
fi
echo -e "\n\033[1;32m✅ Setup done! Seems to be okay!\033[0m"
echo -e "Have a look in your .env"
echo -e "nano .env"