import time import os import threading from flask import Blueprint, request, redirect, url_for, flash, render_template, send_file, jsonify, current_app from flask_login import login_user, logout_user, login_required, current_user from werkzeug.utils import secure_filename from werkzeug.security import generate_password_hash, check_password_hash from .models import db, User, Job, AppConfig from .webcrawler import process_file UPLOAD_FOLDER = '/app/uploads' RESULT_FOLDER = '/app/results' bp = Blueprint('auth', __name__) @bp.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] user = User.query.filter_by(username=username).first() if user and check_password_hash(user.password, password): login_user(user) return redirect(url_for('auth.job_status')) flash('Login fehlgeschlagen. Überprüfen Sie Benutzername und Passwort.') return render_template('login.html') @bp.route('/signup', methods=['GET', 'POST']) def signup(): cfg = AppConfig.query.filter_by(key='allow_signup').first() if not cfg or cfg.value != 'true': flash("Registrierung ist derzeit deaktiviert.") return redirect(url_for('auth.login')) if request.method == 'POST': username = request.form['username'] password = generate_password_hash(request.form['password']) # ✅ Fix new_user = User(username=username, password=password) db.session.add(new_user) db.session.commit() flash('Benutzer erfolgreich erstellt!') return redirect(url_for('auth.login')) return render_template('signup.html') @bp.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('auth.login')) @bp.route('/jobs') @login_required def job_status(): jobs = Job.query.filter_by(user_id=current_user.id).order_by(Job.created_at.desc()).all() return render_template('jobs.html', jobs=jobs) @bp.route('/upload', methods=['GET', 'POST']) @login_required def upload(): if request.method == 'POST': if 'file' not in request.files: flash('Keine Datei ausgewählt.') return redirect(url_for('auth.upload')) file = request.files['file'] if not file or file.filename == '': flash('Keine gültige Datei.') return redirect(url_for('auth.upload')) filename = secure_filename(file.filename) name, ext = os.path.splitext(filename) timestamp = time.strftime("%Y%m%d_%H%M%S") unique_filename = f"{name}_{timestamp}{ext}" if os.path.exists(os.path.join(UPLOAD_FOLDER, filename)) else filename filepath = os.path.join(UPLOAD_FOLDER, unique_filename) file.save(filepath) print(f"💾 UPLOAD: {filepath}") new_job = Job( user_id=current_user.id, filename=unique_filename, status="Pending" ) db.session.add(new_job) db.session.commit() print(f"🆕 JOB #{new_job.id} für User {current_user.id}") thread = threading.Thread( target=process_file, args=(unique_filename, new_job.id, current_app._get_current_object()) ) thread.daemon = True thread.start() print(f"🔄 THREAD STARTED Job {new_job.id}") flash(f'"{unique_filename}" → Job #{new_job.id} läuft!') return redirect(url_for('auth.job_status')) return render_template('upload.html') @bp.route('/download/') @login_required def download_result(job_id): job = Job.query.filter_by(id=job_id, user_id=current_user.id).first_or_404() if not job.result_filename or not job.status.startswith('✅'): flash('Ergebnis nicht bereit.') return redirect(url_for('auth.job_status')) result_path = os.path.join(RESULT_FOLDER, job.result_filename) if os.path.exists(result_path): return send_file(result_path, as_attachment=True) flash('Datei fehlt.') return redirect(url_for('auth.job_status')) @bp.route('/download_raw/') @login_required def download_result_raw(job_id): job = Job.query.filter_by(id=job_id, user_id=current_user.id).first_or_404() if not job.result_filename_raw: flash('Rohdaten nicht verfügbar.') return redirect(url_for('auth.job_status')) result_path = os.path.join(RESULT_FOLDER, job.result_filename_raw) if os.path.exists(result_path): return send_file(result_path, as_attachment=True) flash('Datei fehlt.') return redirect(url_for('auth.job_status')) @bp.route('/delete_job/', methods=['POST']) @login_required def delete_job(job_id): job = Job.query.filter_by(id=job_id, user_id=current_user.id).first_or_404() upload_path = os.path.join(UPLOAD_FOLDER, job.filename) if os.path.exists(upload_path): os.remove(upload_path) if job.result_filename: result_path = os.path.join(RESULT_FOLDER, job.result_filename) if os.path.exists(result_path): os.remove(result_path) if job.result_filename_raw: # ✅ Raw auch löschen raw_path = os.path.join(RESULT_FOLDER, job.result_filename_raw) if os.path.exists(raw_path): os.remove(raw_path) db.session.delete(job) db.session.commit() flash('Job gelöscht.') return redirect(url_for('auth.job_status')) @bp.route('/job_status/') @login_required def job_status_api(job_id): job = Job.query.filter_by(id=job_id, user_id=current_user.id).first() if not job: return jsonify({'error': 'Not found'}), 404 return jsonify({ 'id': job.id, 'status': job.status, 'result_filename': job.result_filename, 'result_filename_raw': getattr(job, 'result_filename_raw', None), 'scraper_job_id': getattr(job, 'scraper_job_id', None) }) @bp.route('/resume_job/', methods=['POST']) @login_required def resume_job(job_id): job = Job.query.filter_by(id=job_id, user_id=current_user.id).first_or_404() thread = threading.Thread( target=process_file, args=(job.filename, job.id, current_app._get_current_object()) ) thread.daemon = True thread.start() flash(f'Job #{job_id} wird fortgesetzt...') return redirect(url_for('auth.job_status')) # ── ADMIN ────────────────────────────────────────── @bp.route('/admin', methods=['GET']) @login_required def admin_panel(): if not current_user.is_admin: flash("Keine Berechtigung.") return redirect(url_for('auth.job_status')) users = User.query.all() cfg = AppConfig.query.filter_by(key='allow_signup').first() signup_allowed = cfg and cfg.value == 'true' return render_template('admin_panel.html', users=users, signup_allowed=signup_allowed) @bp.route('/admin/create_user', methods=['POST']) @login_required def create_user(): if not current_user.is_admin: return redirect(url_for('auth.admin_panel')) username = request.form['username'] password = generate_password_hash(request.form['password']) # ✅ Fix is_admin = 'is_admin' in request.form new_user = User(username=username, password=password, is_admin=is_admin) db.session.add(new_user) db.session.commit() flash(f'{username} erstellt.') return redirect(url_for('auth.admin_panel')) @bp.route('/admin/reset_password/', methods=['POST']) @login_required def reset_password(user_id): if not current_user.is_admin: return redirect(url_for('auth.admin_panel')) user = User.query.get_or_404(user_id) new_password = request.form['new_password'] user.password = generate_password_hash(new_password) # ✅ Fix db.session.commit() flash(f'Passwort {user.username} zurückgesetzt.') return redirect(url_for('auth.admin_panel')) @bp.route('/admin/delete_user/', methods=['POST']) @login_required def delete_user(user_id): if not current_user.is_admin: return redirect(url_for('auth.admin_panel')) user = User.query.get_or_404(user_id) if user.is_admin: flash('Admin nicht löschbar.') return redirect(url_for('auth.admin_panel')) db.session.delete(user) db.session.commit() flash(f'{user.username} gelöscht.') return redirect(url_for('auth.admin_panel')) @bp.route('/admin/toggle_signup', methods=['POST']) @login_required def toggle_signup(): if not current_user.is_admin: return redirect(url_for('auth.admin_panel')) cfg = AppConfig.query.filter_by(key='allow_signup').first() if not cfg: cfg = AppConfig(key='allow_signup', value='true') db.session.add(cfg) else: cfg.value = 'false' if cfg.value == 'true' else 'true' db.session.commit() state = '✅ aktiviert' if cfg.value == 'true' else '🔒 deaktiviert' flash(f'Registrierung {state}.') return redirect(url_for('auth.admin_panel'))