from datetime import datetime, date from flask import Blueprint, render_template, request, redirect, url_for, flash, Response from flask_login import login_user, logout_user, login_required, current_user from werkzeug.security import check_password_hash, generate_password_hash from .models import db, User, DocumentationEntry, DocumentationTemplate, TaskItem main_bp = Blueprint('main', __name__) def parse_date(value): if not value: return date.today() return datetime.strptime(value, '%Y-%m-%d').date() @main_bp.route('/') def index(): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) return redirect(url_for('main.login')) @main_bp.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('main.dashboard')) if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') user = User.query.filter_by(username=username).first() if user and check_password_hash(user.password_hash, password): login_user(user) flash('Anmeldung erfolgreich.', 'success') return redirect(url_for('main.dashboard')) flash('Ungültige Zugangsdaten.', 'danger') return render_template('login.html') @main_bp.route('/logout') @login_required def logout(): logout_user() flash('Du wurdest abgemeldet.', 'info') return redirect(url_for('main.login')) @main_bp.route('/dashboard') @login_required def dashboard(): today = date.today() entries_today = DocumentationEntry.query.filter_by(work_date=today).order_by(DocumentationEntry.created_at.desc()).all() open_tasks = TaskItem.query.filter(TaskItem.status != 'erledigt').order_by(TaskItem.priority.desc(), TaskItem.due_date.asc()).all() latest_entries = DocumentationEntry.query.order_by(DocumentationEntry.updated_at.desc()).limit(8).all() stats = { 'entries_total': DocumentationEntry.query.count(), 'entries_today': len(entries_today), 'templates_total': DocumentationTemplate.query.count(), 'open_tasks': len(open_tasks), } return render_template('dashboard.html', today=today, entries_today=entries_today, open_tasks=open_tasks, latest_entries=latest_entries, stats=stats) @main_bp.route('/entries') @login_required def entries(): q = request.args.get('q', '').strip() status = request.args.get('status', '').strip() category = request.args.get('category', '').strip() query = DocumentationEntry.query if q: like = f'%{q}%' query = query.filter( db.or_( DocumentationEntry.title.ilike(like), DocumentationEntry.system_name.ilike(like), DocumentationEntry.content.ilike(like), DocumentationEntry.tags.ilike(like), ) ) if status: query = query.filter_by(status=status) if category: query = query.filter_by(category=category) items = query.order_by(DocumentationEntry.work_date.desc(), DocumentationEntry.updated_at.desc()).all() categories = [c[0] for c in db.session.query(DocumentationEntry.category).distinct().order_by(DocumentationEntry.category).all()] return render_template('entries.html', items=items, q=q, status=status, category=category, categories=categories) @main_bp.route('/entries/new', methods=['GET', 'POST']) @login_required def entry_new(): templates = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all() if request.method == 'POST': entry = DocumentationEntry( title=request.form.get('title', '').strip(), work_date=parse_date(request.form.get('work_date')), category=request.form.get('category', '').strip() or 'Allgemein', system_name=request.form.get('system_name', '').strip(), priority=request.form.get('priority', 'mittel').strip(), status=request.form.get('status', 'offen').strip(), content=request.form.get('content', '').strip(), tags=request.form.get('tags', '').strip(), created_by=current_user.display_name, ) if not entry.title or not entry.content: flash('Titel und Inhalt sind Pflichtfelder.', 'danger') return render_template('entry_form.html', templates=templates, entry=None) db.session.add(entry) db.session.commit() flash('Dokumentationseintrag wurde erstellt.', 'success') return redirect(url_for('main.entries')) return render_template('entry_form.html', templates=templates, entry=None) @main_bp.route('/entries//edit', methods=['GET', 'POST']) @login_required def entry_edit(entry_id): entry = DocumentationEntry.query.get_or_404(entry_id) templates = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all() if request.method == 'POST': entry.title = request.form.get('title', '').strip() entry.work_date = parse_date(request.form.get('work_date')) entry.category = request.form.get('category', '').strip() or 'Allgemein' entry.system_name = request.form.get('system_name', '').strip() entry.priority = request.form.get('priority', 'mittel').strip() entry.status = request.form.get('status', 'offen').strip() entry.content = request.form.get('content', '').strip() entry.tags = request.form.get('tags', '').strip() if not entry.title or not entry.content: flash('Titel und Inhalt sind Pflichtfelder.', 'danger') return render_template('entry_form.html', templates=templates, entry=entry) db.session.commit() flash('Eintrag aktualisiert.', 'success') return redirect(url_for('main.entries')) return render_template('entry_form.html', templates=templates, entry=entry) @main_bp.route('/entries//delete', methods=['POST']) @login_required def entry_delete(entry_id): entry = DocumentationEntry.query.get_or_404(entry_id) db.session.delete(entry) db.session.commit() flash('Eintrag gelöscht.', 'info') return redirect(url_for('main.entries')) @main_bp.route('/entries/') @login_required def entry_view(entry_id): entry = DocumentationEntry.query.get_or_404(entry_id) return render_template('entry_view.html', entry=entry) @main_bp.route('/templates', methods=['GET', 'POST']) @login_required def templates(): if request.method == 'POST': name = request.form.get('name', '').strip() content = request.form.get('content', '').strip() description = request.form.get('description', '').strip() if name and content: db.session.add(DocumentationTemplate(name=name, description=description, content=content)) db.session.commit() flash('Vorlage gespeichert.', 'success') return redirect(url_for('main.templates')) flash('Name und Inhalt sind Pflichtfelder.', 'danger') items = DocumentationTemplate.query.order_by(DocumentationTemplate.name).all() return render_template('templates.html', items=items) @main_bp.route('/templates//delete', methods=['POST']) @login_required def template_delete(template_id): item = DocumentationTemplate.query.get_or_404(template_id) db.session.delete(item) db.session.commit() flash('Vorlage gelöscht.', 'info') return redirect(url_for('main.templates')) @main_bp.route('/tasks', methods=['GET', 'POST']) @login_required def tasks(): if request.method == 'POST': task = TaskItem( title=request.form.get('title', '').strip(), description=request.form.get('description', '').strip(), due_date=parse_date(request.form.get('due_date')) if request.form.get('due_date') else None, status=request.form.get('status', 'offen').strip(), priority=request.form.get('priority', 'mittel').strip(), ) if not task.title: flash('Titel ist ein Pflichtfeld.', 'danger') else: db.session.add(task) db.session.commit() flash('Aufgabe gespeichert.', 'success') return redirect(url_for('main.tasks')) items = TaskItem.query.order_by(TaskItem.status.asc(), TaskItem.priority.desc(), TaskItem.due_date.asc()).all() return render_template('tasks.html', items=items) @main_bp.route('/tasks//toggle', methods=['POST']) @login_required def task_toggle(task_id): task = TaskItem.query.get_or_404(task_id) task.status = 'erledigt' if task.status != 'erledigt' else 'offen' db.session.commit() flash('Aufgabenstatus geändert.', 'success') return redirect(url_for('main.tasks')) @main_bp.route('/tasks//delete', methods=['POST']) @login_required def task_delete(task_id): task = TaskItem.query.get_or_404(task_id) db.session.delete(task) db.session.commit() flash('Aufgabe gelöscht.', 'info') return redirect(url_for('main.tasks')) @main_bp.route('/users', methods=['GET', 'POST']) @login_required def users(): if current_user.role != 'admin': flash('Nur Administratoren dürfen Benutzer verwalten.', 'danger') return redirect(url_for('main.dashboard')) if request.method == 'POST': username = request.form.get('username', '').strip() display_name = request.form.get('display_name', '').strip() password = request.form.get('password', '').strip() role = request.form.get('role', 'user').strip() if not username or not display_name or not password: flash('Bitte alle Pflichtfelder füllen.', 'danger') elif User.query.filter_by(username=username).first(): flash('Benutzername existiert bereits.', 'warning') else: db.session.add(User( username=username, display_name=display_name, role=role, password_hash=generate_password_hash(password) )) db.session.commit() flash('Benutzer angelegt.', 'success') return redirect(url_for('main.users')) items = User.query.order_by(User.username).all() return render_template('users.html', items=items) @main_bp.route('/export/markdown') @login_required def export_markdown(): items = DocumentationEntry.query.order_by(DocumentationEntry.work_date.desc(), DocumentationEntry.id.desc()).all() lines = ['# Export Dokumentation', ''] for item in items: lines.extend([ f'## {item.title}', f'- Datum: {item.work_date.isoformat()}', f'- Kategorie: {item.category}', f'- System: {item.system_name or "-"}', f'- Priorität: {item.priority}', f'- Status: {item.status}', f'- Tags: {item.tags or "-"}', f'- Erstellt von: {item.created_by}', '', item.content, '', '---', '' ]) content = '\n'.join(lines) return Response( content, mimetype='text/markdown', headers={'Content-Disposition': 'attachment; filename=dokumentation_export.md'} )