nordabiz/blueprints/admin/routes_zopk_timeline.py
Maciej Pienczyn 4181a2e760 refactor: Migrate access control from is_admin to role-based system
Replace ~170 manual `if not current_user.is_admin` checks with:
- @role_required(SystemRole.ADMIN) for user management, security, ZOPK
- @role_required(SystemRole.OFFICE_MANAGER) for content management
- current_user.can_access_admin_panel() for admin UI access
- current_user.can_moderate_forum() for forum moderation
- current_user.can_edit_company(id) for company permissions

Add @office_manager_required decorator shortcut.
Add SQL migration to sync existing users' role field.

Role hierarchy: UNAFFILIATED(10) < MEMBER(20) < EMPLOYEE(30) < MANAGER(40) < OFFICE_MANAGER(50) < ADMIN(100)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:05:22 +01:00

208 lines
6.7 KiB
Python

"""
ZOPK Timeline Routes - Admin blueprint
Migrated from app.py as part of the blueprint refactoring.
Contains routes for ZOPK timeline and milestones management.
"""
import logging
from datetime import datetime
from flask import flash, jsonify, redirect, render_template, request, url_for
from flask_login import current_user, login_required
from database import (
SessionLocal,
SystemRole,
ZOPKMilestone
)
from utils.decorators import role_required
from . import bp
logger = logging.getLogger(__name__)
@bp.route('/zopk/timeline')
@login_required
@role_required(SystemRole.ADMIN)
def admin_zopk_timeline():
"""Panel Timeline ZOPK."""
return render_template('admin/zopk_timeline.html')
@bp.route('/zopk-api/milestones')
@login_required
def api_zopk_milestones():
"""API - lista kamieni milowych ZOPK."""
db = SessionLocal()
try:
milestones = db.query(ZOPKMilestone).order_by(ZOPKMilestone.target_date).all()
return jsonify({
'success': True,
'milestones': [{
'id': m.id,
'title': m.title,
'description': m.description,
'category': m.category,
'target_date': m.target_date.isoformat() if m.target_date else None,
'actual_date': m.actual_date.isoformat() if m.actual_date else None,
'status': m.status,
'source_url': m.source_url
} for m in milestones]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_create():
"""API - utworzenie kamienia milowego."""
db = SessionLocal()
try:
data = request.get_json()
milestone = ZOPKMilestone(
title=data['title'],
description=data.get('description'),
category=data.get('category', 'other'),
target_date=datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data.get('target_date') else None,
actual_date=datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data.get('actual_date') else None,
status=data.get('status', 'planned'),
source_url=data.get('source_url'),
source_news_id=data.get('source_news_id')
)
db.add(milestone)
db.commit()
return jsonify({'success': True, 'id': milestone.id})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['PUT'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_update(milestone_id):
"""API - aktualizacja kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
data = request.get_json()
if 'title' in data:
milestone.title = data['title']
if 'description' in data:
milestone.description = data['description']
if 'category' in data:
milestone.category = data['category']
if 'target_date' in data:
milestone.target_date = datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data['target_date'] else None
if 'actual_date' in data:
milestone.actual_date = datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data['actual_date'] else None
if 'status' in data:
milestone.status = data['status']
if 'source_url' in data:
milestone.source_url = data['source_url']
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['DELETE'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_delete(milestone_id):
"""API - usunięcie kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
db.delete(milestone)
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions')
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestions():
"""API - sugestie kamieni milowych z bazy wiedzy."""
from zopk_knowledge_service import get_timeline_suggestions
limit = request.args.get('limit', 30, type=int)
only_verified = request.args.get('only_verified', 'false').lower() == 'true'
use_ai = request.args.get('use_ai', 'false').lower() == 'true'
db = SessionLocal()
try:
result = get_timeline_suggestions(db, limit=limit, only_verified=only_verified)
if result['success'] and use_ai and result.get('suggestions'):
from zopk_knowledge_service import categorize_milestones_with_ai
result['suggestions'] = categorize_milestones_with_ai(db, result['suggestions'])
return jsonify(result)
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions/approve', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestion_approve():
"""API - zatwierdzenie sugestii i utworzenie kamienia milowego."""
from zopk_knowledge_service import create_milestone_from_suggestion
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
fact_id = data.get('fact_id')
if not fact_id:
return jsonify({'error': 'fact_id is required'}), 400
db = SessionLocal()
try:
result = create_milestone_from_suggestion(
db_session=db,
fact_id=fact_id,
title=data.get('title', 'Kamień milowy'),
description=data.get('description'),
category=data.get('category', 'other'),
target_date=data.get('target_date'),
status=data.get('status', 'planned'),
source_url=data.get('source_url')
)
return jsonify(result)
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()