Compare commits

..

No commits in common. "main" and "1.0" have entirely different histories.
main ... 1.0

16 changed files with 273 additions and 311 deletions

34
.env
View File

@ -1,34 +0,0 @@
# 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,16 +50,7 @@ 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 (Option 1 or Option 2)** ### 1. **Clone the Repository**
### 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

162
setup.sh
View File

@ -1,13 +1,13 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Colors # Color definitions
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 (incl. Arch Linux) # 1. Docker check (including 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
# Docker group membership prüfen # Check Docker group membership
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. Check Docker compose (V1 und V2 Plugin, incl. Arch Support) # 2. Docker Compose check (V1 and V2 plugin, with 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,7 +52,6 @@ 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
@ -74,12 +73,13 @@ else
fi fi
fi fi
# Configuration
PROJECT_DIR="steam-gift-manager"
TRANSLATIONS_DIR="$PWD/translations"
DATA_DIR="$PWD/data"
# 1. Create folders # Konfiguration
PROJECT_DIR="steam-gift-manager"
TRANSLATIONS_DIR="../translations"
DATA_DIR="../data"
# 1. Projektordner & Übersetzungsordner erstellen
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,10 +107,9 @@ matrix-client
reportlab reportlab
requests requests
pillow pillow
gunicorn
EOL EOL
# 3. .env Datei in Parent-VFolder # 3. .env-Datei im übergeordneten Verzeichnis erstellen
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))')
@ -153,11 +152,13 @@ MATRIX_ACCESS_TOKEN=""
MATRIX_ROOM_ID="" MATRIX_ROOM_ID=""
EOL EOL
# Zurück ins Projektverzeichnis
cd $PROJECT_DIR cd $PROJECT_DIR
# 4. app.py (the main app) # 4. app.py (vollständige korrigierte Version)
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)
@ -199,11 +200,8 @@ 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 = {
@ -220,27 +218,22 @@ load_dotenv(override=True)
# Lade Umgebungsvariablen aus .env mit override # Lade Umgebungsvariablen aus .env mit override
load_dotenv(override=True) load_dotenv(override=True)
# App-Configuration # Konfiguration
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', 'en'), BABEL_DEFAULT_LOCALE=os.getenv('BABEL_DEFAULT_LOCALE'),
BABEL_SUPPORTED_LOCALES=os.getenv('BABEL_SUPPORTED_LOCALES', 'de,en').split(','), BABEL_SUPPORTED_LOCALES=os.getenv('BABEL_SUPPORTED_LOCALES').split(','),
BABEL_TRANSLATION_DIRECTORIES=os.path.join(app.root_path, 'translations'), BABEL_TRANSLATION_DIRECTORIES=os.getenv('BABEL_TRANSLATION_DIRECTORIES'),
SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE', 'False') == 'True', SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE') == 'True',
SESSION_COOKIE_SAMESITE='Lax', WTF_CSRF_ENABLED=os.getenv('CSRF_ENABLED') == 'True',
PERMANENT_SESSION_LIFETIME=timedelta(days=30), REGISTRATION_ENABLED=os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true'
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))
# Initialisation # Initialisierung
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)
@ -257,11 +250,6 @@ 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(
@ -269,7 +257,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'
) )
# DB Models # Datenbankmodelle
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)
@ -361,7 +349,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(_('No new registrations. They are deactivated!'), 'danger') flash(_('Registrierungen sind deaktiviert'), 'danger')
return redirect(url_for('login')) return redirect(url_for('login'))
if request.method == 'POST': if request.method == 'POST':
@ -395,16 +383,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(_('Current passwort is wrong'), 'danger') flash(_('Aktuelles Passwort ist falsch'), 'danger')
return redirect(url_for('change_password')) return redirect(url_for('change_password'))
if new_password != confirm_password: if new_password != confirm_password:
flash(_('New Passwords are not matching'), 'danger') flash(_('Neue Passwörter stimmen nicht überein'), '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(_('Password changed successfully'), 'success') flash(_('Passwort erfolgreich geändert'), 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('change_password.html') return render_template('change_password.html')
@ -600,7 +588,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 ''
]) ])
# Table format # Tabelle formatieren
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'),
@ -662,15 +650,15 @@ def import_games():
db.session.commit() db.session.commit()
flash(_('%(new)d new games imported, %(dup)d skipped duplicates', new=new_games, dup=duplicates), 'success') flash(_('%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen', new=new_games, dup=duplicates), 'success')
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
flash(_('Import error: %(error)s', error=str(e)), 'danger') flash(_('Importfehler: %(error)s', error=str(e)), 'danger')
return redirect(url_for('index')) return redirect(url_for('index'))
flash(_('Please upload a valid CSV file.'), 'danger') flash(_('Bitte eine gültige CSV-Datei hochladen.'), 'danger')
return render_template('import.html') return render_template('import.html')
@ -728,13 +716,6 @@ 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"""
@ -831,7 +812,7 @@ def check_expiring_keys():
send_notification(user, game) send_notification(user, game)
# Optional: cleaning up old tokens # Optional: Cleanup-Funktion für regelmäßiges Löschen abgelaufener 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()
@ -840,13 +821,13 @@ def cleanup_expired_tokens():
db.session.commit() db.session.commit()
# Scheduler start # Scheduler initialisieren und starten
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 of the Schedulers when stopping the app # Shutdown des Schedulers bei Beendigung der App
atexit.register(lambda: scheduler.shutdown()) atexit.register(lambda: scheduler.shutdown())
if __name__ == '__main__': if __name__ == '__main__':
@ -856,7 +837,7 @@ if __name__ == '__main__':
PYTHON_END PYTHON_END
# Create Babel configuration # Babel Konfiguration erstellen
cat <<EOL > babel.cfg cat <<EOL > babel.cfg
[python: **.py] [python: **.py]
[jinja2: **/templates/**.html] [jinja2: **/templates/**.html]
@ -898,8 +879,7 @@ 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
@ -914,44 +894,46 @@ services:
- TZ=${TZ} - TZ=${TZ}
volumes: volumes:
- ../data:/app/data - ../data:/app/data
- ../translations:/app/translations:rw - ../translations:/app/translations
- ../.env:/app/.env - ../.env:/app/.env
user: "${UID}:${GID}" user: "1000:1000"
restart: unless-stopped restart: unless-stopped
COMPOSE_END COMPOSE_END
# 7. Directories and permissions # 7. Verzeichnisse und Berechtigungen
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. Translation and upgrade scripts # 8. Übersetzungs- und Upgrade-Scripte
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=("de" "en") declare -A locales=(
["de"]="de"
["en"]="en"
)
# 1. POT-Datei aktualisieren # POT-Datei erstellen
docker-compose run --rm 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 .
# 2. PO files for each language # Für jede Sprache prüfen und ggf. initialisieren
for lang in "${locales[@]}"; do for lang in "${!locales[@]}"; do
docker-compose run --rm steam-manager pybabel update \ if [ ! -f "translations/${locales[$lang]}/LC_MESSAGES/messages.po" ]; then
-i translations/messages.pot \ docker-compose exec steam-manager pybabel init \
-d translations \ -i translations/messages.pot \
-l $lang --previous -d translations \
-l "${locales[$lang]}"
fi
done done
# 3. Compile MO files (without fuzzy entries) # Übersetzungen aktualisieren und kompilieren
docker-compose run --rm steam-manager pybabel compile -d translations docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations
docker-compose exec steam-manager pybabel compile -d translations
echo "✅ Translations successfully updated!" echo "✅ Übersetzungen aktualisiert!"
SCRIPT_END SCRIPT_END
chmod +x ../translate.sh chmod +x ../translate.sh
@ -959,25 +941,25 @@ cat <<'SCRIPT_END' > ../upgrade.sh
#!/bin/bash #!/bin/bash
set -e set -e
# Set the working directory to the project directory # Setze das Arbeitsverzeichnis auf das Projektverzeichnis
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
# Initialize migrations, if not yet available # Initialisiere migrations, falls noch nicht vorhanden
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
# Create migration (only if models have changed) # Erzeuge Migration (nur wenn sich Modelle geändert haben)
docker-compose exec steam-manager flask db migrate -m "Automatic Migration" docker-compose exec steam-manager flask db migrate -m "Automatic Migration"
# Apply migration # Wende Migration an
docker-compose exec steam-manager flask db upgrade docker-compose exec steam-manager flask db upgrade
echo "✅ Database migration completed!" echo "✅ Database-Migration abgeschlossen!"
SCRIPT_END SCRIPT_END
chmod +x ../upgrade.sh chmod +x ../upgrade.sh
@ -1020,8 +1002,6 @@ 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>
@ -1032,7 +1012,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') }}">🔒 {{ _('Password') }}</a> <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Passwort') }}</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>
@ -1401,11 +1381,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">{{ _('Select CSV file') }}</label> <label class="form-label">{{ _('CSV-Datei auswählen') }}</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">{{ _('Import') }}</button> <button type="submit" class="btn btn-success">{{ _('Importieren') }}</button>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a> <a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Abbrechen') }}</a>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
@ -1499,7 +1479,7 @@ function updateCountdown() {
updateProgressBar(percent); updateProgressBar(percent);
} }
// run countdown // Initialisierung
updateCountdown(); updateCountdown();
const timer = setInterval(updateCountdown, 1000); const timer = setInterval(updateCountdown, 1000);
</script> </script>
@ -1575,7 +1555,7 @@ body {
color: #ff6b6b; color: #ff6b6b;
} }
/* Progressbar-Animations */ /* Progressbar-Animationen */
#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,5 +23,4 @@ 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)
# App-Configuration # Konfiguration
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))
# Initialisation # Initialisierung
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'
) )
# DB Models # Datenbankmodelle
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 ''
]) ])
# Table format # Tabelle formatieren
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: cleaning up old tokens # Optional: Cleanup-Funktion für regelmäßiges Löschen abgelaufener 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 start # Scheduler initialisieren und starten
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 of the Schedulers when stopping the app # Shutdown des Schedulers bei Beendigung der App
atexit.register(lambda: scheduler.shutdown()) atexit.register(lambda: scheduler.shutdown())
if __name__ == '__main__': if __name__ == '__main__':

View File

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

View File

@ -42,7 +42,7 @@ body {
color: #ff6b6b; color: #ff6b6b;
} }
/* Progressbar-Animations */ /* Progressbar-Animationen */
#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="266" height="206" class="mb-4" style="object-fit:contain;"> <img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="311" height="240" 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);
} }
// run countdown // Initialisierung
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"
) )
# create POT-file # POT-Datei erstellen
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 .
# Check for each language and initialize if necessary # Für jede Sprache prüfen und ggf. initialisieren
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
# Update and compile translations # Übersetzungen aktualisieren und kompilieren
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 "✅ Translations updated!" echo "✅ Übersetzungen aktualisiert!"

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

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-29 15:53+0000\n" "POT-Creation-Date: 2025-04-26 11:13+0000\n"
"PO-Revision-Date: 2025-04-29 15:42+0000\n" "PO-Revision-Date: 2025-04-26 11:13+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:194 #: app.py:187
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
#: app.py:200 #: app.py:193
msgid "No new registrations. They are deactivated!" msgid "Registrierungen sind deaktiviert"
msgstr "" msgstr ""
#: app.py:208 #: app.py:201
msgid "Username already exists" msgid "Username already exists"
msgstr "" msgstr ""
#: app.py:234 #: app.py:227
msgid "Current passwort is wrong" msgid "Aktuelles Passwort ist falsch"
msgstr "" msgstr ""
#: app.py:238 #: app.py:231
msgid "New Passwords are not matching" msgid "Neue Passwörter stimmen nicht überein"
msgstr "" msgstr ""
#: app.py:243 #: app.py:236
msgid "Password changed successfully" msgid "Passwort erfolgreich geändert"
msgstr "" msgstr ""
#: app.py:273 #: app.py:266
msgid "Game added successfully!" msgid "Game added successfully!"
msgstr "" msgstr ""
#: app.py:278 #: app.py:271
msgid "Steam Key already exists!" msgid "Steam Key already exists!"
msgstr "" msgstr ""
#: app.py:281 app.py:325 #: app.py:274 app.py:318
msgid "Error: " msgid "Error: "
msgstr "" msgstr ""
#: app.py:320 #: app.py:313
msgid "Changes saved!" msgid "Changes saved!"
msgstr "" msgstr ""
#: app.py:408 #: app.py:401
msgid "Game List (without Keys)" msgid "Game List (without Keys)"
msgstr "" msgstr ""
#: app.py:501 #: app.py:494
#, python-format #, python-format
msgid "%(new)d new games imported, %(dup)d skipped duplicates" msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
msgstr "" msgstr ""
#: app.py:505 #: app.py:498
#, python-format #, python-format
msgid "Import error: %(error)s" msgid "Importfehler: %(error)s"
msgstr "" msgstr ""
#: app.py:509 #: app.py:502
msgid "Please upload a valid CSV file." msgid "Bitte eine gültige CSV-Datei hochladen."
msgstr "" msgstr ""
#: templates/add_game.html:4 templates/index.html:9 #: templates/add_game.html:4 templates/index.html:9
@ -130,7 +130,6 @@ 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 ""
@ -146,11 +145,11 @@ msgstr ""
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr ""
#: templates/base.html:46 templates/login.html:16 templates/register.html:15 #: templates/base.html:44
msgid "Password" msgid "Passwort"
msgstr "" msgstr ""
#: templates/base.html:49 #: templates/base.html:47
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
@ -191,11 +190,15 @@ msgid "Import Games"
msgstr "" msgstr ""
#: templates/import.html:8 #: templates/import.html:8
msgid "Select CSV file" msgid "CSV-Datei auswählen"
msgstr "" msgstr ""
#: templates/import.html:11 #: templates/import.html:11
msgid "Import" msgid "Importieren"
msgstr ""
#: templates/import.html:12
msgid "Abbrechen"
msgstr "" msgstr ""
#: templates/index.html:4 #: templates/index.html:4
@ -258,6 +261,10 @@ 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-29 15:53+0000\n" "POT-Creation-Date: 2025-04-26 11:13+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:194 #: app.py:187
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr ""
#: app.py:200 #: app.py:193
msgid "No new registrations. They are deactivated!" msgid "Registrierungen sind deaktiviert"
msgstr "" msgstr ""
#: app.py:208 #: app.py:201
msgid "Username already exists" msgid "Username already exists"
msgstr "" msgstr ""
#: app.py:234 #: app.py:227
msgid "Current passwort is wrong" msgid "Aktuelles Passwort ist falsch"
msgstr "" msgstr ""
#: app.py:238 #: app.py:231
msgid "New Passwords are not matching" msgid "Neue Passwörter stimmen nicht überein"
msgstr "" msgstr ""
#: app.py:243 #: app.py:236
msgid "Password changed successfully" msgid "Passwort erfolgreich geändert"
msgstr "" msgstr ""
#: app.py:273 #: app.py:266
msgid "Game added successfully!" msgid "Game added successfully!"
msgstr "" msgstr ""
#: app.py:278 #: app.py:271
msgid "Steam Key already exists!" msgid "Steam Key already exists!"
msgstr "" msgstr ""
#: app.py:281 app.py:325 #: app.py:274 app.py:318
msgid "Error: " msgid "Error: "
msgstr "" msgstr ""
#: app.py:320 #: app.py:313
msgid "Changes saved!" msgid "Changes saved!"
msgstr "" msgstr ""
#: app.py:408 #: app.py:401
msgid "Game List (without Keys)" msgid "Game List (without Keys)"
msgstr "" msgstr ""
#: app.py:501 #: app.py:494
#, python-format #, python-format
msgid "%(new)d new games imported, %(dup)d skipped duplicates" msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
msgstr "" msgstr ""
#: app.py:505 #: app.py:498
#, python-format #, python-format
msgid "Import error: %(error)s" msgid "Importfehler: %(error)s"
msgstr "" msgstr ""
#: app.py:509 #: app.py:502
msgid "Please upload a valid CSV file." msgid "Bitte eine gültige CSV-Datei hochladen."
msgstr "" msgstr ""
#: templates/add_game.html:4 templates/index.html:9 #: templates/add_game.html:4 templates/index.html:9
@ -129,7 +129,6 @@ 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 +144,11 @@ msgstr ""
msgid "Dark Mode" msgid "Dark Mode"
msgstr "" msgstr ""
#: templates/base.html:46 templates/login.html:16 templates/register.html:15 #: templates/base.html:44
msgid "Password" msgid "Passwort"
msgstr "" msgstr ""
#: templates/base.html:49 #: templates/base.html:47
msgid "Logout" msgid "Logout"
msgstr "" msgstr ""
@ -190,11 +189,15 @@ msgid "Import Games"
msgstr "" msgstr ""
#: templates/import.html:8 #: templates/import.html:8
msgid "Select CSV file" msgid "CSV-Datei auswählen"
msgstr "" msgstr ""
#: templates/import.html:11 #: templates/import.html:11
msgid "Import" msgid "Importieren"
msgstr ""
#: templates/import.html:12
msgid "Abbrechen"
msgstr "" msgstr ""
#: templates/index.html:4 #: templates/index.html:4
@ -257,6 +260,10 @@ 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
# Set the working directory to the project directory # Setze das Arbeitsverzeichnis auf das Projektverzeichnis
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
# Initialize migrations, if not yet available # Initialisiere migrations, falls noch nicht vorhanden
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
# Create migration (only if models have changed) # Erzeuge Migration (nur wenn sich Modelle geändert haben)
docker-compose exec steam-manager flask db migrate -m "Automatic Migration" docker-compose exec steam-manager flask db migrate -m "Automatic Migration"
# Apply migration # Wende Migration an
docker-compose exec steam-manager flask db upgrade docker-compose exec steam-manager flask db upgrade
echo "✅ Database migration completed!" echo "✅ Database-Migration abgeschlossen!"