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>
This commit is contained in:
parent
d90b7ec3b7
commit
4181a2e760
2
app.py
2
app.py
@ -912,7 +912,7 @@ def health():
|
|||||||
@login_required
|
@login_required
|
||||||
def test_error_500():
|
def test_error_500():
|
||||||
"""Test endpoint to trigger 500 error for notification testing. Admin only."""
|
"""Test endpoint to trigger 500 error for notification testing. Admin only."""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
flash('Brak uprawnień', 'error')
|
flash('Brak uprawnień', 'error')
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
# Intentionally raise an error to test error notification
|
# Intentionally raise an error to test error notification
|
||||||
|
|||||||
@ -3,5 +3,12 @@
|
|||||||
|
|
||||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||||
|
|
||||||
*No recent activity*
|
### Jan 31, 2026
|
||||||
|
|
||||||
|
| ID | Time | T | Title | Read |
|
||||||
|
|----|------|---|-------|------|
|
||||||
|
| #250 | 6:33 PM | 🔵 | Nordabiz admin blueprint imports 14 separate routes modules demonstrating extreme modularization | ~677 |
|
||||||
|
| #180 | 6:25 PM | 🔵 | Nordabiz project architecture analyzed revealing 16+ Flask blueprints with modular organization | ~831 |
|
||||||
|
| #170 | 6:23 PM | 🔵 | Nordabiz admin routes handle recommendations moderation with Polish localization | ~713 |
|
||||||
|
| #168 | " | 🔵 | Nordabiz admin blueprint splits functionality across 15 route modules | ~726 |
|
||||||
</claude-mem-context>
|
</claude-mem-context>
|
||||||
@ -23,8 +23,10 @@ from werkzeug.security import generate_password_hash
|
|||||||
from . import bp
|
from . import bp
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal, User, Company, CompanyRecommendation,
|
SessionLocal, User, Company, CompanyRecommendation,
|
||||||
MembershipFee, MembershipFeeConfig, NordaEvent, EventAttendee
|
MembershipFee, MembershipFeeConfig, NordaEvent, EventAttendee,
|
||||||
|
SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
import gemini_service
|
import gemini_service
|
||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
@ -44,12 +46,9 @@ MONTHS_PL = [
|
|||||||
|
|
||||||
@bp.route('/recommendations')
|
@bp.route('/recommendations')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_recommendations():
|
def admin_recommendations():
|
||||||
"""Admin panel for recommendations moderation"""
|
"""Admin panel for recommendations moderation"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
recommendations = db.query(CompanyRecommendation).order_by(
|
recommendations = db.query(CompanyRecommendation).order_by(
|
||||||
@ -86,11 +85,9 @@ def admin_recommendations():
|
|||||||
|
|
||||||
@bp.route('/recommendations/<int:recommendation_id>/approve', methods=['POST'])
|
@bp.route('/recommendations/<int:recommendation_id>/approve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_recommendation_approve(recommendation_id):
|
def admin_recommendation_approve(recommendation_id):
|
||||||
"""Approve a recommendation"""
|
"""Approve a recommendation"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
recommendation = db.query(CompanyRecommendation).filter(
|
recommendation = db.query(CompanyRecommendation).filter(
|
||||||
@ -117,11 +114,9 @@ def admin_recommendation_approve(recommendation_id):
|
|||||||
|
|
||||||
@bp.route('/recommendations/<int:recommendation_id>/reject', methods=['POST'])
|
@bp.route('/recommendations/<int:recommendation_id>/reject', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_recommendation_reject(recommendation_id):
|
def admin_recommendation_reject(recommendation_id):
|
||||||
"""Reject a recommendation"""
|
"""Reject a recommendation"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
recommendation = db.query(CompanyRecommendation).filter(
|
recommendation = db.query(CompanyRecommendation).filter(
|
||||||
@ -154,12 +149,9 @@ def admin_recommendation_reject(recommendation_id):
|
|||||||
|
|
||||||
@bp.route('/users')
|
@bp.route('/users')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_users():
|
def admin_users():
|
||||||
"""Admin panel for user management"""
|
"""Admin panel for user management"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
users = db.query(User).order_by(User.created_at.desc()).all()
|
users = db.query(User).order_by(User.created_at.desc()).all()
|
||||||
@ -187,11 +179,9 @@ def admin_users():
|
|||||||
|
|
||||||
@bp.route('/users/add', methods=['POST'])
|
@bp.route('/users/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_add():
|
def admin_user_add():
|
||||||
"""Create a new user (admin only)"""
|
"""Create a new user (admin only)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -241,11 +231,9 @@ def admin_user_add():
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/toggle-admin', methods=['POST'])
|
@bp.route('/users/<int:user_id>/toggle-admin', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_toggle_admin(user_id):
|
def admin_user_toggle_admin(user_id):
|
||||||
"""Toggle admin status for a user"""
|
"""Toggle admin status for a user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
if user_id == current_user.id:
|
if user_id == current_user.id:
|
||||||
return jsonify({'success': False, 'error': 'Nie możesz zmienić własnych uprawnień'}), 400
|
return jsonify({'success': False, 'error': 'Nie możesz zmienić własnych uprawnień'}), 400
|
||||||
|
|
||||||
@ -271,11 +259,9 @@ def admin_user_toggle_admin(user_id):
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/toggle-verified', methods=['POST'])
|
@bp.route('/users/<int:user_id>/toggle-verified', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_toggle_verified(user_id):
|
def admin_user_toggle_verified(user_id):
|
||||||
"""Toggle verified status for a user"""
|
"""Toggle verified status for a user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
@ -302,11 +288,9 @@ def admin_user_toggle_verified(user_id):
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/update', methods=['POST'])
|
@bp.route('/users/<int:user_id>/update', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_update(user_id):
|
def admin_user_update(user_id):
|
||||||
"""Update user data (name, email)"""
|
"""Update user data (name, email)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
@ -353,11 +337,9 @@ def admin_user_update(user_id):
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/assign-company', methods=['POST'])
|
@bp.route('/users/<int:user_id>/assign-company', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_assign_company(user_id):
|
def admin_user_assign_company(user_id):
|
||||||
"""Assign a company to a user"""
|
"""Assign a company to a user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
@ -392,11 +374,9 @@ def admin_user_assign_company(user_id):
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/delete', methods=['POST'])
|
@bp.route('/users/<int:user_id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_delete(user_id):
|
def admin_user_delete(user_id):
|
||||||
"""Delete a user"""
|
"""Delete a user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
if user_id == current_user.id:
|
if user_id == current_user.id:
|
||||||
return jsonify({'success': False, 'error': 'Nie możesz usunąć własnego konta'}), 400
|
return jsonify({'success': False, 'error': 'Nie możesz usunąć własnego konta'}), 400
|
||||||
|
|
||||||
@ -422,11 +402,9 @@ def admin_user_delete(user_id):
|
|||||||
|
|
||||||
@bp.route('/users/<int:user_id>/reset-password', methods=['POST'])
|
@bp.route('/users/<int:user_id>/reset-password', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_user_reset_password(user_id):
|
def admin_user_reset_password(user_id):
|
||||||
"""Generate password reset token for a user"""
|
"""Generate password reset token for a user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
user = db.query(User).filter(User.id == user_id).first()
|
user = db.query(User).filter(User.id == user_id).first()
|
||||||
@ -458,12 +436,9 @@ def admin_user_reset_password(user_id):
|
|||||||
|
|
||||||
@bp.route('/fees')
|
@bp.route('/fees')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_fees():
|
def admin_fees():
|
||||||
"""Admin panel for membership fee management"""
|
"""Admin panel for membership fee management"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnien do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import func, case
|
from sqlalchemy import func, case
|
||||||
@ -545,11 +520,9 @@ def admin_fees():
|
|||||||
|
|
||||||
@bp.route('/fees/generate', methods=['POST'])
|
@bp.route('/fees/generate', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_fees_generate():
|
def admin_fees_generate():
|
||||||
"""Generate fee records for all companies for a given month"""
|
"""Generate fee records for all companies for a given month"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
year = request.form.get('year', type=int)
|
year = request.form.get('year', type=int)
|
||||||
@ -601,11 +574,9 @@ def admin_fees_generate():
|
|||||||
|
|
||||||
@bp.route('/fees/<int:fee_id>/mark-paid', methods=['POST'])
|
@bp.route('/fees/<int:fee_id>/mark-paid', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_fees_mark_paid(fee_id):
|
def admin_fees_mark_paid(fee_id):
|
||||||
"""Mark a fee as paid"""
|
"""Mark a fee as paid"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
fee = db.query(MembershipFee).filter(MembershipFee.id == fee_id).first()
|
fee = db.query(MembershipFee).filter(MembershipFee.id == fee_id).first()
|
||||||
@ -647,11 +618,9 @@ def admin_fees_mark_paid(fee_id):
|
|||||||
|
|
||||||
@bp.route('/fees/bulk-mark-paid', methods=['POST'])
|
@bp.route('/fees/bulk-mark-paid', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_fees_bulk_mark_paid():
|
def admin_fees_bulk_mark_paid():
|
||||||
"""Bulk mark fees as paid"""
|
"""Bulk mark fees as paid"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
fee_ids = request.form.getlist('fee_ids[]', type=int)
|
fee_ids = request.form.getlist('fee_ids[]', type=int)
|
||||||
@ -686,12 +655,9 @@ def admin_fees_bulk_mark_paid():
|
|||||||
|
|
||||||
@bp.route('/fees/export')
|
@bp.route('/fees/export')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_fees_export():
|
def admin_fees_export():
|
||||||
"""Export fees to CSV"""
|
"""Export fees to CSV"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnien.', 'error')
|
|
||||||
return redirect(url_for('.admin_fees'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
year = request.args.get('year', datetime.now().year, type=int)
|
year = request.args.get('year', datetime.now().year, type=int)
|
||||||
@ -747,12 +713,9 @@ def admin_fees_export():
|
|||||||
|
|
||||||
@bp.route('/kalendarz')
|
@bp.route('/kalendarz')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_calendar():
|
def admin_calendar():
|
||||||
"""Panel admin - zarządzanie wydarzeniami"""
|
"""Panel admin - zarządzanie wydarzeniami"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('calendar_index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
events = db.query(NordaEvent).order_by(NordaEvent.event_date.desc()).all()
|
events = db.query(NordaEvent).order_by(NordaEvent.event_date.desc()).all()
|
||||||
@ -764,12 +727,9 @@ def admin_calendar():
|
|||||||
|
|
||||||
@bp.route('/kalendarz/nowy', methods=['GET', 'POST'])
|
@bp.route('/kalendarz/nowy', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_calendar_new():
|
def admin_calendar_new():
|
||||||
"""Dodaj nowe wydarzenie"""
|
"""Dodaj nowe wydarzenie"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('calendar_index'))
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -803,11 +763,9 @@ def admin_calendar_new():
|
|||||||
|
|
||||||
@bp.route('/kalendarz/<int:event_id>/delete', methods=['POST'])
|
@bp.route('/kalendarz/<int:event_id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_calendar_delete(event_id):
|
def admin_calendar_delete(event_id):
|
||||||
"""Usuń wydarzenie"""
|
"""Usuń wydarzenie"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
||||||
@ -834,14 +792,12 @@ def admin_calendar_delete(event_id):
|
|||||||
|
|
||||||
@bp.route('/notify-release', methods=['POST'])
|
@bp.route('/notify-release', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_notify_release():
|
def admin_notify_release():
|
||||||
"""
|
"""
|
||||||
Send notifications to all users about a new release.
|
Send notifications to all users about a new release.
|
||||||
Called manually by admin after deploying a new version.
|
Called manually by admin after deploying a new version.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
version = data.get('version')
|
version = data.get('version')
|
||||||
highlights = data.get('highlights', [])
|
highlights = data.get('highlights', [])
|
||||||
|
|||||||
@ -18,8 +18,10 @@ from sqlalchemy.orm import joinedload
|
|||||||
from . import bp
|
from . import bp
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal, User, UserSession, PageView, SearchQuery,
|
SessionLocal, User, UserSession, PageView, SearchQuery,
|
||||||
ConversionEvent, JSError, Company, AIUsageLog, AIUsageDaily
|
ConversionEvent, JSError, Company, AIUsageLog, AIUsageDaily,
|
||||||
|
SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -30,12 +32,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/analytics')
|
@bp.route('/analytics')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_analytics():
|
def admin_analytics():
|
||||||
"""Admin dashboard for user analytics - sessions, page views, clicks"""
|
"""Admin dashboard for user analytics - sessions, page views, clicks"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnien do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
period = request.args.get('period', 'week')
|
period = request.args.get('period', 'week')
|
||||||
user_id = request.args.get('user_id', type=int)
|
user_id = request.args.get('user_id', type=int)
|
||||||
|
|
||||||
@ -266,12 +265,9 @@ def admin_analytics():
|
|||||||
|
|
||||||
@bp.route('/analytics/export')
|
@bp.route('/analytics/export')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_analytics_export():
|
def admin_analytics_export():
|
||||||
"""Export analytics data as CSV"""
|
"""Export analytics data as CSV"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnien.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
export_type = request.args.get('type', 'sessions')
|
export_type = request.args.get('type', 'sessions')
|
||||||
period = request.args.get('period', 'month')
|
period = request.args.get('period', 'month')
|
||||||
|
|
||||||
@ -373,12 +369,9 @@ def admin_analytics_export():
|
|||||||
|
|
||||||
@bp.route('/ai-usage')
|
@bp.route('/ai-usage')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_ai_usage():
|
def admin_ai_usage():
|
||||||
"""Admin dashboard for AI (Gemini) API usage monitoring"""
|
"""Admin dashboard for AI (Gemini) API usage monitoring"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
# Get period filter from query params
|
# Get period filter from query params
|
||||||
@ -601,12 +594,9 @@ def admin_ai_usage():
|
|||||||
|
|
||||||
@bp.route('/ai-usage/user/<int:user_id>')
|
@bp.route('/ai-usage/user/<int:user_id>')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_ai_usage_user(user_id):
|
def admin_ai_usage_user(user_id):
|
||||||
"""Detailed AI usage for a specific user"""
|
"""Detailed AI usage for a specific user"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Get user info
|
# Get user info
|
||||||
|
|||||||
@ -13,7 +13,8 @@ from flask import render_template, request, redirect, url_for, flash, jsonify
|
|||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from database import SessionLocal, Announcement
|
from database import SessionLocal, Announcement, SystemRole
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -48,12 +49,9 @@ def generate_slug(title):
|
|||||||
|
|
||||||
@bp.route('/announcements')
|
@bp.route('/announcements')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements():
|
def admin_announcements():
|
||||||
"""Admin panel - lista ogłoszeń"""
|
"""Admin panel - lista ogłoszeń"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Filters
|
# Filters
|
||||||
@ -92,12 +90,9 @@ def admin_announcements():
|
|||||||
|
|
||||||
@bp.route('/announcements/new', methods=['GET', 'POST'])
|
@bp.route('/announcements/new', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements_new():
|
def admin_announcements_new():
|
||||||
"""Admin panel - nowe ogłoszenie"""
|
"""Admin panel - nowe ogłoszenie"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -174,12 +169,9 @@ def admin_announcements_new():
|
|||||||
|
|
||||||
@bp.route('/announcements/<int:id>/edit', methods=['GET', 'POST'])
|
@bp.route('/announcements/<int:id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements_edit(id):
|
def admin_announcements_edit(id):
|
||||||
"""Admin panel - edycja ogłoszenia"""
|
"""Admin panel - edycja ogłoszenia"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
||||||
@ -259,11 +251,9 @@ def admin_announcements_edit(id):
|
|||||||
|
|
||||||
@bp.route('/announcements/<int:id>/publish', methods=['POST'])
|
@bp.route('/announcements/<int:id>/publish', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements_publish(id):
|
def admin_announcements_publish(id):
|
||||||
"""Publikacja ogłoszenia"""
|
"""Publikacja ogłoszenia"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
||||||
@ -300,11 +290,9 @@ def admin_announcements_publish(id):
|
|||||||
|
|
||||||
@bp.route('/announcements/<int:id>/archive', methods=['POST'])
|
@bp.route('/announcements/<int:id>/archive', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements_archive(id):
|
def admin_announcements_archive(id):
|
||||||
"""Archiwizacja ogłoszenia"""
|
"""Archiwizacja ogłoszenia"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
||||||
@ -328,11 +316,9 @@ def admin_announcements_archive(id):
|
|||||||
|
|
||||||
@bp.route('/announcements/<int:id>/delete', methods=['POST'])
|
@bp.route('/announcements/<int:id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_announcements_delete(id):
|
def admin_announcements_delete(id):
|
||||||
"""Usunięcie ogłoszenia"""
|
"""Usunięcie ogłoszenia"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
announcement = db.query(Announcement).filter(Announcement.id == id).first()
|
||||||
|
|||||||
@ -15,8 +15,9 @@ from . import bp
|
|||||||
from database import (
|
from database import (
|
||||||
SessionLocal, Company, Category, CompanyWebsiteAnalysis, GBPAudit,
|
SessionLocal, Company, Category, CompanyWebsiteAnalysis, GBPAudit,
|
||||||
CompanyDigitalMaturity, KRSAudit, CompanyPKD, CompanyPerson,
|
CompanyDigitalMaturity, KRSAudit, CompanyPKD, CompanyPerson,
|
||||||
ITAudit, ITCollaborationMatch
|
ITAudit, ITCollaborationMatch, SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/seo')
|
@bp.route('/seo')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_seo():
|
def admin_seo():
|
||||||
"""
|
"""
|
||||||
Admin dashboard for SEO metrics overview.
|
Admin dashboard for SEO metrics overview.
|
||||||
@ -42,10 +44,6 @@ def admin_seo():
|
|||||||
Query Parameters:
|
Query Parameters:
|
||||||
- company: Slug of company to highlight/filter (optional)
|
- company: Slug of company to highlight/filter (optional)
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Get optional company filter from URL
|
# Get optional company filter from URL
|
||||||
filter_company_slug = request.args.get('company', '')
|
filter_company_slug = request.args.get('company', '')
|
||||||
|
|
||||||
@ -145,6 +143,7 @@ def admin_seo():
|
|||||||
|
|
||||||
@bp.route('/gbp-audit')
|
@bp.route('/gbp-audit')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_gbp_audit():
|
def admin_gbp_audit():
|
||||||
"""
|
"""
|
||||||
Admin dashboard for GBP (Google Business Profile) audit overview.
|
Admin dashboard for GBP (Google Business Profile) audit overview.
|
||||||
@ -155,10 +154,6 @@ def admin_gbp_audit():
|
|||||||
- Review metrics (avg rating, review counts)
|
- Review metrics (avg rating, review counts)
|
||||||
- Photo statistics
|
- Photo statistics
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
@ -309,12 +304,9 @@ def admin_gbp_audit():
|
|||||||
|
|
||||||
@bp.route('/digital-maturity')
|
@bp.route('/digital-maturity')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def digital_maturity_dashboard():
|
def digital_maturity_dashboard():
|
||||||
"""Admin dashboard for digital maturity assessment results"""
|
"""Admin dashboard for digital maturity assessment results"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import func, desc
|
from sqlalchemy import func, desc
|
||||||
@ -386,6 +378,7 @@ def digital_maturity_dashboard():
|
|||||||
|
|
||||||
@bp.route('/krs-audit')
|
@bp.route('/krs-audit')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_krs_audit():
|
def admin_krs_audit():
|
||||||
"""
|
"""
|
||||||
Admin dashboard for KRS (Krajowy Rejestr Sądowy) audit.
|
Admin dashboard for KRS (Krajowy Rejestr Sądowy) audit.
|
||||||
@ -396,10 +389,6 @@ def admin_krs_audit():
|
|||||||
- Audit progress and status for each company
|
- Audit progress and status for each company
|
||||||
- Links to source PDF files
|
- Links to source PDF files
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
# Check if KRS audit service is available
|
# Check if KRS audit service is available
|
||||||
try:
|
try:
|
||||||
from krs_audit_service import KRS_AUDIT_AVAILABLE
|
from krs_audit_service import KRS_AUDIT_AVAILABLE
|
||||||
@ -496,6 +485,7 @@ def admin_krs_audit():
|
|||||||
|
|
||||||
@bp.route('/it-audit')
|
@bp.route('/it-audit')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_it_audit():
|
def admin_it_audit():
|
||||||
"""
|
"""
|
||||||
Admin dashboard for IT audit overview.
|
Admin dashboard for IT audit overview.
|
||||||
@ -507,12 +497,8 @@ def admin_it_audit():
|
|||||||
- Company table with IT audit data
|
- Company table with IT audit data
|
||||||
- Collaboration matches matrix
|
- Collaboration matches matrix
|
||||||
|
|
||||||
Access: Admin only
|
Access: Office Manager and above
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|||||||
@ -15,7 +15,8 @@ from flask import render_template, request, redirect, url_for, flash, jsonify, R
|
|||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from database import SessionLocal, Company, Category, User, Person, CompanyPerson
|
from database import SessionLocal, Company, Category, User, Person, CompanyPerson, SystemRole
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -37,12 +38,9 @@ def validate_nip(nip: str) -> bool:
|
|||||||
|
|
||||||
@bp.route('/companies')
|
@bp.route('/companies')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_companies():
|
def admin_companies():
|
||||||
"""Admin panel for company management"""
|
"""Admin panel for company management"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Get filter parameters
|
# Get filter parameters
|
||||||
@ -104,11 +102,9 @@ def admin_companies():
|
|||||||
|
|
||||||
@bp.route('/companies/add', methods=['POST'])
|
@bp.route('/companies/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_add():
|
def admin_company_add():
|
||||||
"""Create a new company"""
|
"""Create a new company"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -174,11 +170,9 @@ def admin_company_add():
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>')
|
@bp.route('/companies/<int:company_id>')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_get(company_id):
|
def admin_company_get(company_id):
|
||||||
"""Get company details (JSON)"""
|
"""Get company details (JSON)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -207,11 +201,9 @@ def admin_company_get(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/update', methods=['POST'])
|
@bp.route('/companies/<int:company_id>/update', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_update(company_id):
|
def admin_company_update(company_id):
|
||||||
"""Update company data"""
|
"""Update company data"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -278,11 +270,9 @@ def admin_company_update(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/toggle-status', methods=['POST'])
|
@bp.route('/companies/<int:company_id>/toggle-status', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_toggle_status(company_id):
|
def admin_company_toggle_status(company_id):
|
||||||
"""Toggle company status (active <-> inactive)"""
|
"""Toggle company status (active <-> inactive)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -310,11 +300,9 @@ def admin_company_toggle_status(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/delete', methods=['POST'])
|
@bp.route('/companies/<int:company_id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_delete(company_id):
|
def admin_company_delete(company_id):
|
||||||
"""Soft delete company (set status to archived)"""
|
"""Soft delete company (set status to archived)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -337,11 +325,9 @@ def admin_company_delete(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/assign-user', methods=['POST'])
|
@bp.route('/companies/<int:company_id>/assign-user', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_assign_user(company_id):
|
def admin_company_assign_user(company_id):
|
||||||
"""Assign a user to a company"""
|
"""Assign a user to a company"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -373,11 +359,9 @@ def admin_company_assign_user(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/people')
|
@bp.route('/companies/<int:company_id>/people')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_people(company_id):
|
def admin_company_people(company_id):
|
||||||
"""Get people associated with a company"""
|
"""Get people associated with a company"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -414,11 +398,9 @@ def admin_company_people(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/<int:company_id>/users')
|
@bp.route('/companies/<int:company_id>/users')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_users(company_id):
|
def admin_company_users(company_id):
|
||||||
"""Get users assigned to a company"""
|
"""Get users assigned to a company"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
company = db.query(Company).filter(Company.id == company_id).first()
|
company = db.query(Company).filter(Company.id == company_id).first()
|
||||||
@ -446,12 +428,9 @@ def admin_company_users(company_id):
|
|||||||
|
|
||||||
@bp.route('/companies/export')
|
@bp.route('/companies/export')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_companies_export():
|
def admin_companies_export():
|
||||||
"""Export companies to CSV"""
|
"""Export companies to CSV"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
companies = db.query(Company).order_by(Company.name).all()
|
companies = db.query(Company).order_by(Company.name).all()
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import logging
|
|||||||
from flask import render_template, request, redirect, url_for, flash, jsonify
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
|
from database import SystemRole
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -21,22 +23,17 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/insights')
|
@bp.route('/insights')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_insights():
|
def admin_insights():
|
||||||
"""Admin dashboard for development insights from forum and chat"""
|
"""Admin dashboard for development insights from forum and chat"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
return render_template('admin/insights.html')
|
return render_template('admin/insights.html')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/insights-api', methods=['GET'])
|
@bp.route('/insights-api', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_get_insights():
|
def api_get_insights():
|
||||||
"""Get development insights for roadmap"""
|
"""Get development insights for roadmap"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from norda_knowledge_service import get_knowledge_service
|
from norda_knowledge_service import get_knowledge_service
|
||||||
service = get_knowledge_service()
|
service = get_knowledge_service()
|
||||||
@ -61,11 +58,9 @@ def api_get_insights():
|
|||||||
|
|
||||||
@bp.route('/insights-api/<int:insight_id>/status', methods=['PUT'])
|
@bp.route('/insights-api/<int:insight_id>/status', methods=['PUT'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_update_insight_status(insight_id):
|
def api_update_insight_status(insight_id):
|
||||||
"""Update insight status (for roadmap planning)"""
|
"""Update insight status (for roadmap planning)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from norda_knowledge_service import get_knowledge_service
|
from norda_knowledge_service import get_knowledge_service
|
||||||
service = get_knowledge_service()
|
service = get_knowledge_service()
|
||||||
@ -87,11 +82,9 @@ def api_update_insight_status(insight_id):
|
|||||||
|
|
||||||
@bp.route('/insights-api/sync', methods=['POST'])
|
@bp.route('/insights-api/sync', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_sync_insights():
|
def api_sync_insights():
|
||||||
"""Manually trigger knowledge sync from forum and chat"""
|
"""Manually trigger knowledge sync from forum and chat"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from norda_knowledge_service import get_knowledge_service
|
from norda_knowledge_service import get_knowledge_service
|
||||||
service = get_knowledge_service()
|
service = get_knowledge_service()
|
||||||
@ -121,11 +114,9 @@ def api_sync_insights():
|
|||||||
|
|
||||||
@bp.route('/insights-api/stats', methods=['GET'])
|
@bp.route('/insights-api/stats', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_insights_stats():
|
def api_insights_stats():
|
||||||
"""Get knowledge base statistics"""
|
"""Get knowledge base statistics"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from norda_knowledge_service import get_knowledge_service
|
from norda_knowledge_service import get_knowledge_service
|
||||||
service = get_knowledge_service()
|
service = get_knowledge_service()
|
||||||
@ -152,11 +143,9 @@ def api_insights_stats():
|
|||||||
|
|
||||||
@bp.route('/ai-learning-status')
|
@bp.route('/ai-learning-status')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_ai_learning_status():
|
def api_ai_learning_status():
|
||||||
"""API: Get AI feedback learning status and examples"""
|
"""API: Get AI feedback learning status and examples"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from feedback_learning_service import get_feedback_learning_service
|
from feedback_learning_service import get_feedback_learning_service
|
||||||
service = get_feedback_learning_service()
|
service = get_feedback_learning_service()
|
||||||
@ -211,11 +200,9 @@ def api_ai_learning_status():
|
|||||||
|
|
||||||
@bp.route('/chat-stats')
|
@bp.route('/chat-stats')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_chat_stats():
|
def api_chat_stats():
|
||||||
"""API: Get chat statistics for dashboard"""
|
"""API: Get chat statistics for dashboard"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from database import SessionLocal, AIChatMessage
|
from database import SessionLocal, AIChatMessage
|
||||||
|
|
||||||
|
|||||||
@ -19,8 +19,10 @@ from database import (
|
|||||||
CompanyPerson,
|
CompanyPerson,
|
||||||
CompanyPKD,
|
CompanyPKD,
|
||||||
CompanyFinancialReport,
|
CompanyFinancialReport,
|
||||||
KRSAudit
|
KRSAudit,
|
||||||
|
SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -116,6 +118,7 @@ def _import_krs_person(db, company_id, person_data, role_category, source_docume
|
|||||||
|
|
||||||
@bp.route('/krs-api/audit', methods=['POST'])
|
@bp.route('/krs-api/audit', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_krs_audit_trigger():
|
def api_krs_audit_trigger():
|
||||||
"""
|
"""
|
||||||
API: Trigger KRS audit for a company (admin-only).
|
API: Trigger KRS audit for a company (admin-only).
|
||||||
@ -134,12 +137,6 @@ def api_krs_audit_trigger():
|
|||||||
- Success: Audit results saved to database
|
- Success: Audit results saved to database
|
||||||
- Error: Error message with status code
|
- Error: Error message with status code
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty KRS.'
|
|
||||||
}), 403
|
|
||||||
|
|
||||||
if not is_krs_audit_available():
|
if not is_krs_audit_available():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
@ -350,6 +347,7 @@ def api_krs_audit_trigger():
|
|||||||
|
|
||||||
@bp.route('/krs-api/audit/batch', methods=['POST'])
|
@bp.route('/krs-api/audit/batch', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_krs_audit_batch():
|
def api_krs_audit_batch():
|
||||||
"""
|
"""
|
||||||
API: Trigger batch KRS audit for all companies with KRS numbers.
|
API: Trigger batch KRS audit for all companies with KRS numbers.
|
||||||
@ -357,12 +355,6 @@ def api_krs_audit_batch():
|
|||||||
This runs audits sequentially to avoid overloading the system.
|
This runs audits sequentially to avoid overloading the system.
|
||||||
Returns progress updates via the response.
|
Returns progress updates via the response.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({
|
|
||||||
'success': False,
|
|
||||||
'error': 'Brak uprawnień.'
|
|
||||||
}), 403
|
|
||||||
|
|
||||||
if not is_krs_audit_available():
|
if not is_krs_audit_available():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
|
|||||||
@ -16,9 +16,11 @@ from sqlalchemy.orm.attributes import flag_modified
|
|||||||
from . import bp
|
from . import bp
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal, MembershipApplication, CompanyDataRequest,
|
SessionLocal, MembershipApplication, CompanyDataRequest,
|
||||||
Company, Category, User, UserNotification, Person, CompanyPerson, CompanyPKD
|
Company, Category, User, UserNotification, Person, CompanyPerson, CompanyPKD,
|
||||||
|
SystemRole
|
||||||
)
|
)
|
||||||
from krs_api_service import get_company_from_krs
|
from krs_api_service import get_company_from_krs
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -156,12 +158,9 @@ def _enrich_company_from_krs(company, db):
|
|||||||
|
|
||||||
@bp.route('/membership')
|
@bp.route('/membership')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership():
|
def admin_membership():
|
||||||
"""Admin panel for membership applications."""
|
"""Admin panel for membership applications."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Get filter parameters
|
# Get filter parameters
|
||||||
@ -224,12 +223,9 @@ def admin_membership():
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>')
|
@bp.route('/membership/<int:app_id>')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_detail(app_id):
|
def admin_membership_detail(app_id):
|
||||||
"""View membership application details."""
|
"""View membership application details."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -253,11 +249,9 @@ def admin_membership_detail(app_id):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/approve', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/approve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_approve(app_id):
|
def admin_membership_approve(app_id):
|
||||||
"""Approve membership application and create company."""
|
"""Approve membership application and create company."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -398,11 +392,9 @@ def admin_membership_approve(app_id):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/reject', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/reject', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_reject(app_id):
|
def admin_membership_reject(app_id):
|
||||||
"""Reject membership application."""
|
"""Reject membership application."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -444,11 +436,9 @@ def admin_membership_reject(app_id):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/request-changes', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/request-changes', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_request_changes(app_id):
|
def admin_membership_request_changes(app_id):
|
||||||
"""Request changes to membership application."""
|
"""Request changes to membership application."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -490,11 +480,9 @@ def admin_membership_request_changes(app_id):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/start-review', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/start-review', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_start_review(app_id):
|
def admin_membership_start_review(app_id):
|
||||||
"""Mark application as under review."""
|
"""Mark application as under review."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -521,14 +509,12 @@ def admin_membership_start_review(app_id):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/propose-changes', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/propose-changes', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_propose_changes(app_id):
|
def admin_membership_propose_changes(app_id):
|
||||||
"""
|
"""
|
||||||
Propose changes from registry data for user approval.
|
Propose changes from registry data for user approval.
|
||||||
Instead of directly updating, save proposed changes and notify user.
|
Instead of directly updating, save proposed changes and notify user.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
application = db.query(MembershipApplication).get(app_id)
|
application = db.query(MembershipApplication).get(app_id)
|
||||||
@ -651,15 +637,13 @@ def _get_field_label(field_name):
|
|||||||
|
|
||||||
@bp.route('/membership/<int:app_id>/update-from-registry', methods=['POST'])
|
@bp.route('/membership/<int:app_id>/update-from-registry', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_membership_update_from_registry(app_id):
|
def admin_membership_update_from_registry(app_id):
|
||||||
"""
|
"""
|
||||||
[DEPRECATED - use propose-changes instead]
|
[DEPRECATED - use propose-changes instead]
|
||||||
Direct update is now replaced by propose-changes workflow.
|
Direct update is now replaced by propose-changes workflow.
|
||||||
This endpoint is kept for backward compatibility but redirects to propose-changes.
|
This endpoint is kept for backward compatibility but redirects to propose-changes.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
# Redirect to new workflow
|
# Redirect to new workflow
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
return admin_membership_propose_changes.__wrapped__(app_id)
|
return admin_membership_propose_changes.__wrapped__(app_id)
|
||||||
@ -671,12 +655,9 @@ def admin_membership_update_from_registry(app_id):
|
|||||||
|
|
||||||
@bp.route('/company-requests')
|
@bp.route('/company-requests')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_requests():
|
def admin_company_requests():
|
||||||
"""Admin panel for company data requests."""
|
"""Admin panel for company data requests."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
status_filter = request.args.get('status', 'pending')
|
status_filter = request.args.get('status', 'pending')
|
||||||
@ -713,11 +694,9 @@ def admin_company_requests():
|
|||||||
|
|
||||||
@bp.route('/company-requests/<int:req_id>/approve', methods=['POST'])
|
@bp.route('/company-requests/<int:req_id>/approve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_request_approve(req_id):
|
def admin_company_request_approve(req_id):
|
||||||
"""Approve company data request and update company."""
|
"""Approve company data request and update company."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data_request = db.query(CompanyDataRequest).get(req_id)
|
data_request = db.query(CompanyDataRequest).get(req_id)
|
||||||
@ -803,11 +782,9 @@ def admin_company_request_approve(req_id):
|
|||||||
|
|
||||||
@bp.route('/company-requests/<int:req_id>/reject', methods=['POST'])
|
@bp.route('/company-requests/<int:req_id>/reject', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_company_request_reject(req_id):
|
def admin_company_request_reject(req_id):
|
||||||
"""Reject company data request."""
|
"""Reject company data request."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data_request = db.query(CompanyDataRequest).get(req_id)
|
data_request = db.query(CompanyDataRequest).get(req_id)
|
||||||
|
|||||||
@ -28,8 +28,10 @@ from database import (
|
|||||||
GBPAudit,
|
GBPAudit,
|
||||||
NordaEvent,
|
NordaEvent,
|
||||||
SessionLocal,
|
SessionLocal,
|
||||||
|
SystemRole,
|
||||||
ZOPKNews,
|
ZOPKNews,
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
@ -38,11 +40,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/model-comparison')
|
@bp.route('/model-comparison')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_model_comparison():
|
def admin_model_comparison():
|
||||||
"""Admin page for comparing AI model responses"""
|
"""Admin page for comparing AI model responses"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Load saved comparison results if exist
|
# Load saved comparison results if exist
|
||||||
results_file = '/tmp/nordabiz_model_comparison_results.json'
|
results_file = '/tmp/nordabiz_model_comparison_results.json'
|
||||||
@ -68,11 +68,9 @@ def admin_model_comparison():
|
|||||||
|
|
||||||
@bp.route('/model-comparison/run', methods=['POST'])
|
@bp.route('/model-comparison/run', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_model_comparison_run():
|
def admin_model_comparison_run():
|
||||||
"""Run model comparison simulation"""
|
"""Run model comparison simulation"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Questions to compare (from real conversations)
|
# Questions to compare (from real conversations)
|
||||||
comparison_questions = {
|
comparison_questions = {
|
||||||
|
|||||||
@ -13,7 +13,8 @@ from flask import render_template, request, redirect, url_for, flash, jsonify
|
|||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from database import SessionLocal, Company, Person, CompanyPerson
|
from database import SessionLocal, Company, Person, CompanyPerson, SystemRole
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
# Logger
|
# Logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -25,12 +26,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/people')
|
@bp.route('/people')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_people():
|
def admin_people():
|
||||||
"""Admin panel for person management"""
|
"""Admin panel for person management"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Get search query
|
# Get search query
|
||||||
@ -114,11 +112,9 @@ def admin_people():
|
|||||||
|
|
||||||
@bp.route('/people/add', methods=['POST'])
|
@bp.route('/people/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_add():
|
def admin_person_add():
|
||||||
"""Create a new person"""
|
"""Create a new person"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -171,11 +167,9 @@ def admin_person_add():
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>')
|
@bp.route('/people/<int:person_id>')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_get(person_id):
|
def admin_person_get(person_id):
|
||||||
"""Get person details (JSON)"""
|
"""Get person details (JSON)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
person = db.query(Person).filter(Person.id == person_id).first()
|
person = db.query(Person).filter(Person.id == person_id).first()
|
||||||
@ -198,11 +192,9 @@ def admin_person_get(person_id):
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>/update', methods=['POST'])
|
@bp.route('/people/<int:person_id>/update', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_update(person_id):
|
def admin_person_update(person_id):
|
||||||
"""Update person data"""
|
"""Update person data"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
person = db.query(Person).filter(Person.id == person_id).first()
|
person = db.query(Person).filter(Person.id == person_id).first()
|
||||||
@ -256,11 +248,9 @@ def admin_person_update(person_id):
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>/delete', methods=['POST'])
|
@bp.route('/people/<int:person_id>/delete', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_delete(person_id):
|
def admin_person_delete(person_id):
|
||||||
"""Delete person (hard delete with CASCADE on CompanyPerson)"""
|
"""Delete person (hard delete with CASCADE on CompanyPerson)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
person = db.query(Person).filter(Person.id == person_id).first()
|
person = db.query(Person).filter(Person.id == person_id).first()
|
||||||
@ -286,11 +276,9 @@ def admin_person_delete(person_id):
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>/companies')
|
@bp.route('/people/<int:person_id>/companies')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_companies(person_id):
|
def admin_person_companies(person_id):
|
||||||
"""Get companies associated with a person"""
|
"""Get companies associated with a person"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
person = db.query(Person).filter(Person.id == person_id).first()
|
person = db.query(Person).filter(Person.id == person_id).first()
|
||||||
@ -321,11 +309,9 @@ def admin_person_companies(person_id):
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>/link-company', methods=['POST'])
|
@bp.route('/people/<int:person_id>/link-company', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_link_company(person_id):
|
def admin_person_link_company(person_id):
|
||||||
"""Link person to a company"""
|
"""Link person to a company"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -393,11 +379,9 @@ def admin_person_link_company(person_id):
|
|||||||
|
|
||||||
@bp.route('/people/<int:person_id>/unlink-company/<int:company_id>', methods=['POST'])
|
@bp.route('/people/<int:person_id>/unlink-company/<int:company_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_person_unlink_company(person_id, company_id):
|
def admin_person_unlink_company(person_id, company_id):
|
||||||
"""Remove person-company link"""
|
"""Remove person-company link"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -439,11 +423,9 @@ def admin_person_unlink_company(person_id, company_id):
|
|||||||
|
|
||||||
@bp.route('/people/search')
|
@bp.route('/people/search')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_people_search():
|
def admin_people_search():
|
||||||
"""Search people for autocomplete"""
|
"""Search people for autocomplete"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
query = request.args.get('q', '').strip()
|
query = request.args.get('q', '').strip()
|
||||||
|
|||||||
@ -12,7 +12,8 @@ from flask import render_template, request, redirect, url_for, flash, jsonify
|
|||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from database import SessionLocal, User, AuditLog, SecurityAlert
|
from database import SessionLocal, User, AuditLog, SecurityAlert, SystemRole
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -34,12 +35,9 @@ except ImportError:
|
|||||||
|
|
||||||
@bp.route('/security')
|
@bp.route('/security')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_security():
|
def admin_security():
|
||||||
"""Security dashboard - audit logs, alerts, GeoIP stats"""
|
"""Security dashboard - audit logs, alerts, GeoIP stats"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
from sqlalchemy import func, desc
|
from sqlalchemy import func, desc
|
||||||
@ -158,11 +156,9 @@ def admin_security():
|
|||||||
|
|
||||||
@bp.route('/security/alert/<int:alert_id>/acknowledge', methods=['POST'])
|
@bp.route('/security/alert/<int:alert_id>/acknowledge', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def acknowledge_security_alert(alert_id):
|
def acknowledge_security_alert(alert_id):
|
||||||
"""Acknowledge a security alert"""
|
"""Acknowledge a security alert"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
alert = db.query(SecurityAlert).get(alert_id)
|
alert = db.query(SecurityAlert).get(alert_id)
|
||||||
@ -186,11 +182,9 @@ def acknowledge_security_alert(alert_id):
|
|||||||
|
|
||||||
@bp.route('/security/alert/<int:alert_id>/resolve', methods=['POST'])
|
@bp.route('/security/alert/<int:alert_id>/resolve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def resolve_security_alert(alert_id):
|
def resolve_security_alert(alert_id):
|
||||||
"""Resolve a security alert"""
|
"""Resolve a security alert"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
note = request.form.get('note', '')
|
note = request.form.get('note', '')
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -219,11 +213,9 @@ def resolve_security_alert(alert_id):
|
|||||||
|
|
||||||
@bp.route('/security/unlock-account/<int:user_id>', methods=['POST'])
|
@bp.route('/security/unlock-account/<int:user_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def unlock_account(user_id):
|
def unlock_account(user_id):
|
||||||
"""Unlock a locked user account"""
|
"""Unlock a locked user account"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
user = db.query(User).get(user_id)
|
user = db.query(User).get(user_id)
|
||||||
@ -246,11 +238,9 @@ def unlock_account(user_id):
|
|||||||
|
|
||||||
@bp.route('/security/geoip-stats')
|
@bp.route('/security/geoip-stats')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_geoip_stats():
|
def api_geoip_stats():
|
||||||
"""API endpoint for GeoIP stats auto-refresh"""
|
"""API endpoint for GeoIP stats auto-refresh"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
|||||||
@ -14,8 +14,9 @@ from sqlalchemy import func, distinct
|
|||||||
|
|
||||||
from . import bp
|
from . import bp
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal, Company, Category, CompanySocialMedia
|
SessionLocal, Company, Category, CompanySocialMedia, SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -26,12 +27,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/social-media')
|
@bp.route('/social-media')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_social_media():
|
def admin_social_media():
|
||||||
"""Admin dashboard for social media analytics"""
|
"""Admin dashboard for social media analytics"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Total counts per platform
|
# Total counts per platform
|
||||||
@ -123,6 +121,7 @@ def admin_social_media():
|
|||||||
|
|
||||||
@bp.route('/social-audit')
|
@bp.route('/social-audit')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_social_audit():
|
def admin_social_audit():
|
||||||
"""
|
"""
|
||||||
Admin dashboard for Social Media audit overview.
|
Admin dashboard for Social Media audit overview.
|
||||||
@ -133,10 +132,6 @@ def admin_social_audit():
|
|||||||
- Sortable table with platform icons per company
|
- Sortable table with platform icons per company
|
||||||
- Followers aggregate statistics
|
- Followers aggregate statistics
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Platform definitions
|
# Platform definitions
|
||||||
|
|||||||
@ -18,8 +18,9 @@ from sqlalchemy import func, text
|
|||||||
from . import bp
|
from . import bp
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal, Company, User, AuditLog, SecurityAlert,
|
SessionLocal, Company, User, AuditLog, SecurityAlert,
|
||||||
CompanySocialMedia, CompanyWebsiteAnalysis
|
CompanySocialMedia, CompanyWebsiteAnalysis, SystemRole
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -30,12 +31,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/status')
|
@bp.route('/status')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_status():
|
def admin_status():
|
||||||
"""System status dashboard with real-time metrics"""
|
"""System status dashboard with real-time metrics"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Current timestamp
|
# Current timestamp
|
||||||
@ -535,11 +533,9 @@ def admin_status():
|
|||||||
|
|
||||||
@bp.route('/api/status')
|
@bp.route('/api/status')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_admin_status():
|
def api_admin_status():
|
||||||
"""API endpoint for status dashboard auto-refresh"""
|
"""API endpoint for status dashboard auto-refresh"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -611,15 +607,12 @@ def api_admin_status():
|
|||||||
|
|
||||||
@bp.route('/health')
|
@bp.route('/health')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def admin_health():
|
def admin_health():
|
||||||
"""
|
"""
|
||||||
Graphical health check dashboard.
|
Graphical health check dashboard.
|
||||||
Shows status of all critical endpoints with visual indicators.
|
Shows status of all critical endpoints with visual indicators.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
@ -748,11 +741,9 @@ def admin_health():
|
|||||||
|
|
||||||
@bp.route('/api/health')
|
@bp.route('/api/health')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_admin_health():
|
def api_admin_health():
|
||||||
"""API endpoint for health dashboard auto-refresh"""
|
"""API endpoint for health dashboard auto-refresh"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
|
||||||
# Run the same checks as admin_health but return JSON
|
# Run the same checks as admin_health but return JSON
|
||||||
@ -803,21 +794,17 @@ def api_admin_health():
|
|||||||
|
|
||||||
@bp.route('/debug')
|
@bp.route('/debug')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def debug_panel():
|
def debug_panel():
|
||||||
"""Real-time debug panel for monitoring app activity"""
|
"""Real-time debug panel for monitoring app activity"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('public.dashboard'))
|
|
||||||
return render_template('admin/debug.html')
|
return render_template('admin/debug.html')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/api/logs')
|
@bp.route('/api/logs')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_get_logs():
|
def api_get_logs():
|
||||||
"""API: Get recent logs"""
|
"""API: Get recent logs"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
# Import debug_handler from main app
|
# Import debug_handler from main app
|
||||||
from app import debug_handler
|
from app import debug_handler
|
||||||
|
|
||||||
@ -848,11 +835,9 @@ def api_get_logs():
|
|||||||
|
|
||||||
@bp.route('/api/logs/stream')
|
@bp.route('/api/logs/stream')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_logs_stream():
|
def api_logs_stream():
|
||||||
"""SSE endpoint for real-time log streaming"""
|
"""SSE endpoint for real-time log streaming"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
from app import debug_handler
|
from app import debug_handler
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -873,11 +858,9 @@ def api_logs_stream():
|
|||||||
|
|
||||||
@bp.route('/api/logs/clear', methods=['POST'])
|
@bp.route('/api/logs/clear', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_clear_logs():
|
def api_clear_logs():
|
||||||
"""API: Clear log buffer"""
|
"""API: Clear log buffer"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
from app import debug_handler
|
from app import debug_handler
|
||||||
debug_handler.logs.clear()
|
debug_handler.logs.clear()
|
||||||
logger.info("Log buffer cleared by admin")
|
logger.info("Log buffer cleared by admin")
|
||||||
@ -886,11 +869,9 @@ def api_clear_logs():
|
|||||||
|
|
||||||
@bp.route('/api/test-log', methods=['POST'])
|
@bp.route('/api/test-log', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.OFFICE_MANAGER)
|
||||||
def api_test_log():
|
def api_test_log():
|
||||||
"""API: Generate test log entries"""
|
"""API: Generate test log entries"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Not authorized'}), 403
|
|
||||||
|
|
||||||
logger.debug("Test DEBUG message")
|
logger.debug("Test DEBUG message")
|
||||||
logger.info("Test INFO message")
|
logger.info("Test INFO message")
|
||||||
logger.warning("Test WARNING message")
|
logger.warning("Test WARNING message")
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from flask_login import current_user, login_required
|
|||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
from database import SessionLocal, User, Company, SystemRole, CompanyRole, UserCompanyPermissions
|
from database import SessionLocal, User, Company, SystemRole, CompanyRole, UserCompanyPermissions
|
||||||
|
from utils.decorators import role_required
|
||||||
import gemini_service
|
import gemini_service
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
@ -107,11 +108,9 @@ ZWRÓĆ TYLKO CZYSTY JSON w dokładnie takim formacie (bez żadnego tekstu przed
|
|||||||
|
|
||||||
@bp.route('/users-api/ai-parse', methods=['POST'])
|
@bp.route('/users-api/ai-parse', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_users_ai_parse():
|
def admin_users_ai_parse():
|
||||||
"""Parse text or image with AI to extract user data."""
|
"""Parse text or image with AI to extract user data."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Get list of companies for AI context
|
# Get list of companies for AI context
|
||||||
@ -221,11 +220,9 @@ def admin_users_ai_parse():
|
|||||||
|
|
||||||
@bp.route('/users-api/bulk-create', methods=['POST'])
|
@bp.route('/users-api/bulk-create', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_users_bulk_create():
|
def admin_users_bulk_create():
|
||||||
"""Create multiple users from confirmed proposals."""
|
"""Create multiple users from confirmed proposals."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -309,11 +306,9 @@ def admin_users_bulk_create():
|
|||||||
|
|
||||||
@bp.route('/users-api/change-role', methods=['POST'])
|
@bp.route('/users-api/change-role', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_users_change_role():
|
def admin_users_change_role():
|
||||||
"""Change user's system role."""
|
"""Change user's system role."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -389,11 +384,9 @@ def admin_users_change_role():
|
|||||||
|
|
||||||
@bp.route('/users-api/roles', methods=['GET'])
|
@bp.route('/users-api/roles', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_users_get_roles():
|
def admin_users_get_roles():
|
||||||
"""Get list of available roles for dropdown."""
|
"""Get list of available roles for dropdown."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
roles = [
|
roles = [
|
||||||
{'value': 'UNAFFILIATED', 'label': 'Niezrzeszony', 'description': 'Firma spoza Izby'},
|
{'value': 'UNAFFILIATED', 'label': 'Niezrzeszony', 'description': 'Firma spoza Izby'},
|
||||||
{'value': 'MEMBER', 'label': 'Członek', 'description': 'Członek Norda bez firmy'},
|
{'value': 'MEMBER', 'label': 'Członek', 'description': 'Członek Norda bez firmy'},
|
||||||
|
|||||||
@ -11,23 +11,22 @@ from flask_login import current_user, login_required
|
|||||||
|
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal,
|
SessionLocal,
|
||||||
|
SystemRole,
|
||||||
ZOPKProject,
|
ZOPKProject,
|
||||||
ZOPKStakeholder,
|
ZOPKStakeholder,
|
||||||
ZOPKNews,
|
ZOPKNews,
|
||||||
ZOPKResource,
|
ZOPKResource,
|
||||||
ZOPKNewsFetchJob
|
ZOPKNewsFetchJob
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/zopk')
|
@bp.route('/zopk')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk():
|
def admin_zopk():
|
||||||
"""Admin dashboard for ZOPK management"""
|
"""Admin dashboard for ZOPK management"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
# Pagination and filtering parameters
|
# Pagination and filtering parameters
|
||||||
|
|||||||
@ -18,11 +18,13 @@ from sqlalchemy import text, func, distinct
|
|||||||
|
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal,
|
SessionLocal,
|
||||||
|
SystemRole,
|
||||||
ZOPKNews,
|
ZOPKNews,
|
||||||
ZOPKKnowledgeChunk,
|
ZOPKKnowledgeChunk,
|
||||||
ZOPKKnowledgeEntity,
|
ZOPKKnowledgeEntity,
|
||||||
ZOPKKnowledgeEntityMention
|
ZOPKKnowledgeEntityMention
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -35,6 +37,7 @@ _GRAPH_CACHE_TTL = 300 # 5 minutes
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/stats')
|
@bp.route('/zopk/knowledge/stats')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_stats():
|
def admin_zopk_knowledge_stats():
|
||||||
"""
|
"""
|
||||||
Get knowledge extraction statistics.
|
Get knowledge extraction statistics.
|
||||||
@ -44,9 +47,6 @@ def admin_zopk_knowledge_stats():
|
|||||||
- knowledge_base: stats about chunks, facts, entities, relations
|
- knowledge_base: stats about chunks, facts, entities, relations
|
||||||
- top_entities: most mentioned entities
|
- top_entities: most mentioned entities
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import get_knowledge_stats
|
from zopk_knowledge_service import get_knowledge_stats
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -65,6 +65,7 @@ def admin_zopk_knowledge_stats():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/extract', methods=['POST'])
|
@bp.route('/zopk/knowledge/extract', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_extract():
|
def admin_zopk_knowledge_extract():
|
||||||
"""
|
"""
|
||||||
Batch extract knowledge from scraped articles.
|
Batch extract knowledge from scraped articles.
|
||||||
@ -77,9 +78,6 @@ def admin_zopk_knowledge_extract():
|
|||||||
- chunks/facts/entities/relations created
|
- chunks/facts/entities/relations created
|
||||||
- errors list
|
- errors list
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import ZOPKKnowledgeService
|
from zopk_knowledge_service import ZOPKKnowledgeService
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -114,13 +112,11 @@ def admin_zopk_knowledge_extract():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/extract/<int:news_id>', methods=['POST'])
|
@bp.route('/zopk/knowledge/extract/<int:news_id>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_extract_single(news_id):
|
def admin_zopk_knowledge_extract_single(news_id):
|
||||||
"""
|
"""
|
||||||
Extract knowledge from a single article.
|
Extract knowledge from a single article.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import ZOPKKnowledgeService
|
from zopk_knowledge_service import ZOPKKnowledgeService
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -155,6 +151,7 @@ def admin_zopk_knowledge_extract_single(news_id):
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/embeddings', methods=['POST'])
|
@bp.route('/zopk/knowledge/embeddings', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_generate_embeddings():
|
def admin_zopk_generate_embeddings():
|
||||||
"""
|
"""
|
||||||
Generate embeddings for chunks that don't have them.
|
Generate embeddings for chunks that don't have them.
|
||||||
@ -162,9 +159,6 @@ def admin_zopk_generate_embeddings():
|
|||||||
Request JSON:
|
Request JSON:
|
||||||
- limit: int (default 100) - max chunks to process
|
- limit: int (default 100) - max chunks to process
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import generate_chunk_embeddings
|
from zopk_knowledge_service import generate_chunk_embeddings
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -192,6 +186,7 @@ def admin_zopk_generate_embeddings():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/extract/stream', methods=['GET'])
|
@bp.route('/zopk/knowledge/extract/stream', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_extract_stream():
|
def admin_zopk_knowledge_extract_stream():
|
||||||
"""
|
"""
|
||||||
SSE endpoint for streaming knowledge extraction progress.
|
SSE endpoint for streaming knowledge extraction progress.
|
||||||
@ -199,9 +194,6 @@ def admin_zopk_knowledge_extract_stream():
|
|||||||
Query params:
|
Query params:
|
||||||
- limit: int (default 10) - max articles to process
|
- limit: int (default 10) - max articles to process
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
limit = min(int(request.args.get('limit', 10)), 50)
|
limit = min(int(request.args.get('limit', 10)), 50)
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
|
|
||||||
@ -275,6 +267,7 @@ def admin_zopk_knowledge_extract_stream():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/embeddings/stream', methods=['GET'])
|
@bp.route('/zopk/knowledge/embeddings/stream', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_embeddings_stream():
|
def admin_zopk_embeddings_stream():
|
||||||
"""
|
"""
|
||||||
SSE endpoint for streaming embeddings generation progress.
|
SSE endpoint for streaming embeddings generation progress.
|
||||||
@ -282,9 +275,6 @@ def admin_zopk_embeddings_stream():
|
|||||||
Query params:
|
Query params:
|
||||||
- limit: int (default 50) - max chunks to process
|
- limit: int (default 50) - max chunks to process
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
limit = min(int(request.args.get('limit', 50)), 200)
|
limit = min(int(request.args.get('limit', 50)), 200)
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
|
|
||||||
@ -413,28 +403,22 @@ def api_zopk_knowledge_search():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge')
|
@bp.route('/zopk/knowledge')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_dashboard():
|
def admin_zopk_knowledge_dashboard():
|
||||||
"""
|
"""
|
||||||
Dashboard for ZOPK Knowledge Base management.
|
Dashboard for ZOPK Knowledge Base management.
|
||||||
Shows stats and links to chunks, facts, entities lists.
|
Shows stats and links to chunks, facts, entities lists.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej sekcji.', 'warning')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
return render_template('admin/zopk_knowledge_dashboard.html')
|
return render_template('admin/zopk_knowledge_dashboard.html')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/zopk/knowledge/chunks')
|
@bp.route('/zopk/knowledge/chunks')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_chunks():
|
def admin_zopk_knowledge_chunks():
|
||||||
"""
|
"""
|
||||||
List knowledge chunks with pagination and filtering.
|
List knowledge chunks with pagination and filtering.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej sekcji.', 'warning')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
from zopk_knowledge_service import list_chunks
|
from zopk_knowledge_service import list_chunks
|
||||||
|
|
||||||
# Get query params
|
# Get query params
|
||||||
@ -478,14 +462,11 @@ def admin_zopk_knowledge_chunks():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/facts')
|
@bp.route('/zopk/knowledge/facts')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_facts():
|
def admin_zopk_knowledge_facts():
|
||||||
"""
|
"""
|
||||||
List knowledge facts with pagination and filtering.
|
List knowledge facts with pagination and filtering.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej sekcji.', 'warning')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
from zopk_knowledge_service import list_facts
|
from zopk_knowledge_service import list_facts
|
||||||
|
|
||||||
# Get query params
|
# Get query params
|
||||||
@ -528,14 +509,11 @@ def admin_zopk_knowledge_facts():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/entities')
|
@bp.route('/zopk/knowledge/entities')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_entities():
|
def admin_zopk_knowledge_entities():
|
||||||
"""
|
"""
|
||||||
List knowledge entities with pagination and filtering.
|
List knowledge entities with pagination and filtering.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej sekcji.', 'warning')
|
|
||||||
return redirect(url_for('index'))
|
|
||||||
|
|
||||||
from zopk_knowledge_service import list_entities
|
from zopk_knowledge_service import list_entities
|
||||||
|
|
||||||
# Get query params
|
# Get query params
|
||||||
@ -578,11 +556,9 @@ def admin_zopk_knowledge_entities():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>')
|
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_chunk_detail(chunk_id):
|
def api_zopk_chunk_detail(chunk_id):
|
||||||
"""Get detailed information about a chunk."""
|
"""Get detailed information about a chunk."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import get_chunk_detail
|
from zopk_knowledge_service import get_chunk_detail
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -598,11 +574,9 @@ def api_zopk_chunk_detail(chunk_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>/verify', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>/verify', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_chunk_verify(chunk_id):
|
def api_zopk_chunk_verify(chunk_id):
|
||||||
"""Toggle chunk verification status."""
|
"""Toggle chunk verification status."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import update_chunk_verification
|
from zopk_knowledge_service import update_chunk_verification
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -624,11 +598,9 @@ def api_zopk_chunk_verify(chunk_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/facts/<int:fact_id>/verify', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/facts/<int:fact_id>/verify', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_fact_verify(fact_id):
|
def api_zopk_fact_verify(fact_id):
|
||||||
"""Toggle fact verification status."""
|
"""Toggle fact verification status."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import update_fact_verification
|
from zopk_knowledge_service import update_fact_verification
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -650,11 +622,9 @@ def api_zopk_fact_verify(fact_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/entities/<int:entity_id>/verify', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/entities/<int:entity_id>/verify', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_entity_verify(entity_id):
|
def api_zopk_entity_verify(entity_id):
|
||||||
"""Toggle entity verification status."""
|
"""Toggle entity verification status."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import update_entity_verification
|
from zopk_knowledge_service import update_entity_verification
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -676,11 +646,9 @@ def api_zopk_entity_verify(entity_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>', methods=['DELETE'])
|
@bp.route('/zopk-api/knowledge/chunks/<int:chunk_id>', methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_chunk_delete(chunk_id):
|
def api_zopk_chunk_delete(chunk_id):
|
||||||
"""Delete a chunk and its associated data."""
|
"""Delete a chunk and its associated data."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import delete_chunk
|
from zopk_knowledge_service import delete_chunk
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -699,12 +667,9 @@ def api_zopk_chunk_delete(chunk_id):
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/duplicates')
|
@bp.route('/zopk/knowledge/duplicates')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_duplicates():
|
def admin_zopk_knowledge_duplicates():
|
||||||
"""Admin page for managing duplicate entities."""
|
"""Admin page for managing duplicate entities."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
from zopk_knowledge_service import find_duplicate_entities
|
from zopk_knowledge_service import find_duplicate_entities
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -737,11 +702,9 @@ def admin_zopk_knowledge_duplicates():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/duplicates/preview', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/duplicates/preview', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_duplicates_preview():
|
def api_zopk_duplicates_preview():
|
||||||
"""Preview merge operation between two entities."""
|
"""Preview merge operation between two entities."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import get_entity_merge_preview
|
from zopk_knowledge_service import get_entity_merge_preview
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -764,11 +727,9 @@ def api_zopk_duplicates_preview():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/duplicates/merge', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/duplicates/merge', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_duplicates_merge():
|
def api_zopk_duplicates_merge():
|
||||||
"""Merge two entities - keep primary, delete duplicate."""
|
"""Merge two entities - keep primary, delete duplicate."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import merge_entities
|
from zopk_knowledge_service import merge_entities
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -789,17 +750,15 @@ def api_zopk_duplicates_merge():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/graph')
|
@bp.route('/zopk/knowledge/graph')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_knowledge_graph():
|
def admin_zopk_knowledge_graph():
|
||||||
"""Admin page for entity relations graph visualization."""
|
"""Admin page for entity relations graph visualization."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
return render_template('admin/zopk_knowledge_graph.html')
|
return render_template('admin/zopk_knowledge_graph.html')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/graph/data')
|
@bp.route('/zopk-api/knowledge/graph/data')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_knowledge_graph_data():
|
def api_zopk_knowledge_graph_data():
|
||||||
"""Get graph data for entity co-occurrence visualization.
|
"""Get graph data for entity co-occurrence visualization.
|
||||||
|
|
||||||
@ -808,9 +767,6 @@ def api_zopk_knowledge_graph_data():
|
|||||||
"""
|
"""
|
||||||
global _graph_cache
|
global _graph_cache
|
||||||
|
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
# Build cache key from parameters
|
# Build cache key from parameters
|
||||||
entity_type = request.args.get('entity_type', '')
|
entity_type = request.args.get('entity_type', '')
|
||||||
min_cooccurrence = int(request.args.get('min_cooccurrence', 3))
|
min_cooccurrence = int(request.args.get('min_cooccurrence', 3))
|
||||||
@ -923,21 +879,17 @@ def api_zopk_knowledge_graph_data():
|
|||||||
|
|
||||||
@bp.route('/zopk/knowledge/fact-duplicates')
|
@bp.route('/zopk/knowledge/fact-duplicates')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_fact_duplicates():
|
def admin_zopk_fact_duplicates():
|
||||||
"""Panel deduplikacji faktów."""
|
"""Panel deduplikacji faktów."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
return render_template('admin/zopk_fact_duplicates.html')
|
return render_template('admin/zopk_fact_duplicates.html')
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/fact-duplicates')
|
@bp.route('/zopk-api/knowledge/fact-duplicates')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_fact_duplicates():
|
def api_zopk_fact_duplicates():
|
||||||
"""API - lista duplikatów faktów."""
|
"""API - lista duplikatów faktów."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import find_duplicate_facts
|
from zopk_knowledge_service import find_duplicate_facts
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -955,11 +907,9 @@ def api_zopk_fact_duplicates():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/fact-duplicates/merge', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/fact-duplicates/merge', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_fact_merge():
|
def api_zopk_fact_merge():
|
||||||
"""API - merge duplikatów faktów."""
|
"""API - merge duplikatów faktów."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import merge_facts
|
from zopk_knowledge_service import merge_facts
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -978,11 +928,9 @@ def api_zopk_fact_merge():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/auto-verify/entities', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/auto-verify/entities', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_auto_verify_entities():
|
def api_zopk_auto_verify_entities():
|
||||||
"""Auto-weryfikacja encji z wysoką liczbą wzmianek."""
|
"""Auto-weryfikacja encji z wysoką liczbą wzmianek."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import auto_verify_top_entities
|
from zopk_knowledge_service import auto_verify_top_entities
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -1000,11 +948,9 @@ def api_zopk_auto_verify_entities():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/auto-verify/facts', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/auto-verify/facts', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_auto_verify_facts():
|
def api_zopk_auto_verify_facts():
|
||||||
"""Auto-weryfikacja faktów z wysoką ważnością."""
|
"""Auto-weryfikacja faktów z wysoką ważnością."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import auto_verify_top_facts
|
from zopk_knowledge_service import auto_verify_top_facts
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -1022,11 +968,9 @@ def api_zopk_auto_verify_facts():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/auto-verify/similar', methods=['POST'])
|
@bp.route('/zopk-api/knowledge/auto-verify/similar', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_auto_verify_similar():
|
def api_zopk_auto_verify_similar():
|
||||||
"""Auto-weryfikacja faktów podobnych do już zweryfikowanych (uczenie się)."""
|
"""Auto-weryfikacja faktów podobnych do już zweryfikowanych (uczenie się)."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import auto_verify_similar_to_verified
|
from zopk_knowledge_service import auto_verify_similar_to_verified
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -1044,11 +988,9 @@ def api_zopk_auto_verify_similar():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/suggest-similar-facts')
|
@bp.route('/zopk-api/knowledge/suggest-similar-facts')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_suggest_similar_facts():
|
def api_zopk_suggest_similar_facts():
|
||||||
"""Pobierz sugestie faktów podobnych do zweryfikowanych (bez auto-weryfikacji)."""
|
"""Pobierz sugestie faktów podobnych do zweryfikowanych (bez auto-weryfikacji)."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import find_similar_to_verified_facts
|
from zopk_knowledge_service import find_similar_to_verified_facts
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -1069,11 +1011,9 @@ def api_zopk_suggest_similar_facts():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/knowledge/dashboard-stats')
|
@bp.route('/zopk-api/knowledge/dashboard-stats')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_dashboard_stats():
|
def api_zopk_dashboard_stats():
|
||||||
"""API - statystyki dashboardu."""
|
"""API - statystyki dashboardu."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import get_knowledge_dashboard_stats
|
from zopk_knowledge_service import get_knowledge_dashboard_stats
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -18,10 +18,12 @@ from sqlalchemy.sql import nullslast
|
|||||||
|
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal,
|
SessionLocal,
|
||||||
|
SystemRole,
|
||||||
ZOPKProject,
|
ZOPKProject,
|
||||||
ZOPKNews,
|
ZOPKNews,
|
||||||
ZOPKNewsFetchJob
|
ZOPKNewsFetchJob
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -29,11 +31,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/zopk/news')
|
@bp.route('/zopk/news')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news():
|
def admin_zopk_news():
|
||||||
"""Admin news management for ZOPK"""
|
"""Admin news management for ZOPK"""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -90,10 +90,9 @@ def admin_zopk_news():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/<int:news_id>/approve', methods=['POST'])
|
@bp.route('/zopk/news/<int:news_id>/approve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news_approve(news_id):
|
def admin_zopk_news_approve(news_id):
|
||||||
"""Approve a ZOPK news item"""
|
"""Approve a ZOPK news item"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -119,10 +118,9 @@ def admin_zopk_news_approve(news_id):
|
|||||||
|
|
||||||
@bp.route('/zopk/news/<int:news_id>/reject', methods=['POST'])
|
@bp.route('/zopk/news/<int:news_id>/reject', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news_reject(news_id):
|
def admin_zopk_news_reject(news_id):
|
||||||
"""Reject a ZOPK news item"""
|
"""Reject a ZOPK news item"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -152,10 +150,9 @@ def admin_zopk_news_reject(news_id):
|
|||||||
|
|
||||||
@bp.route('/zopk/news/add', methods=['POST'])
|
@bp.route('/zopk/news/add', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news_add():
|
def admin_zopk_news_add():
|
||||||
"""Manually add a ZOPK news item"""
|
"""Manually add a ZOPK news item"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -233,10 +230,9 @@ def admin_zopk_news_add():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/reject-old', methods=['POST'])
|
@bp.route('/zopk/news/reject-old', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_reject_old_news():
|
def admin_zopk_reject_old_news():
|
||||||
"""Reject all news from before a certain year (ZOPK didn't exist then)"""
|
"""Reject all news from before a certain year (ZOPK didn't exist then)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -278,10 +274,9 @@ def admin_zopk_reject_old_news():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/star-counts')
|
@bp.route('/zopk/news/star-counts')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news_star_counts():
|
def admin_zopk_news_star_counts():
|
||||||
"""Get counts of pending news items grouped by star rating"""
|
"""Get counts of pending news items grouped by star rating"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -318,10 +313,9 @@ def admin_zopk_news_star_counts():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/reject-by-stars', methods=['POST'])
|
@bp.route('/zopk/news/reject-by-stars', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_reject_by_stars():
|
def admin_zopk_reject_by_stars():
|
||||||
"""Reject all pending news items with specified star ratings"""
|
"""Reject all pending news items with specified star ratings"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -383,10 +377,9 @@ def admin_zopk_reject_by_stars():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/evaluate-ai', methods=['POST'])
|
@bp.route('/zopk/news/evaluate-ai', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_evaluate_ai():
|
def admin_zopk_evaluate_ai():
|
||||||
"""Evaluate pending news for ZOPK relevance using Gemini AI"""
|
"""Evaluate pending news for ZOPK relevance using Gemini AI"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_news_service import evaluate_pending_news
|
from zopk_news_service import evaluate_pending_news
|
||||||
|
|
||||||
@ -418,10 +411,9 @@ def admin_zopk_evaluate_ai():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/reevaluate-scores', methods=['POST'])
|
@bp.route('/zopk/news/reevaluate-scores', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_reevaluate_scores():
|
def admin_zopk_reevaluate_scores():
|
||||||
"""Re-evaluate news items that have ai_relevant but no ai_relevance_score (1-5 stars)"""
|
"""Re-evaluate news items that have ai_relevant but no ai_relevance_score (1-5 stars)"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_news_service import reevaluate_news_without_score
|
from zopk_news_service import reevaluate_news_without_score
|
||||||
|
|
||||||
@ -453,6 +445,7 @@ def admin_zopk_reevaluate_scores():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/reevaluate-low-scores', methods=['POST'])
|
@bp.route('/zopk/news/reevaluate-low-scores', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_reevaluate_low_scores():
|
def admin_zopk_reevaluate_low_scores():
|
||||||
"""
|
"""
|
||||||
Re-evaluate news with low AI scores (1-2★) that contain key ZOPK topics.
|
Re-evaluate news with low AI scores (1-2★) that contain key ZOPK topics.
|
||||||
@ -461,9 +454,6 @@ def admin_zopk_reevaluate_low_scores():
|
|||||||
Old articles scored low before these topics were recognized will be re-evaluated
|
Old articles scored low before these topics were recognized will be re-evaluated
|
||||||
and potentially upgraded.
|
and potentially upgraded.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_news_service import reevaluate_low_score_news
|
from zopk_news_service import reevaluate_low_score_news
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -496,6 +486,7 @@ def admin_zopk_reevaluate_low_scores():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/search-news', methods=['POST'])
|
@bp.route('/zopk-api/search-news', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_search_news():
|
def api_zopk_search_news():
|
||||||
"""
|
"""
|
||||||
Search for ZOPK news using multiple sources with cross-verification.
|
Search for ZOPK news using multiple sources with cross-verification.
|
||||||
@ -509,9 +500,6 @@ def api_zopk_search_news():
|
|||||||
- 1 source → pending (manual review)
|
- 1 source → pending (manual review)
|
||||||
- 3+ sources → auto_approved
|
- 3+ sources → auto_approved
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_news_service import ZOPKNewsService
|
from zopk_news_service import ZOPKNewsService
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -594,6 +582,7 @@ def api_zopk_search_news():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/scrape-stats')
|
@bp.route('/zopk/news/scrape-stats')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_scrape_stats():
|
def admin_zopk_scrape_stats():
|
||||||
"""
|
"""
|
||||||
Get content scraping statistics.
|
Get content scraping statistics.
|
||||||
@ -606,9 +595,6 @@ def admin_zopk_scrape_stats():
|
|||||||
- skipped: Skipped (social media, paywalls)
|
- skipped: Skipped (social media, paywalls)
|
||||||
- ready_for_extraction: Scraped but not yet processed for knowledge
|
- ready_for_extraction: Scraped but not yet processed for knowledge
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_content_scraper import get_scrape_stats
|
from zopk_content_scraper import get_scrape_stats
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -627,6 +613,7 @@ def admin_zopk_scrape_stats():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/scrape-content', methods=['POST'])
|
@bp.route('/zopk/news/scrape-content', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_scrape_content():
|
def admin_zopk_scrape_content():
|
||||||
"""
|
"""
|
||||||
Batch scrape article content from source URLs.
|
Batch scrape article content from source URLs.
|
||||||
@ -642,9 +629,6 @@ def admin_zopk_scrape_content():
|
|||||||
- errors: list of error details
|
- errors: list of error details
|
||||||
- scraped_articles: list of scraped article info
|
- scraped_articles: list of scraped article info
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_content_scraper import ZOPKContentScraper
|
from zopk_content_scraper import ZOPKContentScraper
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -673,13 +657,11 @@ def admin_zopk_scrape_content():
|
|||||||
|
|
||||||
@bp.route('/zopk/news/<int:news_id>/scrape', methods=['POST'])
|
@bp.route('/zopk/news/<int:news_id>/scrape', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_scrape_single(news_id):
|
def admin_zopk_scrape_single(news_id):
|
||||||
"""
|
"""
|
||||||
Scrape content for a single article.
|
Scrape content for a single article.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_content_scraper import ZOPKContentScraper
|
from zopk_content_scraper import ZOPKContentScraper
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -711,6 +693,7 @@ def admin_zopk_scrape_single(news_id):
|
|||||||
|
|
||||||
@bp.route('/zopk/news/scrape-content/stream', methods=['GET'])
|
@bp.route('/zopk/news/scrape-content/stream', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_news_scrape_stream():
|
def admin_zopk_news_scrape_stream():
|
||||||
"""
|
"""
|
||||||
Stream scraping progress using Server-Sent Events.
|
Stream scraping progress using Server-Sent Events.
|
||||||
@ -719,9 +702,6 @@ def admin_zopk_news_scrape_stream():
|
|||||||
- limit: int (default 50)
|
- limit: int (default 50)
|
||||||
- force: bool (default false)
|
- force: bool (default false)
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
|
||||||
|
|
||||||
from zopk_content_scraper import ZOPKContentScraper, MAX_RETRY_ATTEMPTS
|
from zopk_content_scraper import ZOPKContentScraper, MAX_RETRY_ATTEMPTS
|
||||||
|
|
||||||
limit = request.args.get('limit', 50, type=int)
|
limit = request.args.get('limit', 50, type=int)
|
||||||
|
|||||||
@ -13,8 +13,10 @@ from flask_login import current_user, login_required
|
|||||||
|
|
||||||
from database import (
|
from database import (
|
||||||
SessionLocal,
|
SessionLocal,
|
||||||
|
SystemRole,
|
||||||
ZOPKMilestone
|
ZOPKMilestone
|
||||||
)
|
)
|
||||||
|
from utils.decorators import role_required
|
||||||
from . import bp
|
from . import bp
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -22,11 +24,9 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@bp.route('/zopk/timeline')
|
@bp.route('/zopk/timeline')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def admin_zopk_timeline():
|
def admin_zopk_timeline():
|
||||||
"""Panel Timeline ZOPK."""
|
"""Panel Timeline ZOPK."""
|
||||||
if not current_user.is_admin:
|
|
||||||
flash('Brak uprawnień.', 'error')
|
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
return render_template('admin/zopk_timeline.html')
|
return render_template('admin/zopk_timeline.html')
|
||||||
|
|
||||||
|
|
||||||
@ -58,10 +58,9 @@ def api_zopk_milestones():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/milestones', methods=['POST'])
|
@bp.route('/zopk-api/milestones', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_milestone_create():
|
def api_zopk_milestone_create():
|
||||||
"""API - utworzenie kamienia milowego."""
|
"""API - utworzenie kamienia milowego."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -88,10 +87,9 @@ def api_zopk_milestone_create():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['PUT'])
|
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['PUT'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_milestone_update(milestone_id):
|
def api_zopk_milestone_update(milestone_id):
|
||||||
"""API - aktualizacja kamienia milowego."""
|
"""API - aktualizacja kamienia milowego."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -126,10 +124,9 @@ def api_zopk_milestone_update(milestone_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['DELETE'])
|
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['DELETE'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_milestone_delete(milestone_id):
|
def api_zopk_milestone_delete(milestone_id):
|
||||||
"""API - usunięcie kamienia milowego."""
|
"""API - usunięcie kamienia milowego."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
try:
|
try:
|
||||||
@ -149,10 +146,9 @@ def api_zopk_milestone_delete(milestone_id):
|
|||||||
|
|
||||||
@bp.route('/zopk-api/timeline/suggestions')
|
@bp.route('/zopk-api/timeline/suggestions')
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_timeline_suggestions():
|
def api_zopk_timeline_suggestions():
|
||||||
"""API - sugestie kamieni milowych z bazy wiedzy."""
|
"""API - sugestie kamieni milowych z bazy wiedzy."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import get_timeline_suggestions
|
from zopk_knowledge_service import get_timeline_suggestions
|
||||||
|
|
||||||
@ -177,10 +173,9 @@ def api_zopk_timeline_suggestions():
|
|||||||
|
|
||||||
@bp.route('/zopk-api/timeline/suggestions/approve', methods=['POST'])
|
@bp.route('/zopk-api/timeline/suggestions/approve', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@role_required(SystemRole.ADMIN)
|
||||||
def api_zopk_timeline_suggestion_approve():
|
def api_zopk_timeline_suggestion_approve():
|
||||||
"""API - zatwierdzenie sugestii i utworzenie kamienia milowego."""
|
"""API - zatwierdzenie sugestii i utworzenie kamienia milowego."""
|
||||||
if not current_user.is_admin:
|
|
||||||
return jsonify({'error': 'Forbidden'}), 403
|
|
||||||
|
|
||||||
from zopk_knowledge_service import create_milestone_from_suggestion
|
from zopk_knowledge_service import create_milestone_from_suggestion
|
||||||
|
|
||||||
|
|||||||
@ -511,9 +511,9 @@ def api_enrich_company_ai(company_id):
|
|||||||
'error': 'Firma nie znaleziona'
|
'error': 'Firma nie znaleziona'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Check permissions: admin or company owner
|
# Check permissions: user with company edit rights
|
||||||
logger.info(f"Permission check: user={current_user.email}, is_admin={current_user.is_admin}, user_company_id={current_user.company_id}, target_company_id={company.id}")
|
logger.info(f"Permission check: user={current_user.email}, is_admin={current_user.is_admin}, user_company_id={current_user.company_id}, target_company_id={company.id}")
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnien. Tylko administrator lub wlasciciel firmy moze wzbogacac dane.'
|
'error': 'Brak uprawnien. Tylko administrator lub wlasciciel firmy moze wzbogacac dane.'
|
||||||
@ -755,8 +755,8 @@ def api_get_proposals(company_id):
|
|||||||
if not company:
|
if not company:
|
||||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions - user with company edit rights
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
proposals = db.query(AiEnrichmentProposal).filter_by(
|
proposals = db.query(AiEnrichmentProposal).filter_by(
|
||||||
@ -798,8 +798,8 @@ def api_approve_proposal(company_id, proposal_id):
|
|||||||
if not company:
|
if not company:
|
||||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||||
|
|
||||||
# Check permissions - only admin or company owner
|
# Check permissions - user with company edit rights
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
proposal = db.query(AiEnrichmentProposal).filter_by(
|
proposal = db.query(AiEnrichmentProposal).filter_by(
|
||||||
@ -904,8 +904,8 @@ def api_reject_proposal(company_id, proposal_id):
|
|||||||
if not company:
|
if not company:
|
||||||
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Firma nie istnieje'}), 404
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions - user with company edit rights
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
proposal = db.query(AiEnrichmentProposal).filter_by(
|
proposal = db.query(AiEnrichmentProposal).filter_by(
|
||||||
@ -972,7 +972,7 @@ def test_sanitization():
|
|||||||
Admin API: Test sensitive data detection without saving.
|
Admin API: Test sensitive data detection without saving.
|
||||||
Allows admins to verify what data would be sanitized.
|
Allows admins to verify what data would be sanitized.
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
return jsonify({'success': False, 'error': 'Admin access required'}), 403
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -283,14 +283,12 @@ def api_gbp_audit_trigger():
|
|||||||
'error': 'Firma nie znaleziona lub nieaktywna.'
|
'error': 'Firma nie znaleziona lub nieaktywna.'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Check access: admin can audit any company, member only their own
|
# Check access: users with company edit rights can audit
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
# Check if user is associated with this company
|
return jsonify({
|
||||||
if current_user.company_id != company.id:
|
'success': False,
|
||||||
return jsonify({
|
'error': 'Brak uprawnień. Możesz audytować tylko własną firmę.'
|
||||||
'success': False,
|
}), 403
|
||||||
'error': 'Brak uprawnień. Możesz audytować tylko własną firmę.'
|
|
||||||
}), 403
|
|
||||||
|
|
||||||
logger.info(f"GBP audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
logger.info(f"GBP audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||||
|
|
||||||
|
|||||||
@ -233,8 +233,8 @@ def api_edit_recommendation(rec_id):
|
|||||||
'error': 'Rekomendacja nie znaleziona'
|
'error': 'Rekomendacja nie znaleziona'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Check authorization - user must be the owner OR admin
|
# Check authorization - user must be the owner OR have admin panel access
|
||||||
if recommendation.user_id != current_user.id and not current_user.is_admin:
|
if recommendation.user_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnień do edycji tej rekomendacji'
|
'error': 'Brak uprawnień do edycji tej rekomendacji'
|
||||||
@ -313,8 +313,8 @@ def api_delete_recommendation(rec_id):
|
|||||||
'error': 'Rekomendacja nie znaleziona'
|
'error': 'Rekomendacja nie znaleziona'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Check authorization - user must be the owner OR admin
|
# Check authorization - user must be the owner OR have admin panel access
|
||||||
if recommendation.user_id != current_user.id and not current_user.is_admin:
|
if recommendation.user_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnień do usunięcia tej rekomendacji'
|
'error': 'Brak uprawnień do usunięcia tej rekomendacji'
|
||||||
|
|||||||
@ -336,8 +336,8 @@ def api_seo_audit_trigger():
|
|||||||
- Success: Full SEO audit results saved to database
|
- Success: Full SEO audit results saved to database
|
||||||
- Error: Error message with status code
|
- Error: Error message with status code
|
||||||
"""
|
"""
|
||||||
# Admin-only check
|
# Check admin panel access
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty SEO.'
|
'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty SEO.'
|
||||||
|
|||||||
@ -88,13 +88,12 @@ def api_social_audit_trigger():
|
|||||||
'error': 'Firma nie znaleziona lub nieaktywna.'
|
'error': 'Firma nie znaleziona lub nieaktywna.'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Access control - admin can audit all, users only their company
|
# Access control - users with admin panel access or company edit rights can audit
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
if current_user.company_id != company.id:
|
return jsonify({
|
||||||
return jsonify({
|
'success': False,
|
||||||
'success': False,
|
'error': 'Brak uprawnień do audytu social media tej firmy.'
|
||||||
'error': 'Brak uprawnień do audytu social media tej firmy.'
|
}), 403
|
||||||
}), 403
|
|
||||||
|
|
||||||
logger.info(f"Social Media audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
logger.info(f"Social Media audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||||
|
|
||||||
|
|||||||
11
blueprints/audit/CLAUDE.md
Normal file
11
blueprints/audit/CLAUDE.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<claude-mem-context>
|
||||||
|
# Recent Activity
|
||||||
|
|
||||||
|
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||||
|
|
||||||
|
### Jan 31, 2026
|
||||||
|
|
||||||
|
| ID | Time | T | Title | Read |
|
||||||
|
|----|------|---|-------|------|
|
||||||
|
| #175 | 6:24 PM | 🔵 | Nordabiz audit blueprint provides user-facing dashboards for SEO, GBP, social media, and IT audits | ~542 |
|
||||||
|
</claude-mem-context>
|
||||||
@ -66,11 +66,10 @@ def seo_audit_dashboard(slug):
|
|||||||
flash('Firma nie została znaleziona.', 'error')
|
flash('Firma nie została znaleziona.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Access control: admin can view any company, member only their own
|
# Access control: users with company edit rights can view
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
if current_user.company_id != company.id:
|
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
||||||
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Get latest SEO analysis for this company
|
# Get latest SEO analysis for this company
|
||||||
analysis = db.query(CompanyWebsiteAnalysis).filter(
|
analysis = db.query(CompanyWebsiteAnalysis).filter(
|
||||||
@ -90,8 +89,8 @@ def seo_audit_dashboard(slug):
|
|||||||
'url': analysis.website_url
|
'url': analysis.website_url
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine if user can run audit (admin or company owner)
|
# Determine if user can run audit (user with company edit rights)
|
||||||
can_audit = current_user.is_admin or current_user.company_id == company.id
|
can_audit = current_user.can_edit_company(company.id)
|
||||||
|
|
||||||
logger.info(f"SEO audit dashboard viewed by {current_user.email} for company: {company.name}")
|
logger.info(f"SEO audit dashboard viewed by {current_user.email} for company: {company.name}")
|
||||||
|
|
||||||
@ -139,11 +138,10 @@ def social_audit_dashboard(slug):
|
|||||||
flash('Firma nie została znaleziona.', 'error')
|
flash('Firma nie została znaleziona.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Access control - admin can view all, users only their company
|
# Access control - users with company edit rights can view
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
if current_user.company_id != company.id:
|
flash('Brak uprawnień do wyświetlenia audytu social media tej firmy.', 'error')
|
||||||
flash('Brak uprawnień do wyświetlenia audytu social media tej firmy.', 'error')
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Get social media profiles for this company
|
# Get social media profiles for this company
|
||||||
social_profiles = db.query(CompanySocialMedia).filter(
|
social_profiles = db.query(CompanySocialMedia).filter(
|
||||||
@ -179,8 +177,8 @@ def social_audit_dashboard(slug):
|
|||||||
'score': score
|
'score': score
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine if user can run audit (admin or company owner)
|
# Determine if user can run audit (user with company edit rights)
|
||||||
can_audit = current_user.is_admin or current_user.company_id == company.id
|
can_audit = current_user.can_edit_company(company.id)
|
||||||
|
|
||||||
logger.info(f"Social Media audit dashboard viewed by {current_user.email} for company: {company.name}")
|
logger.info(f"Social Media audit dashboard viewed by {current_user.email} for company: {company.name}")
|
||||||
|
|
||||||
@ -233,11 +231,10 @@ def gbp_audit_dashboard(slug):
|
|||||||
flash('Firma nie została znaleziona.', 'error')
|
flash('Firma nie została znaleziona.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Access control: admin can view any company, member only their own
|
# Access control: users with company edit rights can view
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
if current_user.company_id != company.id:
|
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
||||||
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Get latest audit for this company
|
# Get latest audit for this company
|
||||||
audit = gbp_get_company_audit(db, company.id)
|
audit = gbp_get_company_audit(db, company.id)
|
||||||
@ -245,8 +242,8 @@ def gbp_audit_dashboard(slug):
|
|||||||
# If no audit exists, we still render the page (template handles this)
|
# If no audit exists, we still render the page (template handles this)
|
||||||
# The user can trigger an audit from the dashboard
|
# The user can trigger an audit from the dashboard
|
||||||
|
|
||||||
# Determine if user can run audit (admin or company owner)
|
# Determine if user can run audit (user with company edit rights)
|
||||||
can_audit = current_user.is_admin or current_user.company_id == company.id
|
can_audit = current_user.can_edit_company(company.id)
|
||||||
|
|
||||||
logger.info(f"GBP audit dashboard viewed by {current_user.email} for company: {company.name}")
|
logger.info(f"GBP audit dashboard viewed by {current_user.email} for company: {company.name}")
|
||||||
|
|
||||||
@ -297,11 +294,10 @@ def it_audit_dashboard(slug):
|
|||||||
flash('Firma nie została znaleziona.', 'error')
|
flash('Firma nie została znaleziona.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Access control: admin can view any company, member only their own
|
# Access control: users with company edit rights can view
|
||||||
if not current_user.is_admin:
|
if not current_user.can_edit_company(company.id):
|
||||||
if current_user.company_id != company.id:
|
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
||||||
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
|
return redirect(url_for('dashboard'))
|
||||||
return redirect(url_for('dashboard'))
|
|
||||||
|
|
||||||
# Get latest IT audit for this company
|
# Get latest IT audit for this company
|
||||||
audit = db.query(ITAudit).filter(
|
audit = db.query(ITAudit).filter(
|
||||||
@ -356,8 +352,8 @@ def it_audit_dashboard(slug):
|
|||||||
'recommendations': audit.recommendations
|
'recommendations': audit.recommendations
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine if user can edit audit (admin or company owner)
|
# Determine if user can edit audit (user with company edit rights)
|
||||||
can_edit = current_user.is_admin or current_user.company_id == company.id
|
can_edit = current_user.can_edit_company(company.id)
|
||||||
|
|
||||||
logger.info(f"IT audit dashboard viewed by {current_user.email} for company: {company.name}")
|
logger.info(f"IT audit dashboard viewed by {current_user.email} for company: {company.name}")
|
||||||
|
|
||||||
|
|||||||
11
blueprints/chat/CLAUDE.md
Normal file
11
blueprints/chat/CLAUDE.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<claude-mem-context>
|
||||||
|
# Recent Activity
|
||||||
|
|
||||||
|
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||||
|
|
||||||
|
### Jan 31, 2026
|
||||||
|
|
||||||
|
| ID | Time | T | Title | Read |
|
||||||
|
|----|------|---|-------|------|
|
||||||
|
| #180 | 6:25 PM | 🔵 | Nordabiz project architecture analyzed revealing 16+ Flask blueprints with modular organization | ~831 |
|
||||||
|
</claude-mem-context>
|
||||||
@ -394,8 +394,8 @@ def chat_feedback():
|
|||||||
@login_required
|
@login_required
|
||||||
def chat_analytics():
|
def chat_analytics():
|
||||||
"""Admin dashboard for chat analytics"""
|
"""Admin dashboard for chat analytics"""
|
||||||
# Only admins can access
|
# Only users with admin panel access can view chat analytics
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
flash('Brak uprawnień do tej strony.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
|||||||
@ -159,7 +159,7 @@ def view(classified_id):
|
|||||||
questions_query = db.query(ClassifiedQuestion).filter(
|
questions_query = db.query(ClassifiedQuestion).filter(
|
||||||
ClassifiedQuestion.classified_id == classified.id
|
ClassifiedQuestion.classified_id == classified.id
|
||||||
)
|
)
|
||||||
if classified.author_id != current_user.id and not current_user.is_admin:
|
if classified.author_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
questions_query = questions_query.filter(ClassifiedQuestion.is_public == True)
|
questions_query = questions_query.filter(ClassifiedQuestion.is_public == True)
|
||||||
questions = questions_query.order_by(ClassifiedQuestion.created_at.asc()).all()
|
questions = questions_query.order_by(ClassifiedQuestion.created_at.asc()).all()
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ def close(classified_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def delete(classified_id):
|
def delete(classified_id):
|
||||||
"""Usuń ogłoszenie (admin only)"""
|
"""Usuń ogłoszenie (admin only)"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -233,7 +233,7 @@ def delete(classified_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def toggle_active(classified_id):
|
def toggle_active(classified_id):
|
||||||
"""Aktywuj/dezaktywuj ogłoszenie (admin only)"""
|
"""Aktywuj/dezaktywuj ogłoszenie (admin only)"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -323,8 +323,8 @@ def list_interests(classified_id):
|
|||||||
if not classified:
|
if not classified:
|
||||||
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
||||||
|
|
||||||
# Tylko autor może widzieć pełną listę
|
# Tylko autor może widzieć pełną listę (lub admin)
|
||||||
if classified.author_id != current_user.id and not current_user.is_admin:
|
if classified.author_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
interests = db.query(ClassifiedInterest).filter(
|
interests = db.query(ClassifiedInterest).filter(
|
||||||
@ -469,7 +469,7 @@ def hide_question(classified_id, question_id):
|
|||||||
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Ogłoszenie nie istnieje'}), 404
|
||||||
|
|
||||||
# Tylko autor ogłoszenia lub admin może ukrywać
|
# Tylko autor ogłoszenia lub admin może ukrywać
|
||||||
if classified.author_id != current_user.id and not current_user.is_admin:
|
if classified.author_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
question = db.query(ClassifiedQuestion).filter(
|
question = db.query(ClassifiedQuestion).filter(
|
||||||
@ -511,7 +511,7 @@ def list_questions(classified_id):
|
|||||||
ClassifiedQuestion.classified_id == classified_id
|
ClassifiedQuestion.classified_id == classified_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if classified.author_id != current_user.id and not current_user.is_admin:
|
if classified.author_id != current_user.id and not current_user.can_access_admin_panel():
|
||||||
query = query.filter(ClassifiedQuestion.is_public == True)
|
query = query.filter(ClassifiedQuestion.is_public == True)
|
||||||
|
|
||||||
questions = query.order_by(desc(ClassifiedQuestion.created_at)).all()
|
questions = query.order_by(desc(ClassifiedQuestion.created_at)).all()
|
||||||
|
|||||||
@ -118,7 +118,7 @@ def detail(contact_id):
|
|||||||
).order_by(ExternalContact.last_name).limit(5).all()
|
).order_by(ExternalContact.last_name).limit(5).all()
|
||||||
|
|
||||||
# Check if current user can edit (creator or admin)
|
# Check if current user can edit (creator or admin)
|
||||||
can_edit = (current_user.is_admin or
|
can_edit = (current_user.can_access_admin_panel() or
|
||||||
(contact.created_by and contact.created_by == current_user.id))
|
(contact.created_by and contact.created_by == current_user.id))
|
||||||
|
|
||||||
return render_template('contacts/detail.html',
|
return render_template('contacts/detail.html',
|
||||||
@ -213,8 +213,8 @@ def edit(contact_id):
|
|||||||
flash('Kontakt nie został znaleziony.', 'error')
|
flash('Kontakt nie został znaleziony.', 'error')
|
||||||
return redirect(url_for('.contacts_list'))
|
return redirect(url_for('.contacts_list'))
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions - creator or admin
|
||||||
if not current_user.is_admin and contact.created_by != current_user.id:
|
if not current_user.can_access_admin_panel() and contact.created_by != current_user.id:
|
||||||
flash('Nie masz uprawnień do edycji tego kontaktu.', 'error')
|
flash('Nie masz uprawnień do edycji tego kontaktu.', 'error')
|
||||||
return redirect(url_for('.contact_detail', contact_id=contact_id))
|
return redirect(url_for('.contact_detail', contact_id=contact_id))
|
||||||
|
|
||||||
@ -282,8 +282,8 @@ def delete(contact_id):
|
|||||||
flash('Kontakt nie został znaleziony.', 'error')
|
flash('Kontakt nie został znaleziony.', 'error')
|
||||||
return redirect(url_for('.contacts_list'))
|
return redirect(url_for('.contacts_list'))
|
||||||
|
|
||||||
# Check permissions
|
# Check permissions - creator or admin
|
||||||
if not current_user.is_admin and contact.created_by != current_user.id:
|
if not current_user.can_access_admin_panel() and contact.created_by != current_user.id:
|
||||||
flash('Nie masz uprawnień do usunięcia tego kontaktu.', 'error')
|
flash('Nie masz uprawnień do usunięcia tego kontaktu.', 'error')
|
||||||
return redirect(url_for('.contact_detail', contact_id=contact_id))
|
return redirect(url_for('.contact_detail', contact_id=contact_id))
|
||||||
|
|
||||||
|
|||||||
@ -221,8 +221,8 @@ def forum_topic(topic_id):
|
|||||||
flash('Temat nie istnieje.', 'error')
|
flash('Temat nie istnieje.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
# Check if topic is soft-deleted (only admins can view)
|
# Check if topic is soft-deleted (only moderators can view)
|
||||||
if topic.is_deleted and not current_user.is_admin:
|
if topic.is_deleted and not current_user.can_moderate_forum():
|
||||||
flash('Temat nie istnieje.', 'error')
|
flash('Temat nie istnieje.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
@ -237,9 +237,9 @@ def forum_topic(topic_id):
|
|||||||
if not existing_topic_read:
|
if not existing_topic_read:
|
||||||
db.add(ForumTopicRead(topic_id=topic.id, user_id=current_user.id))
|
db.add(ForumTopicRead(topic_id=topic.id, user_id=current_user.id))
|
||||||
|
|
||||||
# Filter soft-deleted replies for non-admins
|
# Filter soft-deleted replies for non-moderators
|
||||||
visible_replies = [r for r in topic.replies
|
visible_replies = [r for r in topic.replies
|
||||||
if not r.is_deleted or current_user.is_admin]
|
if not r.is_deleted or current_user.can_moderate_forum()]
|
||||||
|
|
||||||
# Record read for all visible replies
|
# Record read for all visible replies
|
||||||
for reply in visible_replies:
|
for reply in visible_replies:
|
||||||
@ -397,7 +397,7 @@ def forum_reply(topic_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum():
|
def admin_forum():
|
||||||
"""Admin panel for forum moderation"""
|
"""Admin panel for forum moderation"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
flash('Brak uprawnień do tej strony.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
@ -451,7 +451,7 @@ def admin_forum():
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_pin(topic_id):
|
def admin_forum_pin(topic_id):
|
||||||
"""Toggle topic pin status"""
|
"""Toggle topic pin status"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -477,7 +477,7 @@ def admin_forum_pin(topic_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_lock(topic_id):
|
def admin_forum_lock(topic_id):
|
||||||
"""Toggle topic lock status"""
|
"""Toggle topic lock status"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -503,7 +503,7 @@ def admin_forum_lock(topic_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_delete_topic(topic_id):
|
def admin_forum_delete_topic(topic_id):
|
||||||
"""Delete topic and all its replies"""
|
"""Delete topic and all its replies"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -529,7 +529,7 @@ def admin_forum_delete_topic(topic_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_delete_reply(reply_id):
|
def admin_forum_delete_reply(reply_id):
|
||||||
"""Delete a reply"""
|
"""Delete a reply"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -554,8 +554,8 @@ def admin_forum_delete_reply(reply_id):
|
|||||||
@bp.route('/admin/forum/topic/<int:topic_id>/status', methods=['POST'])
|
@bp.route('/admin/forum/topic/<int:topic_id>/status', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def admin_forum_change_status(topic_id):
|
def admin_forum_change_status(topic_id):
|
||||||
"""Change topic status (admin only)"""
|
"""Change topic status (moderators only)"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -593,8 +593,8 @@ def admin_forum_change_status(topic_id):
|
|||||||
@bp.route('/admin/forum/bulk-action', methods=['POST'])
|
@bp.route('/admin/forum/bulk-action', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def admin_forum_bulk_action():
|
def admin_forum_bulk_action():
|
||||||
"""Perform bulk action on multiple topics (admin only)"""
|
"""Perform bulk action on multiple topics (moderators only)"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -700,12 +700,12 @@ def edit_topic(topic_id):
|
|||||||
if not topic:
|
if not topic:
|
||||||
return jsonify({'success': False, 'error': 'Temat nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Temat nie istnieje'}), 404
|
||||||
|
|
||||||
# Check ownership (unless admin)
|
# Check ownership (unless moderator)
|
||||||
if topic.author_id != current_user.id and not current_user.is_admin:
|
if topic.author_id != current_user.id and not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
# Check time limit (unless admin)
|
# Check time limit (unless moderator)
|
||||||
if not current_user.is_admin and not _can_edit_content(topic.created_at):
|
if not current_user.can_moderate_forum() and not _can_edit_content(topic.created_at):
|
||||||
return jsonify({'success': False, 'error': 'Minął limit czasu edycji (24h)'}), 403
|
return jsonify({'success': False, 'error': 'Minął limit czasu edycji (24h)'}), 403
|
||||||
|
|
||||||
if topic.is_locked:
|
if topic.is_locked:
|
||||||
@ -756,12 +756,12 @@ def edit_reply(reply_id):
|
|||||||
if not reply:
|
if not reply:
|
||||||
return jsonify({'success': False, 'error': 'Odpowiedź nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Odpowiedź nie istnieje'}), 404
|
||||||
|
|
||||||
# Check ownership (unless admin)
|
# Check ownership (unless moderator)
|
||||||
if reply.author_id != current_user.id and not current_user.is_admin:
|
if reply.author_id != current_user.id and not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
# Check time limit (unless admin)
|
# Check time limit (unless moderator)
|
||||||
if not current_user.is_admin and not _can_edit_content(reply.created_at):
|
if not current_user.can_moderate_forum() and not _can_edit_content(reply.created_at):
|
||||||
return jsonify({'success': False, 'error': 'Minął limit czasu edycji (24h)'}), 403
|
return jsonify({'success': False, 'error': 'Minął limit czasu edycji (24h)'}), 403
|
||||||
|
|
||||||
# Check if topic is locked
|
# Check if topic is locked
|
||||||
@ -809,7 +809,7 @@ def delete_own_reply(reply_id):
|
|||||||
return jsonify({'success': False, 'error': 'Odpowiedź nie istnieje'}), 404
|
return jsonify({'success': False, 'error': 'Odpowiedź nie istnieje'}), 404
|
||||||
|
|
||||||
# Check ownership
|
# Check ownership
|
||||||
if reply.author_id != current_user.id and not current_user.is_admin:
|
if reply.author_id != current_user.id and not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
# Check if topic is locked
|
# Check if topic is locked
|
||||||
@ -1094,8 +1094,8 @@ def report_content():
|
|||||||
@bp.route('/admin/forum/topic/<int:topic_id>/admin-edit', methods=['POST'])
|
@bp.route('/admin/forum/topic/<int:topic_id>/admin-edit', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def admin_edit_topic(topic_id):
|
def admin_edit_topic(topic_id):
|
||||||
"""Admin: Edit any topic content"""
|
"""Moderator: Edit any topic content"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -1150,8 +1150,8 @@ def admin_edit_topic(topic_id):
|
|||||||
@bp.route('/admin/forum/reply/<int:reply_id>/admin-edit', methods=['POST'])
|
@bp.route('/admin/forum/reply/<int:reply_id>/admin-edit', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def admin_edit_reply(reply_id):
|
def admin_edit_reply(reply_id):
|
||||||
"""Admin: Edit any reply content"""
|
"""Moderator: Edit any reply content"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -1196,8 +1196,8 @@ def admin_edit_reply(reply_id):
|
|||||||
@bp.route('/admin/forum/reply/<int:reply_id>/solution', methods=['POST'])
|
@bp.route('/admin/forum/reply/<int:reply_id>/solution', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def mark_as_solution(reply_id):
|
def mark_as_solution(reply_id):
|
||||||
"""Admin: Mark reply as solution"""
|
"""Moderator: Mark reply as solution"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -1251,8 +1251,8 @@ def mark_as_solution(reply_id):
|
|||||||
@bp.route('/admin/forum/topic/<int:topic_id>/restore', methods=['POST'])
|
@bp.route('/admin/forum/topic/<int:topic_id>/restore', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def restore_topic(topic_id):
|
def restore_topic(topic_id):
|
||||||
"""Admin: Restore soft-deleted topic"""
|
"""Moderator: Restore soft-deleted topic"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -1281,8 +1281,8 @@ def restore_topic(topic_id):
|
|||||||
@bp.route('/admin/forum/reply/<int:reply_id>/restore', methods=['POST'])
|
@bp.route('/admin/forum/reply/<int:reply_id>/restore', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def restore_reply(reply_id):
|
def restore_reply(reply_id):
|
||||||
"""Admin: Restore soft-deleted reply"""
|
"""Moderator: Restore soft-deleted reply"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -1311,8 +1311,8 @@ def restore_reply(reply_id):
|
|||||||
@bp.route('/admin/forum/reports')
|
@bp.route('/admin/forum/reports')
|
||||||
@login_required
|
@login_required
|
||||||
def admin_forum_reports():
|
def admin_forum_reports():
|
||||||
"""Admin: View all reports"""
|
"""Moderator: View all reports"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
flash('Brak uprawnień do tej strony.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
@ -1348,8 +1348,8 @@ def admin_forum_reports():
|
|||||||
@bp.route('/admin/forum/report/<int:report_id>/review', methods=['POST'])
|
@bp.route('/admin/forum/report/<int:report_id>/review', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def review_report(report_id):
|
def review_report(report_id):
|
||||||
"""Admin: Review a report"""
|
"""Moderator: Review a report"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -1383,8 +1383,8 @@ def review_report(report_id):
|
|||||||
@bp.route('/admin/forum/topic/<int:topic_id>/history')
|
@bp.route('/admin/forum/topic/<int:topic_id>/history')
|
||||||
@login_required
|
@login_required
|
||||||
def topic_edit_history(topic_id):
|
def topic_edit_history(topic_id):
|
||||||
"""Admin: View topic edit history"""
|
"""Moderator: View topic edit history"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -1412,8 +1412,8 @@ def topic_edit_history(topic_id):
|
|||||||
@bp.route('/admin/forum/reply/<int:reply_id>/history')
|
@bp.route('/admin/forum/reply/<int:reply_id>/history')
|
||||||
@login_required
|
@login_required
|
||||||
def reply_edit_history(reply_id):
|
def reply_edit_history(reply_id):
|
||||||
"""Admin: View reply edit history"""
|
"""Moderator: View reply edit history"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403
|
||||||
|
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
@ -1441,8 +1441,8 @@ def reply_edit_history(reply_id):
|
|||||||
@bp.route('/admin/forum/deleted')
|
@bp.route('/admin/forum/deleted')
|
||||||
@login_required
|
@login_required
|
||||||
def admin_deleted_content():
|
def admin_deleted_content():
|
||||||
"""Admin: View soft-deleted topics and replies"""
|
"""Moderator: View soft-deleted topics and replies"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
flash('Brak uprawnień do tej strony.', 'error')
|
flash('Brak uprawnień do tej strony.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
@ -1553,7 +1553,7 @@ def user_forum_stats(user_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_analytics():
|
def admin_forum_analytics():
|
||||||
"""Forum analytics dashboard with stats, charts, and rankings"""
|
"""Forum analytics dashboard with stats, charts, and rankings"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
flash('Brak uprawnien do tej strony.', 'error')
|
flash('Brak uprawnien do tej strony.', 'error')
|
||||||
return redirect(url_for('.forum_index'))
|
return redirect(url_for('.forum_index'))
|
||||||
|
|
||||||
@ -1787,7 +1787,7 @@ def admin_forum_analytics():
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_forum_export_activity():
|
def admin_forum_export_activity():
|
||||||
"""Export forum activity to CSV"""
|
"""Export forum activity to CSV"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
from flask import Response
|
from flask import Response
|
||||||
@ -1880,7 +1880,7 @@ def admin_forum_export_activity():
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_move_topic(topic_id):
|
def admin_move_topic(topic_id):
|
||||||
"""Move topic to different category"""
|
"""Move topic to different category"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -1914,7 +1914,7 @@ def admin_move_topic(topic_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def admin_merge_topics():
|
def admin_merge_topics():
|
||||||
"""Merge multiple topics into one"""
|
"""Merge multiple topics into one"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
@ -1986,8 +1986,8 @@ def admin_merge_topics():
|
|||||||
@bp.route('/admin/forum/search')
|
@bp.route('/admin/forum/search')
|
||||||
@login_required
|
@login_required
|
||||||
def admin_forum_search():
|
def admin_forum_search():
|
||||||
"""Search all forum content (including deleted) - admin only"""
|
"""Search all forum content (including deleted) - moderators only"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
query = request.args.get('q', '').strip()
|
query = request.args.get('q', '').strip()
|
||||||
@ -2076,8 +2076,8 @@ def admin_forum_search():
|
|||||||
@bp.route('/admin/forum/user/<int:user_id>/activity')
|
@bp.route('/admin/forum/user/<int:user_id>/activity')
|
||||||
@login_required
|
@login_required
|
||||||
def admin_user_forum_activity(user_id):
|
def admin_user_forum_activity(user_id):
|
||||||
"""Get detailed forum activity for a specific user - admin only"""
|
"""Get detailed forum activity for a specific user - moderators only"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_moderate_forum():
|
||||||
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
return jsonify({'success': False, 'error': 'Brak uprawnien'}), 403
|
||||||
|
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|||||||
@ -71,7 +71,7 @@ def it_audit_form():
|
|||||||
# If no company_id provided, use current user's company
|
# If no company_id provided, use current user's company
|
||||||
if current_user.company_id:
|
if current_user.company_id:
|
||||||
company_id = current_user.company_id
|
company_id = current_user.company_id
|
||||||
elif current_user.is_admin:
|
elif current_user.can_access_admin_panel():
|
||||||
# Admin without specific company_id should redirect to admin dashboard
|
# Admin without specific company_id should redirect to admin dashboard
|
||||||
flash('Wybierz firmę do przeprowadzenia audytu IT.', 'info')
|
flash('Wybierz firmę do przeprowadzenia audytu IT.', 'info')
|
||||||
return redirect(url_for('admin_it_audit'))
|
return redirect(url_for('admin_it_audit'))
|
||||||
@ -89,8 +89,8 @@ def it_audit_form():
|
|||||||
flash('Firma nie została znaleziona.', 'error')
|
flash('Firma nie została znaleziona.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Access control: admin can access any company, users only their own
|
# Access control: users with company edit rights can access
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
flash('Nie masz uprawnień do edycji audytu IT tej firmy.', 'error')
|
flash('Nie masz uprawnień do edycji audytu IT tej firmy.', 'error')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
@ -193,8 +193,8 @@ def it_audit_save():
|
|||||||
'error': 'Firma nie znaleziona lub nieaktywna.'
|
'error': 'Firma nie znaleziona lub nieaktywna.'
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
# Access control: admin can save for any company, users only their own
|
# Access control: users with company edit rights can save
|
||||||
if not current_user.is_admin and current_user.company_id != company.id:
|
if not current_user.can_edit_company(company.id):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Nie masz uprawnień do edycji audytu IT tej firmy.'
|
'error': 'Nie masz uprawnień do edycji audytu IT tej firmy.'
|
||||||
|
|||||||
@ -43,8 +43,8 @@ def api_it_audit_matches(company_id):
|
|||||||
- partner company info (id, name, slug)
|
- partner company info (id, name, slug)
|
||||||
- match_reason and shared_attributes
|
- match_reason and shared_attributes
|
||||||
"""
|
"""
|
||||||
# Only admins can view collaboration matches
|
# Only users with admin panel access can view collaboration matches
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnień. Tylko administrator może przeglądać dopasowania.'
|
'error': 'Brak uprawnień. Tylko administrator może przeglądać dopasowania.'
|
||||||
@ -138,8 +138,8 @@ def api_it_audit_history(company_id):
|
|||||||
"""
|
"""
|
||||||
from it_audit_service import get_company_audit_history
|
from it_audit_service import get_company_audit_history
|
||||||
|
|
||||||
# Access control: users can only view their own company's history
|
# Access control: users with company edit rights can view history
|
||||||
if not current_user.is_admin and current_user.company_id != company_id:
|
if not current_user.can_edit_company(company_id):
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Brak uprawnień do przeglądania historii audytów tej firmy.'
|
'error': 'Brak uprawnień do przeglądania historii audytów tej firmy.'
|
||||||
@ -210,7 +210,7 @@ def api_it_audit_export():
|
|||||||
Returns:
|
Returns:
|
||||||
CSV file with IT audit data
|
CSV file with IT audit data
|
||||||
"""
|
"""
|
||||||
if not current_user.is_admin:
|
if not current_user.can_access_admin_panel():
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Tylko administrator może eksportować dane audytów.'
|
'error': 'Tylko administrator może eksportować dane audytów.'
|
||||||
|
|||||||
@ -186,10 +186,10 @@ def company_detail(company_id):
|
|||||||
company_id=company_id
|
company_id=company_id
|
||||||
).order_by(CompanyPKD.is_primary.desc(), CompanyPKD.pkd_code).all()
|
).order_by(CompanyPKD.is_primary.desc(), CompanyPKD.pkd_code).all()
|
||||||
|
|
||||||
# Check if current user can enrich company data (admin or company owner)
|
# Check if current user can enrich company data (user with company edit rights)
|
||||||
can_enrich = False
|
can_enrich = False
|
||||||
if current_user.is_authenticated:
|
if current_user.is_authenticated:
|
||||||
can_enrich = current_user.is_admin or (current_user.company_id == company.id)
|
can_enrich = current_user.can_edit_company(company.id)
|
||||||
|
|
||||||
return render_template('company_detail.html',
|
return render_template('company_detail.html',
|
||||||
company=company,
|
company=company,
|
||||||
|
|||||||
32
database/migrations/20260201_sync_user_roles.sql
Normal file
32
database/migrations/20260201_sync_user_roles.sql
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
-- Migration: Sync user roles with is_admin flag
|
||||||
|
-- Date: 2026-02-01
|
||||||
|
-- Description: Ensures all users have proper role field based on is_admin and company membership
|
||||||
|
-- Part of: Role-based access control migration from is_admin to SystemRole
|
||||||
|
|
||||||
|
-- 1. Set ADMIN role for users with is_admin=true
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'ADMIN'
|
||||||
|
WHERE is_admin = true AND (role IS NULL OR role != 'ADMIN');
|
||||||
|
|
||||||
|
-- 2. Set MEMBER role for non-admin users who have is_norda_member=true but no company
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'MEMBER'
|
||||||
|
WHERE is_admin = false
|
||||||
|
AND is_norda_member = true
|
||||||
|
AND company_id IS NULL
|
||||||
|
AND (role IS NULL OR role = 'UNAFFILIATED');
|
||||||
|
|
||||||
|
-- 3. Set EMPLOYEE role for non-admin users who have a company assigned
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'EMPLOYEE'
|
||||||
|
WHERE is_admin = false
|
||||||
|
AND company_id IS NOT NULL
|
||||||
|
AND (role IS NULL OR role = 'UNAFFILIATED');
|
||||||
|
|
||||||
|
-- 4. Set UNAFFILIATED for remaining users without role
|
||||||
|
UPDATE users
|
||||||
|
SET role = 'UNAFFILIATED'
|
||||||
|
WHERE role IS NULL;
|
||||||
|
|
||||||
|
-- 5. Verify: Show role distribution after migration
|
||||||
|
-- SELECT role, COUNT(*) as count FROM users GROUP BY role ORDER BY role;
|
||||||
@ -1339,7 +1339,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="btn-icon edit" title="{{ 'Edytuj audyt' if has_audit else 'Utwórz audyt' }}">
|
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="btn-icon edit" title="{{ 'Edytuj audyt' if has_audit else 'Utwórz audyt' }}">
|
||||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
{% if has_audit %}
|
{% if has_audit %}
|
||||||
|
|||||||
@ -558,7 +558,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
API
|
API
|
||||||
</a>
|
</a>
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<button class="btn btn-primary btn-sm" onclick="runBatchAudit()" id="batchAuditBtn">
|
<button class="btn btn-primary btn-sm" onclick="runBatchAudit()" id="batchAuditBtn">
|
||||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||||
@ -758,7 +758,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<button class="btn-icon audit" onclick="runSingleAudit('{{ company.slug }}')" title="Uruchom audyt SEO">
|
<button class="btn-icon audit" onclick="runSingleAudit('{{ company.slug }}')" title="Uruchom audyt SEO">
|
||||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
|
||||||
|
|||||||
@ -1215,7 +1215,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||||
<!-- Admin Bar -->
|
<!-- Admin Bar -->
|
||||||
<div class="admin-bar">
|
<div class="admin-bar">
|
||||||
<div class="admin-bar-inner">
|
<div class="admin-bar-inner">
|
||||||
|
|||||||
@ -375,7 +375,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary btn-sm">Zarządzaj</a>
|
<a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary btn-sm">Zarządzaj</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
/* Reset dla pełnoekranowego chatu jak ChatGPT/Claude */
|
/* Reset dla pełnoekranowego chatu jak ChatGPT/Claude */
|
||||||
:root {
|
:root {
|
||||||
/* Wysokość nagłówka: 73px navbar + 36px admin bar (jeśli admin) */
|
/* Wysokość nagłówka: 73px navbar + 36px admin bar (jeśli admin) */
|
||||||
--header-height: {% if current_user.is_authenticated and current_user.is_admin %}109px{% else %}73px{% endif %};
|
--header-height: {% if current_user.is_authenticated and current_user.can_access_admin_panel() %}109px{% else %}73px{% endif %};
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
|
|||||||
@ -628,7 +628,7 @@
|
|||||||
{% if classified.author_id == current_user.id %}
|
{% if classified.author_id == current_user.id %}
|
||||||
<button class="btn btn-secondary btn-sm close-btn" onclick="closeClassified()">Zamknij ogloszenie</button>
|
<button class="btn btn-secondary btn-sm close-btn" onclick="closeClassified()">Zamknij ogloszenie</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||||
<div class="admin-actions">
|
<div class="admin-actions">
|
||||||
<button type="button" class="admin-btn admin-btn-toggle {% if not classified.is_active %}inactive{% endif %}" onclick="toggleActive()" title="{% if classified.is_active %}Dezaktywuj{% else %}Aktywuj{% endif %}">
|
<button type="button" class="admin-btn admin-btn-toggle {% if not classified.is_active %}inactive{% endif %}" onclick="toggleActive()" title="{% if classified.is_active %}Dezaktywuj{% else %}Aktywuj{% endif %}">
|
||||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
|
|||||||
@ -740,7 +740,7 @@
|
|||||||
|
|
||||||
{# GBP Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
{# GBP Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% if current_user.is_admin or (current_user.company_id and current_user.company_id == company.id) %}
|
{% if current_user.can_edit_company(company.id) %}
|
||||||
<a href="{{ url_for('gbp_audit_dashboard', slug=company.slug) }}" class="contact-bar-item gbp-audit" title="Audyt Google Business Profile">
|
<a href="{{ url_for('gbp_audit_dashboard', slug=company.slug) }}" class="contact-bar-item gbp-audit" title="Audyt Google Business Profile">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
@ -752,7 +752,7 @@
|
|||||||
|
|
||||||
{# SEO Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
{# SEO Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% if current_user.is_admin or (current_user.company_id and current_user.company_id == company.id) %}
|
{% if current_user.can_edit_company(company.id) %}
|
||||||
<a href="{{ url_for('seo_audit_dashboard', slug=company.slug) }}" class="contact-bar-item seo-audit" title="Audyt SEO strony WWW">
|
<a href="{{ url_for('seo_audit_dashboard', slug=company.slug) }}" class="contact-bar-item seo-audit" title="Audyt SEO strony WWW">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||||
@ -764,7 +764,7 @@
|
|||||||
|
|
||||||
{# Social Media Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
{# Social Media Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% if current_user.is_admin or (current_user.company_id and current_user.company_id == company.id) %}
|
{% if current_user.can_edit_company(company.id) %}
|
||||||
<a href="{{ url_for('social_audit_dashboard', slug=company.slug) }}" class="contact-bar-item social-audit" title="Audyt Social Media">
|
<a href="{{ url_for('social_audit_dashboard', slug=company.slug) }}" class="contact-bar-item social-audit" title="Audyt Social Media">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
|
||||||
@ -776,7 +776,7 @@
|
|||||||
|
|
||||||
{# IT Infrastructure Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
{# IT Infrastructure Audit link - visible to admins (all profiles) or regular users (own company only) #}
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{% if current_user.is_admin or (current_user.company_id and current_user.company_id == company.id) %}
|
{% if current_user.can_edit_company(company.id) %}
|
||||||
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="contact-bar-item it-audit" title="Audyt Infrastruktury IT">
|
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="contact-bar-item it-audit" title="Audyt Infrastruktury IT">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
|
||||||
|
|||||||
@ -388,7 +388,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<p style="color: var(--text-secondary); margin: 8px 0 0 0;">
|
<p style="color: var(--text-secondary); margin: 8px 0 0 0;">
|
||||||
Witaj, <strong>{{ current_user.name }}</strong>!
|
Witaj, <strong>{{ current_user.name }}</strong>!
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<span class="badge-admin">Administrator</span>
|
<span class="badge-admin">Administrator</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
@ -508,7 +508,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<!-- Admin Section -->
|
<!-- Admin Section -->
|
||||||
<div class="admin-section-highlight">
|
<div class="admin-section-highlight">
|
||||||
<h4 style="margin: 0 0 var(--spacing-lg, 20px) 0; display: flex; align-items: center; gap: 8px;">
|
<h4 style="margin: 0 0 var(--spacing-lg, 20px) 0; display: flex; align-items: center; gap: 8px;">
|
||||||
@ -660,7 +660,7 @@
|
|||||||
<p style="margin: 0 0 4px 0;"><strong>Email:</strong> {{ current_user.email }}</p>
|
<p style="margin: 0 0 4px 0;"><strong>Email:</strong> {{ current_user.email }}</p>
|
||||||
<p style="margin: 0;">
|
<p style="margin: 0;">
|
||||||
<strong>Rola:</strong>
|
<strong>Rola:</strong>
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.can_access_admin_panel() %}
|
||||||
<span class="badge-admin">Administrator</span>
|
<span class="badge-admin">Administrator</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem;">Użytkownik</span>
|
<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 12px; font-size: 0.75rem;">Użytkownik</span>
|
||||||
|
|||||||
@ -986,7 +986,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{{ topic.title }}
|
{{ topic.title }}
|
||||||
</h1>
|
</h1>
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_moderate_forum() %}
|
||||||
<div class="admin-actions">
|
<div class="admin-actions">
|
||||||
<button type="button" class="admin-btn admin-btn-pin" onclick="togglePin({{ topic.id }})" title="{% if topic.is_pinned %}Odepnij{% else %}Przypnij{% endif %}">
|
<button type="button" class="admin-btn admin-btn-pin" onclick="togglePin({{ topic.id }})" title="{% if topic.is_pinned %}Odepnij{% else %}Przypnij{% endif %}">
|
||||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||||
@ -1090,7 +1090,7 @@
|
|||||||
<!-- User actions for topic -->
|
<!-- User actions for topic -->
|
||||||
{% if not topic.is_locked %}
|
{% if not topic.is_locked %}
|
||||||
<div class="user-actions">
|
<div class="user-actions">
|
||||||
{% if topic.author_id == current_user.id or current_user.is_admin %}
|
{% if topic.author_id == current_user.id or current_user.can_moderate_forum() %}
|
||||||
<button type="button" class="action-btn" onclick="openEditModal('topic', {{ topic.id }}, document.getElementById('topicContent').innerText)">
|
<button type="button" class="action-btn" onclick="openEditModal('topic', {{ topic.id }}, document.getElementById('topicContent').innerText)">
|
||||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||||
Edytuj
|
Edytuj
|
||||||
@ -1151,7 +1151,7 @@
|
|||||||
<span class="edited-badge">(edytowano)</span>
|
<span class="edited-badge">(edytowano)</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_moderate_forum() %}
|
||||||
<div class="reply-admin-actions">
|
<div class="reply-admin-actions">
|
||||||
<button type="button" class="admin-btn admin-btn-sm" onclick="toggleSolution({{ reply.id }})" title="{% if reply.is_solution %}Usuń oznaczenie{% else %}Oznacz jako rozwiązanie{% endif %}">
|
<button type="button" class="admin-btn admin-btn-sm" onclick="toggleSolution({{ reply.id }})" title="{% if reply.is_solution %}Usuń oznaczenie{% else %}Oznacz jako rozwiązanie{% endif %}">
|
||||||
✓
|
✓
|
||||||
|
|||||||
@ -263,7 +263,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; gap: 8px;">
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
<div class="release-date">{{ release.date }}</div>
|
<div class="release-date">{{ release.date }}</div>
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||||
<button class="notify-btn" onclick="notifyRelease('{{ release.version }}', this)" title="Wyślij powiadomienia o tej wersji">
|
<button class="notify-btn" onclick="notifyRelease('{{ release.version }}', this)" title="Wyślij powiadomienia o tej wersji">
|
||||||
🔔 Powiadom
|
🔔 Powiadom
|
||||||
</button>
|
</button>
|
||||||
@ -472,7 +472,7 @@ document.getElementById('confirmModal').addEventListener('click', function(e) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||||
function notifyRelease(version, btn) {
|
function notifyRelease(version, btn) {
|
||||||
showConfirmModal(
|
showConfirmModal(
|
||||||
'Wyślij powiadomienia',
|
'Wyślij powiadomienia',
|
||||||
|
|||||||
@ -160,6 +160,32 @@ def company_permission(permission_type='edit'):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def office_manager_required(f):
|
||||||
|
"""
|
||||||
|
Decorator that requires user to be at least OFFICE_MANAGER.
|
||||||
|
Shortcut for @role_required(SystemRole.OFFICE_MANAGER).
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
@bp.route('/admin/companies')
|
||||||
|
@login_required
|
||||||
|
@office_manager_required
|
||||||
|
def admin_companies():
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated_function(*args, **kwargs):
|
||||||
|
if not current_user.is_authenticated:
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
SystemRole = _get_system_role()
|
||||||
|
if not current_user.has_role(SystemRole.OFFICE_MANAGER):
|
||||||
|
flash('Ta strona wymaga uprawnień kierownika biura.', 'error')
|
||||||
|
return redirect(url_for('public.index'))
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
def forum_access_required(f):
|
def forum_access_required(f):
|
||||||
"""
|
"""
|
||||||
Decorator that requires user to have forum access.
|
Decorator that requires user to have forum access.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user