seems to be the last RC - this app is done - so am I
This commit is contained in:
parent
1506201913
commit
f5b184fe54
37 changed files with 1932 additions and 661 deletions
150
setup.sh
150
setup.sh
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue