Compare commits

..

11 Commits
1.0 ... main

16 changed files with 310 additions and 272 deletions

34
.env Normal file
View File

@ -0,0 +1,34 @@
# Flask-Configuration - Key are generated through setup.sh
SECRET_KEY=""
REDEEM_SECRET=""
WTF_CSRF_SECRET_KEY=""
# locales
BABEL_DEFAULT_LOCALE="en"
BABEL_SUPPORTED_LOCALES="de,en"
BABEL_TRANSLATION_DIRECTORIES="translations"
# Timezone
TZ=Europe/Berlin
# Security
SESSION_COOKIE_SECURE="False"
CSRF_ENABLED="True"
# Account registration
REGISTRATION_ENABLED="True"
# checking interval if keys have to be redeemed before a specific date
CHECK_EXPIRING_KEYS_INTERVAL_HOURS=6
# Pushover
PUSHOVER_APP_TOKEN=""
PUSHOVER_USER_KEY=""
# Gotify
GOTIFY_URL=""
GOTIFY_TOKEN=""
# Matrix
MATRIX_HOMESERVER=""
MATRIX_ACCESS_TOKEN=""
MATRIX_ROOM_ID=""

View File

@ -50,7 +50,16 @@ It's even possible to gift your keys via a unique website. Just edit the game to
## 🚀 Get Started! 🚀 ## 🚀 Get Started! 🚀
### 1. **Clone the Repository** ## 1. **Clone the Repository (Option 1 or Option 2)**
### Option 1: Clone the main repository
```bash
git clone https://codeberg.org/nocci/GameKeyManager
cd steam-gift-manager
```
### Option 2: Clone from alternative repository (if option 1 fails)
```bash ```bash
git clone https://git.nocci.it/nocci/GameKeyManager git clone https://git.nocci.it/nocci/GameKeyManager

156
setup.sh
View File

@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Color definitions # Colors
RED='\033[1;31m' RED='\033[1;31m'
GREEN='\033[1;32m' GREEN='\033[1;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
NC='\033[0m' NC='\033[0m'
# 1. Docker check (including Arch Linux) # 1. Docker check (incl. Arch Linux)
if ! command -v docker &>/dev/null; then if ! command -v docker &>/dev/null; then
echo -e "${RED}❗ Docker is not installed.${NC}" echo -e "${RED}❗ Docker is not installed.${NC}"
read -p "Would you like to install Docker automatically now? [y/N]: " install_docker read -p "Would you like to install Docker automatically now? [y/N]: " install_docker
@ -23,7 +23,7 @@ if ! command -v docker &>/dev/null; then
rm get-docker.sh rm get-docker.sh
fi fi
# Check Docker group membership # Docker group membership prüfen
if ! groups | grep -q '\bdocker\b'; then if ! groups | grep -q '\bdocker\b'; then
echo -e "${YELLOW}⚠️ Your user is not in the docker group. Adding now...${NC}" echo -e "${YELLOW}⚠️ Your user is not in the docker group. Adding now...${NC}"
sudo usermod -aG docker $USER sudo usermod -aG docker $USER
@ -37,7 +37,7 @@ if ! command -v docker &>/dev/null; then
fi fi
fi fi
# 2. Docker Compose check (V1 and V2 plugin, with Arch support) # 2. Check Docker compose (V1 und V2 Plugin, incl. Arch Support)
DOCKER_COMPOSE_CMD="" DOCKER_COMPOSE_CMD=""
if command -v docker-compose &>/dev/null; then if command -v docker-compose &>/dev/null; then
DOCKER_COMPOSE_CMD="docker-compose" DOCKER_COMPOSE_CMD="docker-compose"
@ -52,6 +52,7 @@ else
elif command -v apt-get &>/dev/null; then elif command -v apt-get &>/dev/null; then
sudo apt-get update sudo apt-get update
sudo apt-get install -y docker-compose-plugin sudo apt-get install -y docker-compose-plugin
sudo apt-get install -y docker-compose
elif command -v dnf &>/dev/null; then elif command -v dnf &>/dev/null; then
sudo dnf install -y docker-compose sudo dnf install -y docker-compose
elif command -v yum &>/dev/null; then elif command -v yum &>/dev/null; then
@ -73,13 +74,12 @@ else
fi fi
fi fi
# Configuration
# Konfiguration
PROJECT_DIR="steam-gift-manager" PROJECT_DIR="steam-gift-manager"
TRANSLATIONS_DIR="../translations" TRANSLATIONS_DIR="$PWD/translations"
DATA_DIR="../data" DATA_DIR="$PWD/data"
# 1. Projektordner & Übersetzungsordner erstellen # 1. Create folders
mkdir -p "$PROJECT_DIR"/{templates,static} mkdir -p "$PROJECT_DIR"/{templates,static}
mkdir -p "$TRANSLATIONS_DIR"/de/LC_MESSAGES mkdir -p "$TRANSLATIONS_DIR"/de/LC_MESSAGES
mkdir -p "$TRANSLATIONS_DIR"/en/LC_MESSAGES mkdir -p "$TRANSLATIONS_DIR"/en/LC_MESSAGES
@ -107,9 +107,10 @@ matrix-client
reportlab reportlab
requests requests
pillow pillow
gunicorn
EOL EOL
# 3. .env-Datei im übergeordneten Verzeichnis erstellen # 3. .env Datei in Parent-VFolder
cd .. cd ..
SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(24))') SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(24))')
REDEEM_SECRET=$(python3 -c 'import secrets; print(secrets.token_hex(16))') REDEEM_SECRET=$(python3 -c 'import secrets; print(secrets.token_hex(16))')
@ -152,13 +153,11 @@ MATRIX_ACCESS_TOKEN=""
MATRIX_ROOM_ID="" MATRIX_ROOM_ID=""
EOL EOL
# Zurück ins Projektverzeichnis
cd $PROJECT_DIR cd $PROJECT_DIR
# 4. app.py (vollständige korrigierte Version) # 4. app.py (the main app)
cat <<'PYTHON_END' > app.py cat <<'PYTHON_END' > app.py
import os import os
import logging
import warnings import warnings
from sqlalchemy.exc import LegacyAPIWarning from sqlalchemy.exc import LegacyAPIWarning
warnings.simplefilter("ignore", category=LegacyAPIWarning) warnings.simplefilter("ignore", category=LegacyAPIWarning)
@ -200,8 +199,11 @@ from reportlab.lib.utils import ImageReader
from reportlab.lib.units import cm, inch, mm from reportlab.lib.units import cm, inch, mm
from io import BytesIO from io import BytesIO
import reportlab.lib import reportlab.lib
import logging
logging.basicConfig()
logging.getLogger('babel').setLevel(logging.DEBUG)
app = Flask(__name__) app = Flask(__name__)
csrf = CSRFProtect(app) csrf = CSRFProtect(app)
convention = { convention = {
@ -218,22 +220,27 @@ load_dotenv(override=True)
# Lade Umgebungsvariablen aus .env mit override # Lade Umgebungsvariablen aus .env mit override
load_dotenv(override=True) load_dotenv(override=True)
# Konfiguration # App-Configuration
app.config.update( app.config.update(
SECRET_KEY=os.getenv('SECRET_KEY'), SECRET_KEY=os.getenv('SECRET_KEY'),
SQLALCHEMY_DATABASE_URI=('sqlite:////app/data/games.db'), SQLALCHEMY_DATABASE_URI='sqlite:////app/data/games.db',
SQLALCHEMY_TRACK_MODIFICATIONS=False, SQLALCHEMY_TRACK_MODIFICATIONS=False,
BABEL_DEFAULT_LOCALE=os.getenv('BABEL_DEFAULT_LOCALE'), BABEL_DEFAULT_LOCALE=os.getenv('BABEL_DEFAULT_LOCALE', 'en'),
BABEL_SUPPORTED_LOCALES=os.getenv('BABEL_SUPPORTED_LOCALES').split(','), BABEL_SUPPORTED_LOCALES=os.getenv('BABEL_SUPPORTED_LOCALES', 'de,en').split(','),
BABEL_TRANSLATION_DIRECTORIES=os.getenv('BABEL_TRANSLATION_DIRECTORIES'), BABEL_TRANSLATION_DIRECTORIES=os.path.join(app.root_path, 'translations'),
SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE') == 'True', SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE', 'False') == 'True',
WTF_CSRF_ENABLED=os.getenv('CSRF_ENABLED') == 'True', SESSION_COOKIE_SAMESITE='Lax',
REGISTRATION_ENABLED=os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true' PERMANENT_SESSION_LIFETIME=timedelta(days=30),
SESSION_REFRESH_EACH_REQUEST=False,
WTF_CSRF_ENABLED=os.getenv('CSRF_ENABLED', 'True') == 'True',
REGISTRATION_ENABLED=os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true',
SEND_FILE_MAX_AGE_DEFAULT=int(os.getenv('SEND_FILE_MAX_AGE_DEFAULT', 0)),
TEMPLATES_AUTO_RELOAD=os.getenv('TEMPLATES_AUTO_RELOAD', 'True') == 'True'
) )
interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12)) interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12))
# Initialisierung # Initialisation
db = SQLAlchemy(app, metadata=metadata) db = SQLAlchemy(app, metadata=metadata)
migrate = Migrate(app, db) migrate = Migrate(app, db)
login_manager = LoginManager(app) login_manager = LoginManager(app)
@ -250,6 +257,11 @@ def get_locale():
return session['lang'] return session['lang']
return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES']) return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])
@app.before_request
def reload_translations():
if app.config['DEBUG']:
babel.reload()
@app.context_processor @app.context_processor
def inject_template_vars(): def inject_template_vars():
return dict( return dict(
@ -257,7 +269,7 @@ def inject_template_vars():
theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light' theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light'
) )
# Datenbankmodelle # DB Models
class User(db.Model, UserMixin): class User(db.Model, UserMixin):
__tablename__ = 'users' __tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -349,7 +361,7 @@ def login():
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
def register(): def register():
if not app.config['REGISTRATION_ENABLED']: if not app.config['REGISTRATION_ENABLED']:
flash(_('Registrierungen sind deaktiviert'), 'danger') flash(_('No new registrations. They are deactivated!'), 'danger')
return redirect(url_for('login')) return redirect(url_for('login'))
if request.method == 'POST': if request.method == 'POST':
@ -383,16 +395,16 @@ def change_password():
confirm_password = request.form['confirm_password'] confirm_password = request.form['confirm_password']
if not check_password_hash(current_user.password, current_password): if not check_password_hash(current_user.password, current_password):
flash(_('Aktuelles Passwort ist falsch'), 'danger') flash(_('Current passwort is wrong'), 'danger')
return redirect(url_for('change_password')) return redirect(url_for('change_password'))
if new_password != confirm_password: if new_password != confirm_password:
flash(_('Neue Passwörter stimmen nicht überein'), 'danger') flash(_('New Passwords are not matching'), 'danger')
return redirect(url_for('change_password')) return redirect(url_for('change_password'))
current_user.password = generate_password_hash(new_password) current_user.password = generate_password_hash(new_password)
db.session.commit() db.session.commit()
flash(_('Passwort erfolgreich geändert'), 'success') flash(_('Password changed successfully'), 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('change_password.html') return render_template('change_password.html')
@ -588,7 +600,7 @@ def export_pdf():
game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else '' game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else ''
]) ])
# Tabelle formatieren # Table format
table = Table(data, colWidths=col_widths, repeatRows=1) table = Table(data, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([ table.setStyle(TableStyle([
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
@ -650,15 +662,15 @@ def import_games():
db.session.commit() db.session.commit()
flash(_('%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen', new=new_games, dup=duplicates), 'success') flash(_('%(new)d new games imported, %(dup)d skipped duplicates', new=new_games, dup=duplicates), 'success')
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
flash(_('Importfehler: %(error)s', error=str(e)), 'danger') flash(_('Import error: %(error)s', error=str(e)), 'danger')
return redirect(url_for('index')) return redirect(url_for('index'))
flash(_('Bitte eine gültige CSV-Datei hochladen.'), 'danger') flash(_('Please upload a valid CSV file.'), 'danger')
return render_template('import.html') return render_template('import.html')
@ -716,6 +728,13 @@ def redeem_page(token):
redeem_token=redeem_token, redeem_token=redeem_token,
platform_link='https://store.steampowered.com/account/registerkey?key=' if game.steam_appid else 'https://www.gog.com/redeem') platform_link='https://store.steampowered.com/account/registerkey?key=' if game.steam_appid else 'https://www.gog.com/redeem')
@app.route('/debug-session')
def debug_session():
return jsonify({
'session_lang': session.get('lang'),
'config_locales': app.config['BABEL_SUPPORTED_LOCALES']
})
# Benachrichtigungsfunktionen # Benachrichtigungsfunktionen
def send_pushover_notification(user, game): def send_pushover_notification(user, game):
"""Sendet Pushover-Benachrichtigung für ablaufenden Key""" """Sendet Pushover-Benachrichtigung für ablaufenden Key"""
@ -812,7 +831,7 @@ def check_expiring_keys():
send_notification(user, game) send_notification(user, game)
# Optional: Cleanup-Funktion für regelmäßiges Löschen abgelaufener Tokens # Optional: cleaning up old tokens
def cleanup_expired_tokens(): def cleanup_expired_tokens():
now = datetime.utcnow() now = datetime.utcnow()
expired = RedeemToken.query.filter(RedeemToken.expires < now).all() expired = RedeemToken.query.filter(RedeemToken.expires < now).all()
@ -821,13 +840,13 @@ def cleanup_expired_tokens():
db.session.commit() db.session.commit()
# Scheduler initialisieren und starten # Scheduler start
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
scheduler.add_job(func=check_expiring_keys, trigger="interval", hours=interval_hours) scheduler.add_job(func=check_expiring_keys, trigger="interval", hours=interval_hours)
scheduler.add_job(func=cleanup_expired_tokens, trigger="interval", hours=1) scheduler.add_job(func=cleanup_expired_tokens, trigger="interval", hours=1)
scheduler.start() scheduler.start()
# Shutdown des Schedulers bei Beendigung der App # Shutdown of the Schedulers when stopping the app
atexit.register(lambda: scheduler.shutdown()) atexit.register(lambda: scheduler.shutdown())
if __name__ == '__main__': if __name__ == '__main__':
@ -837,7 +856,7 @@ if __name__ == '__main__':
PYTHON_END PYTHON_END
# Babel Konfiguration erstellen # Create Babel configuration
cat <<EOL > babel.cfg cat <<EOL > babel.cfg
[python: **.py] [python: **.py]
[jinja2: **/templates/**.html] [jinja2: **/templates/**.html]
@ -879,7 +898,8 @@ RUN groupadd -g \$GID appuser && \
USER appuser USER appuser
EXPOSE 5000 EXPOSE 5000
CMD ["python", "app.py"]
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
DOCKER_END DOCKER_END
# 6. docker-compose.yml # 6. docker-compose.yml
@ -894,46 +914,44 @@ services:
- TZ=${TZ} - TZ=${TZ}
volumes: volumes:
- ../data:/app/data - ../data:/app/data
- ../translations:/app/translations - ../translations:/app/translations:rw
- ../.env:/app/.env - ../.env:/app/.env
user: "1000:1000" user: "${UID}:${GID}"
restart: unless-stopped restart: unless-stopped
COMPOSE_END COMPOSE_END
# 7. Verzeichnisse und Berechtigungen # 7. Directories and permissions
mkdir -p ../data ../translations mkdir -p ../data ../translations
chmod -R a+rwX ../data ../translations chmod -R a+rwX ../data ../translations
find ../data ../translations -type d -exec chmod 775 {} \;
find ../data ../translations -type f -exec chmod 664 {} \;
# 8. Übersetzungs- und Upgrade-Scripte # 8. Translation and upgrade scripts
cat <<'SCRIPT_END' > ../translate.sh cat <<'SCRIPT_END' > ../translate.sh
#!/bin/bash #!/bin/bash
set -e set -e
# 0.1 Change to the project directory (where docker-compose.yml is located)
cd "$(dirname "$0")/steam-gift-manager" cd "$(dirname "$0")/steam-gift-manager"
declare -A locales=( declare -a locales=("de" "en")
["de"]="de"
["en"]="en"
)
# POT-Datei erstellen # 1. POT-Datei aktualisieren
docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot . docker-compose run --rm steam-manager pybabel extract -F babel.cfg -o translations/messages.pot .
# Für jede Sprache prüfen und ggf. initialisieren # 2. PO files for each language
for lang in "${!locales[@]}"; do for lang in "${locales[@]}"; do
if [ ! -f "translations/${locales[$lang]}/LC_MESSAGES/messages.po" ]; then docker-compose run --rm steam-manager pybabel update \
docker-compose exec steam-manager pybabel init \
-i translations/messages.pot \ -i translations/messages.pot \
-d translations \ -d translations \
-l "${locales[$lang]}" -l $lang --previous
fi
done done
# Übersetzungen aktualisieren und kompilieren # 3. Compile MO files (without fuzzy entries)
docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations docker-compose run --rm steam-manager pybabel compile -d translations
docker-compose exec steam-manager pybabel compile -d translations
echo "✅ Übersetzungen aktualisiert!" echo "✅ Translations successfully updated!"
SCRIPT_END SCRIPT_END
chmod +x ../translate.sh chmod +x ../translate.sh
@ -941,25 +959,25 @@ cat <<'SCRIPT_END' > ../upgrade.sh
#!/bin/bash #!/bin/bash
set -e set -e
# Setze das Arbeitsverzeichnis auf das Projektverzeichnis # Set the working directory to the project directory
cd "$(dirname "$0")/steam-gift-manager" cd "$(dirname "$0")/steam-gift-manager"
# Setze FLASK_APP, falls nötig # Setze FLASK_APP, falls nötig
export FLASK_APP=app.py export FLASK_APP=app.py
# Initialisiere migrations, falls noch nicht vorhanden # Initialize migrations, if not yet available
if [ ! -d migrations ]; then if [ ! -d migrations ]; then
echo "Starting Flask-Migrate..." echo "Starting Flask-Migrate..."
docker-compose exec steam-manager flask db init docker-compose exec steam-manager flask db init
fi fi
# Erzeuge Migration (nur wenn sich Modelle geändert haben) # Create migration (only if models have changed)
docker-compose exec steam-manager flask db migrate -m "Automatic Migration" docker-compose exec steam-manager flask db migrate -m "Automatic Migration"
# Wende Migration an # Apply migration
docker-compose exec steam-manager flask db upgrade docker-compose exec steam-manager flask db upgrade
echo "✅ Database-Migration abgeschlossen!" echo "✅ Database migration completed!"
SCRIPT_END SCRIPT_END
chmod +x ../upgrade.sh chmod +x ../upgrade.sh
@ -1002,6 +1020,8 @@ cat <<HTML_END > templates/base.html
<label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label> <label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
</div> </div>
<div class="dropdown ms-3"> <div class="dropdown ms-3">
<!-- DEBUG: Current locale {{ get_locale() }} -->
<div hidden id="locale-debug" data-locale="{{ get_locale() }}"></div>
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
{% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %} {% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %}
</button> </button>
@ -1012,7 +1032,7 @@ cat <<HTML_END > templates/base.html
</div> </div>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Passwort') }}</a> <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Password') }}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a> <a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a>
@ -1381,11 +1401,11 @@ cat <<HTML_END > templates/import.html
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">{{ _('CSV-Datei auswählen') }}</label> <label class="form-label">{{ _('Select CSV file') }}</label>
<input type="file" name="file" class="form-control" accept=".csv" required> <input type="file" name="file" class="form-control" accept=".csv" required>
</div> </div>
<button type="submit" class="btn btn-success">{{ _('Importieren') }}</button> <button type="submit" class="btn btn-success">{{ _('Import') }}</button>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Abbrechen') }}</a> <a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
@ -1479,7 +1499,7 @@ function updateCountdown() {
updateProgressBar(percent); updateProgressBar(percent);
} }
// Initialisierung // run countdown
updateCountdown(); updateCountdown();
const timer = setInterval(updateCountdown, 1000); const timer = setInterval(updateCountdown, 1000);
</script> </script>
@ -1555,7 +1575,7 @@ body {
color: #ff6b6b; color: #ff6b6b;
} }
/* Progressbar-Animationen */ /* Progressbar-Animations */
#expiry-bar { #expiry-bar {
transition: width 1s linear, background-color 0.5s ease; transition: width 1s linear, background-color 0.5s ease;
} }

View File

@ -23,4 +23,5 @@ RUN groupadd -g $GID appuser && useradd -u $UID -g $GID -m appuser && ch
USER appuser USER appuser
EXPOSE 5000 EXPOSE 5000
CMD ["python", "app.py"]
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]

View File

@ -59,7 +59,7 @@ load_dotenv(override=True)
# Lade Umgebungsvariablen aus .env mit override # Lade Umgebungsvariablen aus .env mit override
load_dotenv(override=True) load_dotenv(override=True)
# Konfiguration # App-Configuration
app.config.update( app.config.update(
SECRET_KEY=os.getenv('SECRET_KEY'), SECRET_KEY=os.getenv('SECRET_KEY'),
SQLALCHEMY_DATABASE_URI=('sqlite:////app/data/games.db'), SQLALCHEMY_DATABASE_URI=('sqlite:////app/data/games.db'),
@ -74,7 +74,7 @@ app.config.update(
interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12)) interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12))
# Initialisierung # Initialisation
db = SQLAlchemy(app, metadata=metadata) db = SQLAlchemy(app, metadata=metadata)
migrate = Migrate(app, db) migrate = Migrate(app, db)
login_manager = LoginManager(app) login_manager = LoginManager(app)
@ -98,7 +98,7 @@ def inject_template_vars():
theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light' theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light'
) )
# Datenbankmodelle # DB Models
class User(db.Model, UserMixin): class User(db.Model, UserMixin):
__tablename__ = 'users' __tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -429,7 +429,7 @@ def export_pdf():
game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else '' game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else ''
]) ])
# Tabelle formatieren # Table format
table = Table(data, colWidths=col_widths, repeatRows=1) table = Table(data, colWidths=col_widths, repeatRows=1)
table.setStyle(TableStyle([ table.setStyle(TableStyle([
('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
@ -653,7 +653,7 @@ def check_expiring_keys():
send_notification(user, game) send_notification(user, game)
# Optional: Cleanup-Funktion für regelmäßiges Löschen abgelaufener Tokens # Optional: cleaning up old tokens
def cleanup_expired_tokens(): def cleanup_expired_tokens():
now = datetime.utcnow() now = datetime.utcnow()
expired = RedeemToken.query.filter(RedeemToken.expires < now).all() expired = RedeemToken.query.filter(RedeemToken.expires < now).all()
@ -662,13 +662,13 @@ def cleanup_expired_tokens():
db.session.commit() db.session.commit()
# Scheduler initialisieren und starten # Scheduler start
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
scheduler.add_job(func=check_expiring_keys, trigger="interval", hours=interval_hours) scheduler.add_job(func=check_expiring_keys, trigger="interval", hours=interval_hours)
scheduler.add_job(func=cleanup_expired_tokens, trigger="interval", hours=1) scheduler.add_job(func=cleanup_expired_tokens, trigger="interval", hours=1)
scheduler.start() scheduler.start()
# Shutdown des Schedulers bei Beendigung der App # Shutdown of the Schedulers when stopping the app
atexit.register(lambda: scheduler.shutdown()) atexit.register(lambda: scheduler.shutdown())
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -14,3 +14,4 @@ matrix-client
reportlab reportlab
requests requests
pillow pillow
gunicorn

View File

@ -42,7 +42,7 @@ body {
color: #ff6b6b; color: #ff6b6b;
} }
/* Progressbar-Animationen */ /* Progressbar-Animations */
#expiry-bar { #expiry-bar {
transition: width 1s linear, background-color 0.5s ease; transition: width 1s linear, background-color 0.5s ease;
} }

View File

@ -4,7 +4,7 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-body text-center"> <div class="card-body text-center">
<img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="311" height="240" class="mb-4" style="object-fit:contain;"> <img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="266" height="206" class="mb-4" style="object-fit:contain;">
<h2 class="card-title mb-4">{{ _('Login') }}</h2> <h2 class="card-title mb-4">{{ _('Login') }}</h2>
<form method="POST"> <form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

View File

@ -84,7 +84,7 @@ function updateCountdown() {
updateProgressBar(percent); updateProgressBar(percent);
} }
// Initialisierung // run countdown
updateCountdown(); updateCountdown();
const timer = setInterval(updateCountdown, 1000); const timer = setInterval(updateCountdown, 1000);
</script> </script>

View File

@ -8,10 +8,10 @@ declare -A locales=(
["en"]="en" ["en"]="en"
) )
# POT-Datei erstellen # create POT-file
docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot . docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot .
# Für jede Sprache prüfen und ggf. initialisieren # Check for each language and initialize if necessary
for lang in "${!locales[@]}"; do for lang in "${!locales[@]}"; do
if [ ! -f "translations/${locales[$lang]}/LC_MESSAGES/messages.po" ]; then if [ ! -f "translations/${locales[$lang]}/LC_MESSAGES/messages.po" ]; then
docker-compose exec steam-manager pybabel init \ docker-compose exec steam-manager pybabel init \
@ -21,8 +21,8 @@ for lang in "${!locales[@]}"; do
fi fi
done done
# Übersetzungen aktualisieren und kompilieren # Update and compile translations
docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations
docker-compose exec steam-manager pybabel compile -d translations docker-compose exec steam-manager pybabel compile -d translations
echo "✅ Übersetzungen aktualisiert!" echo "✅ Translations updated!"

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-04-26 11:13+0000\n" "POT-Creation-Date: 2025-04-29 15:53+0000\n"
"PO-Revision-Date: 2025-04-26 11:13+0000\n" "PO-Revision-Date: 2025-04-29 15:42+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n" "Language: de\n"
"Language-Team: de <LL@li.org>\n" "Language-Team: de <LL@li.org>\n"
@ -18,270 +18,257 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n" "Generated-By: Babel 2.17.0\n"
#: app.py:187 #: app.py:194
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr "Ungültige Anmeldedaten"
#: app.py:193 #: app.py:200
msgid "Registrierungen sind deaktiviert" msgid "No new registrations. They are deactivated!"
msgstr "" msgstr "Keine neuen Registrierungen. Sie sind deaktiviert!"
#: app.py:201 #: app.py:208
msgid "Username already exists" msgid "Username already exists"
msgstr "" msgstr "Benutzername existiert bereits"
#: app.py:227 #: app.py:234
msgid "Aktuelles Passwort ist falsch" msgid "Current passwort is wrong"
msgstr "" msgstr "Aktuelles Passwort ist falsch"
#: app.py:231 #: app.py:238
msgid "Neue Passwörter stimmen nicht überein" msgid "New Passwords are not matching"
msgstr "" msgstr "Neue Passwörter stimmen nicht überein"
#: app.py:236 #: app.py:243
msgid "Passwort erfolgreich geändert" msgid "Password changed successfully"
msgstr "" msgstr "Passwort erfolgreich geändert"
#: app.py:266 #: app.py:273
msgid "Game added successfully!" msgid "Game added successfully!"
msgstr "" msgstr "Spiel erfolgreich hinzugefügt!"
#: app.py:271 #: app.py:278
msgid "Steam Key already exists!" msgid "Steam Key already exists!"
msgstr "" msgstr "Steam-Key existiert bereits!"
#: app.py:274 app.py:318 #: app.py:281 app.py:325
msgid "Error: " msgid "Error: "
msgstr "" msgstr "Fehler: "
#: app.py:313 #: app.py:320
msgid "Changes saved!" msgid "Changes saved!"
msgstr "" msgstr "Änderungen gespeichert!"
#: app.py:401 #: app.py:408
msgid "Game List (without Keys)" msgid "Game List (without Keys)"
msgstr "" msgstr "Spieleliste (ohne Keys)"
#: app.py:494 #: app.py:501
#, python-format #, python-format
msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen" msgid "%(new)d new games imported, %(dup)d skipped duplicates"
msgstr "" msgstr "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
#: app.py:498 #: app.py:505
#, python-format #, python-format
msgid "Importfehler: %(error)s" msgid "Import error: %(error)s"
msgstr "" msgstr "Importfehler: %(error)s"
#: app.py:502 #: app.py:509
msgid "Bitte eine gültige CSV-Datei hochladen." msgid "Please upload a valid CSV file."
msgstr "" msgstr "Bitte eine gültige CSV-Datei hochladen."
#: templates/add_game.html:4 templates/index.html:9 #: templates/add_game.html:4 templates/index.html:9
msgid "Add New Game" msgid "Add New Game"
msgstr "" msgstr "Neues Spiel hinzufügen"
#: templates/add_game.html:9 templates/edit_game.html:9 templates/index.html:19 #: templates/add_game.html:9 templates/edit_game.html:9 templates/index.html:19
msgid "Name" msgid "Name"
msgstr "" msgstr "Name"
#: templates/add_game.html:13 templates/edit_game.html:13 #: templates/add_game.html:13 templates/edit_game.html:13
msgid "Game Key" msgid "Game Key"
msgstr "" msgstr "Spiele-Key"
#: templates/add_game.html:17 templates/edit_game.html:21 #: templates/add_game.html:17 templates/edit_game.html:21 templates/index.html:21
#: templates/index.html:21
msgid "Status" msgid "Status"
msgstr "" msgstr "Status"
#: templates/add_game.html:19 templates/edit_game.html:23 #: templates/add_game.html:19 templates/edit_game.html:23 templates/index.html:41
#: templates/index.html:41
msgid "Not redeemed" msgid "Not redeemed"
msgstr "" msgstr "Nicht eingelöst"
#: templates/add_game.html:20 templates/edit_game.html:24 #: templates/add_game.html:20 templates/edit_game.html:24 templates/index.html:43
#: templates/index.html:43
msgid "Gifted" msgid "Gifted"
msgstr "" msgstr "Verschenkt"
#: templates/add_game.html:21 templates/edit_game.html:25 #: templates/add_game.html:21 templates/edit_game.html:25 templates/index.html:45
#: templates/index.html:45
msgid "Redeemed" msgid "Redeemed"
msgstr "" msgstr "Eingelöst"
#: templates/add_game.html:25 templates/edit_game.html:29 #: templates/add_game.html:25 templates/edit_game.html:29 templates/index.html:23
#: templates/index.html:23
msgid "Redeem by" msgid "Redeem by"
msgstr "" msgstr "Einzulösen bis"
#: templates/add_game.html:29 templates/edit_game.html:33 #: templates/add_game.html:29 templates/edit_game.html:33
msgid "Recipient" msgid "Recipient"
msgstr "" msgstr "Empfänger"
#: templates/add_game.html:33 templates/edit_game.html:37 #: templates/add_game.html:33 templates/edit_game.html:37
msgid "Shop URL" msgid "Shop URL"
msgstr "" msgstr "Shop-URL"
#: templates/add_game.html:37 templates/edit_game.html:41 #: templates/add_game.html:37 templates/edit_game.html:41
msgid "Notes" msgid "Notes"
msgstr "" msgstr "Notizen"
#: templates/add_game.html:41 templates/edit_game.html:60 #: templates/add_game.html:41 templates/edit_game.html:60
msgid "Save" msgid "Save"
msgstr "" msgstr "Speichern"
#: templates/add_game.html:42 templates/edit_game.html:61 #: templates/add_game.html:42 templates/edit_game.html:61 templates/import.html:12
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr "Abbrechen"
#: templates/base.html:7 #: templates/base.html:7
msgid "Game Key Manager" msgid "Game Key Manager"
msgstr "" msgstr "Game-Key-Verwaltung"
#: templates/base.html:23 #: templates/base.html:23
msgid "Search" msgid "Search"
msgstr "" msgstr "Suche"
#: templates/base.html:31 #: templates/base.html:31
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr "Dunkler Modus"
#: templates/base.html:44 #: templates/base.html:46 templates/login.html:16 templates/register.html:15
msgid "Passwort" msgid "Password"
msgstr "" msgstr "Passwort"
#: templates/base.html:47 #: templates/base.html:49
msgid "Logout" msgid "Logout"
msgstr "" msgstr "Abmelden"
#: templates/change_password.html:4 templates/change_password.html:19 #: templates/change_password.html:4 templates/change_password.html:19
msgid "Change Password" msgid "Change Password"
msgstr "" msgstr "Passwort ändern"
#: templates/change_password.html:8 #: templates/change_password.html:8
msgid "Current Password" msgid "Current Password"
msgstr "" msgstr "Aktuelles Passwort"
#: templates/change_password.html:12 #: templates/change_password.html:12
msgid "New Password" msgid "New Password"
msgstr "" msgstr "Neues Passwort"
#: templates/change_password.html:16 #: templates/change_password.html:16
msgid "Confirm New Password" msgid "Confirm New Password"
msgstr "" msgstr "Neues Passwort bestätigen"
#: templates/edit_game.html:4 #: templates/edit_game.html:4
msgid "Edit Game" msgid "Edit Game"
msgstr "" msgstr "Spiel bearbeiten"
#: templates/edit_game.html:17 #: templates/edit_game.html:17
msgid "Steam AppID (optional)" msgid "Steam AppID (optional)"
msgstr "" msgstr "Steam-AppID (optional)"
#: templates/edit_game.html:47 #: templates/edit_game.html:47
msgid "Active Redeem Link" msgid "Active Redeem Link"
msgstr "" msgstr "Aktiver Einlöse-Link"
#: templates/edit_game.html:54 #: templates/edit_game.html:54
msgid "Expires at" msgid "Expires at"
msgstr "" msgstr "Ablaufdatum"
#: templates/import.html:4 #: templates/import.html:4
msgid "Import Games" msgid "Import Games"
msgstr "" msgstr "Spiele importieren"
#: templates/import.html:8 #: templates/import.html:8
msgid "CSV-Datei auswählen" msgid "Select CSV file"
msgstr "" msgstr "CSV-Datei auswählen"
#: templates/import.html:11 #: templates/import.html:11
msgid "Importieren" msgid "Import"
msgstr "" msgstr "Importieren"
#: templates/import.html:12
msgid "Abbrechen"
msgstr ""
#: templates/index.html:4 #: templates/index.html:4
msgid "My Games" msgid "My Games"
msgstr "" msgstr "Meine Spiele"
#: templates/index.html:6 #: templates/index.html:6
msgid "Export CSV" msgid "Export CSV"
msgstr "" msgstr "CSV exportieren"
#: templates/index.html:8 #: templates/index.html:8
msgid "Import CSV" msgid "Import CSV"
msgstr "" msgstr "CSV importieren"
#: templates/index.html:18 #: templates/index.html:18
msgid "Cover" msgid "Cover"
msgstr "" msgstr "Cover"
#: templates/index.html:20 #: templates/index.html:20
msgid "Key" msgid "Key"
msgstr "" msgstr "Key"
#: templates/index.html:22 #: templates/index.html:22
msgid "Created" msgid "Created"
msgstr "" msgstr "Erstellt"
#: templates/index.html:24 templates/index.html:56 #: templates/index.html:24 templates/index.html:56
msgid "Shop" msgid "Shop"
msgstr "" msgstr "Shop"
#: templates/index.html:25 #: templates/index.html:25
msgid "Actions" msgid "Actions"
msgstr "" msgstr "Aktionen"
#: templates/index.html:63 #: templates/index.html:63
msgid "Generate redeem link" msgid "Generate redeem link"
msgstr "" msgstr "Einlöse-Link generieren"
#: templates/index.html:70 #: templates/index.html:70
msgid "Really delete?" msgid "Really delete?"
msgstr "" msgstr "Wirklich löschen?"
#: templates/index.html:96 #: templates/index.html:96
msgid "Redeem link copied to clipboard!" msgid "Redeem link copied to clipboard!"
msgstr "" msgstr "Einlöse-Link in die Zwischenablage kopiert!"
#: templates/index.html:100 #: templates/index.html:100
msgid "Error generating link" msgid "Error generating link"
msgstr "" msgstr "Fehler beim Generieren des Links"
#: templates/index.html:106 #: templates/index.html:106
msgid "No games yet" msgid "No games yet"
msgstr "" msgstr "Der Kornspeicher ist leer, Sire!"
#: templates/login.html:8 templates/login.html:19 #: templates/login.html:8 templates/login.html:19
msgid "Login" msgid "Login"
msgstr "" msgstr "Anmelden"
#: templates/login.html:12 templates/register.html:11 #: templates/login.html:12 templates/register.html:11
msgid "Username" msgid "Username"
msgstr "" msgstr "Benutzername"
#: templates/login.html:16 templates/register.html:15
msgid "Password"
msgstr ""
#: templates/login.html:22 #: templates/login.html:22
msgid "No account yet? Register" msgid "No account yet? Register"
msgstr "" msgstr "Noch kein Konto? Jetzt registrieren"
#: templates/redeem.html:16 #: templates/redeem.html:16
msgid "Your Key:" msgid "Your Key:"
msgstr "" msgstr "Dein Key:"
#: templates/redeem.html:22 #: templates/redeem.html:22
msgid "Redeem now on" msgid "Redeem now on"
msgstr "" msgstr "Jetzt einlösen bei"
#: templates/redeem.html:26 #: templates/redeem.html:26
msgid "This page will expire in" msgid "This page will expire in"
msgstr "" msgstr "Diese Seite läuft ab in"
#: templates/register.html:7 templates/register.html:18 #: templates/register.html:7 templates/register.html:18
msgid "Register" msgid "Register"
msgstr "" msgstr "Registrieren"

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-04-26 11:13+0000\n" "POT-Creation-Date: 2025-04-29 15:53+0000\n"
"PO-Revision-Date: 2025-04-26 11:13+0000\n" "PO-Revision-Date: 2025-04-29 15:42+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n" "Language: en\n"
"Language-Team: en <LL@li.org>\n" "Language-Team: en <LL@li.org>\n"
@ -18,62 +18,62 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n" "Generated-By: Babel 2.17.0\n"
#: app.py:187 #: app.py:194
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
#: app.py:193 #: app.py:200
msgid "Registrierungen sind deaktiviert" msgid "No new registrations. They are deactivated!"
msgstr "" msgstr ""
#: app.py:201 #: app.py:208
msgid "Username already exists" msgid "Username already exists"
msgstr "" msgstr ""
#: app.py:227 #: app.py:234
msgid "Aktuelles Passwort ist falsch" msgid "Current passwort is wrong"
msgstr "" msgstr ""
#: app.py:231 #: app.py:238
msgid "Neue Passwörter stimmen nicht überein" msgid "New Passwords are not matching"
msgstr "" msgstr ""
#: app.py:236 #: app.py:243
msgid "Passwort erfolgreich geändert" msgid "Password changed successfully"
msgstr "" msgstr ""
#: app.py:266 #: app.py:273
msgid "Game added successfully!" msgid "Game added successfully!"
msgstr "" msgstr ""
#: app.py:271 #: app.py:278
msgid "Steam Key already exists!" msgid "Steam Key already exists!"
msgstr "" msgstr ""
#: app.py:274 app.py:318 #: app.py:281 app.py:325
msgid "Error: " msgid "Error: "
msgstr "" msgstr ""
#: app.py:313 #: app.py:320
msgid "Changes saved!" msgid "Changes saved!"
msgstr "" msgstr ""
#: app.py:401 #: app.py:408
msgid "Game List (without Keys)" msgid "Game List (without Keys)"
msgstr "" msgstr ""
#: app.py:494 #: app.py:501
#, python-format #, python-format
msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen" msgid "%(new)d new games imported, %(dup)d skipped duplicates"
msgstr "" msgstr ""
#: app.py:498 #: app.py:505
#, python-format #, python-format
msgid "Importfehler: %(error)s" msgid "Import error: %(error)s"
msgstr "" msgstr ""
#: app.py:502 #: app.py:509
msgid "Bitte eine gültige CSV-Datei hochladen." msgid "Please upload a valid CSV file."
msgstr "" msgstr ""
#: templates/add_game.html:4 templates/index.html:9 #: templates/add_game.html:4 templates/index.html:9
@ -130,6 +130,7 @@ msgid "Save"
msgstr "" msgstr ""
#: templates/add_game.html:42 templates/edit_game.html:61 #: templates/add_game.html:42 templates/edit_game.html:61
#: templates/import.html:12
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -145,11 +146,11 @@ msgstr ""
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr ""
#: templates/base.html:44 #: templates/base.html:46 templates/login.html:16 templates/register.html:15
msgid "Passwort" msgid "Password"
msgstr "" msgstr ""
#: templates/base.html:47 #: templates/base.html:49
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
@ -190,15 +191,11 @@ msgid "Import Games"
msgstr "" msgstr ""
#: templates/import.html:8 #: templates/import.html:8
msgid "CSV-Datei auswählen" msgid "Select CSV file"
msgstr "" msgstr ""
#: templates/import.html:11 #: templates/import.html:11
msgid "Importieren" msgid "Import"
msgstr ""
#: templates/import.html:12
msgid "Abbrechen"
msgstr "" msgstr ""
#: templates/index.html:4 #: templates/index.html:4
@ -261,10 +258,6 @@ msgstr ""
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: templates/login.html:16 templates/register.html:15
msgid "Password"
msgstr ""
#: templates/login.html:22 #: templates/login.html:22
msgid "No account yet? Register" msgid "No account yet? Register"
msgstr "" msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-04-26 11:13+0000\n" "POT-Creation-Date: 2025-04-29 15:53+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,62 +17,62 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.17.0\n" "Generated-By: Babel 2.17.0\n"
#: app.py:187 #: app.py:194
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
#: app.py:193 #: app.py:200
msgid "Registrierungen sind deaktiviert" msgid "No new registrations. They are deactivated!"
msgstr "" msgstr ""
#: app.py:201 #: app.py:208
msgid "Username already exists" msgid "Username already exists"
msgstr "" msgstr ""
#: app.py:227 #: app.py:234
msgid "Aktuelles Passwort ist falsch" msgid "Current passwort is wrong"
msgstr "" msgstr ""
#: app.py:231 #: app.py:238
msgid "Neue Passwörter stimmen nicht überein" msgid "New Passwords are not matching"
msgstr "" msgstr ""
#: app.py:236 #: app.py:243
msgid "Passwort erfolgreich geändert" msgid "Password changed successfully"
msgstr "" msgstr ""
#: app.py:266 #: app.py:273
msgid "Game added successfully!" msgid "Game added successfully!"
msgstr "" msgstr ""
#: app.py:271 #: app.py:278
msgid "Steam Key already exists!" msgid "Steam Key already exists!"
msgstr "" msgstr ""
#: app.py:274 app.py:318 #: app.py:281 app.py:325
msgid "Error: " msgid "Error: "
msgstr "" msgstr ""
#: app.py:313 #: app.py:320
msgid "Changes saved!" msgid "Changes saved!"
msgstr "" msgstr ""
#: app.py:401 #: app.py:408
msgid "Game List (without Keys)" msgid "Game List (without Keys)"
msgstr "" msgstr ""
#: app.py:494 #: app.py:501
#, python-format #, python-format
msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen" msgid "%(new)d new games imported, %(dup)d skipped duplicates"
msgstr "" msgstr ""
#: app.py:498 #: app.py:505
#, python-format #, python-format
msgid "Importfehler: %(error)s" msgid "Import error: %(error)s"
msgstr "" msgstr ""
#: app.py:502 #: app.py:509
msgid "Bitte eine gültige CSV-Datei hochladen." msgid "Please upload a valid CSV file."
msgstr "" msgstr ""
#: templates/add_game.html:4 templates/index.html:9 #: templates/add_game.html:4 templates/index.html:9
@ -129,6 +129,7 @@ msgid "Save"
msgstr "" msgstr ""
#: templates/add_game.html:42 templates/edit_game.html:61 #: templates/add_game.html:42 templates/edit_game.html:61
#: templates/import.html:12
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
@ -144,11 +145,11 @@ msgstr ""
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr ""
#: templates/base.html:44 #: templates/base.html:46 templates/login.html:16 templates/register.html:15
msgid "Passwort" msgid "Password"
msgstr "" msgstr ""
#: templates/base.html:47 #: templates/base.html:49
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
@ -189,15 +190,11 @@ msgid "Import Games"
msgstr "" msgstr ""
#: templates/import.html:8 #: templates/import.html:8
msgid "CSV-Datei auswählen" msgid "Select CSV file"
msgstr "" msgstr ""
#: templates/import.html:11 #: templates/import.html:11
msgid "Importieren" msgid "Import"
msgstr ""
#: templates/import.html:12
msgid "Abbrechen"
msgstr "" msgstr ""
#: templates/index.html:4 #: templates/index.html:4
@ -260,10 +257,6 @@ msgstr ""
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: templates/login.html:16 templates/register.html:15
msgid "Password"
msgstr ""
#: templates/login.html:22 #: templates/login.html:22
msgid "No account yet? Register" msgid "No account yet? Register"
msgstr "" msgstr ""

View File

@ -1,22 +1,22 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Setze das Arbeitsverzeichnis auf das Projektverzeichnis # Set the working directory to the project directory
cd "$(dirname "$0")/steam-gift-manager" cd "$(dirname "$0")/steam-gift-manager"
# Setze FLASK_APP, falls nötig # Setze FLASK_APP, falls nötig
export FLASK_APP=app.py export FLASK_APP=app.py
# Initialisiere migrations, falls noch nicht vorhanden # Initialize migrations, if not yet available
if [ ! -d migrations ]; then if [ ! -d migrations ]; then
echo "Starting Flask-Migrate..." echo "Starting Flask-Migrate..."
docker-compose exec steam-manager flask db init docker-compose exec steam-manager flask db init
fi fi
# Erzeuge Migration (nur wenn sich Modelle geändert haben) # Create migration (only if models have changed)
docker-compose exec steam-manager flask db migrate -m "Automatic Migration" docker-compose exec steam-manager flask db migrate -m "Automatic Migration"
# Wende Migration an # Apply migration
docker-compose exec steam-manager flask db upgrade docker-compose exec steam-manager flask db upgrade
echo "✅ Database-Migration abgeschlossen!" echo "✅ Database migration completed!"