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

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

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

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

368 lines
13 KiB
Python

"""
Audit Dashboard Routes - Audit blueprint
Migrated from app.py as part of the blueprint refactoring.
Contains user-facing audit dashboard pages for SEO, GBP, Social Media, and IT.
"""
import logging
from flask import flash, redirect, render_template, url_for
from flask_login import current_user, login_required
from database import (
SessionLocal, Company, CompanyWebsiteAnalysis,
CompanySocialMedia, ITAudit
)
from . import bp
logger = logging.getLogger(__name__)
# Check if GBP audit service is available
try:
from gbp_audit_service import (
get_company_audit as gbp_get_company_audit
)
GBP_AUDIT_AVAILABLE = True
GBP_AUDIT_VERSION = '1.0'
except ImportError:
GBP_AUDIT_AVAILABLE = False
GBP_AUDIT_VERSION = None
gbp_get_company_audit = None
# ============================================================
# SEO AUDIT USER-FACING DASHBOARD
# ============================================================
@bp.route('/seo/<slug>')
@login_required
def seo_audit_dashboard(slug):
"""
User-facing SEO audit dashboard for a specific company.
Displays SEO audit results with:
- PageSpeed Insights scores (SEO, Performance, Accessibility, Best Practices)
- Website analysis data
- Improvement recommendations
Access control:
- Admin users can view audit for any company
- Regular users can only view audit for their own company
Args:
slug: Company slug identifier
Returns:
Rendered seo_audit.html template with company and audit data
"""
db = SessionLocal()
try:
# Find company by slug
company = db.query(Company).filter_by(slug=slug, status='active').first()
if not company:
flash('Firma nie została znaleziona.', 'error')
return redirect(url_for('dashboard'))
# Access control: users with company edit rights can view
if not current_user.can_edit_company(company.id):
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
return redirect(url_for('dashboard'))
# Get latest SEO analysis for this company
analysis = db.query(CompanyWebsiteAnalysis).filter(
CompanyWebsiteAnalysis.company_id == company.id
).order_by(CompanyWebsiteAnalysis.seo_audited_at.desc()).first()
# Build SEO data dict if analysis exists
seo_data = None
if analysis and analysis.seo_audited_at:
seo_data = {
'seo_score': analysis.pagespeed_seo_score,
'performance_score': analysis.pagespeed_performance_score,
'accessibility_score': analysis.pagespeed_accessibility_score,
'best_practices_score': analysis.pagespeed_best_practices_score,
'audited_at': analysis.seo_audited_at,
'audit_version': analysis.seo_audit_version,
'url': analysis.website_url
}
# Determine if user can run audit (user with company edit rights)
can_audit = current_user.can_edit_company(company.id)
logger.info(f"SEO audit dashboard viewed by {current_user.email} for company: {company.name}")
return render_template('seo_audit.html',
company=company,
seo_data=seo_data,
can_audit=can_audit
)
finally:
db.close()
# ============================================================
# SOCIAL MEDIA AUDIT USER-FACING DASHBOARD
# ============================================================
@bp.route('/social/<slug>')
@login_required
def social_audit_dashboard(slug):
"""
User-facing Social Media audit dashboard for a specific company.
Displays social media presence audit with:
- Overall presence score (platforms found / total platforms)
- Platform-by-platform status
- Profile validation status
- Recommendations for missing platforms
Access control:
- Admins: Can view all companies
- Regular users: Can only view their own company
Args:
slug: Company URL slug
Returns:
Rendered social_audit.html template with company and social data
"""
db = SessionLocal()
try:
# Find company by slug
company = db.query(Company).filter_by(slug=slug, status='active').first()
if not company:
flash('Firma nie została znaleziona.', 'error')
return redirect(url_for('dashboard'))
# Access control - users with company edit rights can view
if not current_user.can_edit_company(company.id):
flash('Brak uprawnień do wyświetlenia audytu social media tej firmy.', 'error')
return redirect(url_for('dashboard'))
# Get social media profiles for this company
social_profiles = db.query(CompanySocialMedia).filter(
CompanySocialMedia.company_id == company.id
).all()
# Define all platforms we track
all_platforms = ['facebook', 'instagram', 'linkedin', 'youtube', 'twitter', 'tiktok']
# Build social media data
profiles_dict = {}
for profile in social_profiles:
profiles_dict[profile.platform] = {
'url': profile.url,
'is_valid': profile.is_valid,
'check_status': profile.check_status,
'page_name': profile.page_name,
'followers_count': profile.followers_count,
'verified_at': profile.verified_at,
'last_checked_at': profile.last_checked_at
}
# Calculate score (platforms with profiles / total platforms)
platforms_with_profiles = len([p for p in all_platforms if p in profiles_dict])
total_platforms = len(all_platforms)
score = int((platforms_with_profiles / total_platforms) * 100) if total_platforms > 0 else 0
social_data = {
'profiles': profiles_dict,
'all_platforms': all_platforms,
'platforms_count': platforms_with_profiles,
'total_platforms': total_platforms,
'score': score
}
# Determine if user can run audit (user with company edit rights)
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}")
return render_template('social_audit.html',
company=company,
social_data=social_data,
can_audit=can_audit
)
finally:
db.close()
# ============================================================
# GBP AUDIT USER-FACING DASHBOARD
# ============================================================
@bp.route('/gbp/<slug>')
@login_required
def gbp_audit_dashboard(slug):
"""
User-facing GBP audit dashboard for a specific company.
Displays Google Business Profile completeness audit results with:
- Overall completeness score (0-100)
- Field-by-field status breakdown
- AI-generated improvement recommendations
- Historical audit data
Access control:
- Admin users can view audit for any company
- Regular users can only view audit for their own company
Args:
slug: Company slug identifier
Returns:
Rendered gbp_audit.html template with company and audit data
"""
if not GBP_AUDIT_AVAILABLE:
flash('Usługa audytu Google Business Profile jest tymczasowo niedostępna.', 'error')
return redirect(url_for('dashboard'))
db = SessionLocal()
try:
# Find company by slug
company = db.query(Company).filter_by(slug=slug, status='active').first()
if not company:
flash('Firma nie została znaleziona.', 'error')
return redirect(url_for('dashboard'))
# Access control: users with company edit rights can view
if not current_user.can_edit_company(company.id):
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
return redirect(url_for('dashboard'))
# Get latest audit for this company
audit = gbp_get_company_audit(db, company.id)
# If no audit exists, we still render the page (template handles this)
# The user can trigger an audit from the dashboard
# Determine if user can run audit (user with company edit rights)
can_audit = current_user.can_edit_company(company.id)
logger.info(f"GBP audit dashboard viewed by {current_user.email} for company: {company.name}")
return render_template('gbp_audit.html',
company=company,
audit=audit,
can_audit=can_audit,
gbp_audit_available=GBP_AUDIT_AVAILABLE,
gbp_audit_version=GBP_AUDIT_VERSION
)
finally:
db.close()
# ============================================================
# IT AUDIT USER-FACING DASHBOARD
# ============================================================
@bp.route('/it/<slug>')
@login_required
def it_audit_dashboard(slug):
"""
User-facing IT infrastructure audit dashboard for a specific company.
Displays IT audit results with:
- Overall score and maturity level
- Security, collaboration, and completeness sub-scores
- Technology stack summary (Azure AD, M365, backup, monitoring)
- AI-generated recommendations
Access control:
- Admin users can view audit for any company
- Regular users can only view audit for their own company
Args:
slug: Company slug identifier
Returns:
Rendered it_audit.html template with company and audit data
"""
db = SessionLocal()
try:
# Find company by slug
company = db.query(Company).filter_by(slug=slug, status='active').first()
if not company:
flash('Firma nie została znaleziona.', 'error')
return redirect(url_for('dashboard'))
# Access control: users with company edit rights can view
if not current_user.can_edit_company(company.id):
flash('Brak uprawnień. Możesz przeglądać audyt tylko własnej firmy.', 'error')
return redirect(url_for('dashboard'))
# Get latest IT audit for this company
audit = db.query(ITAudit).filter(
ITAudit.company_id == company.id
).order_by(ITAudit.audit_date.desc()).first()
# Build audit data dict if audit exists
audit_data = None
if audit:
# Get maturity label
maturity_labels = {
'basic': 'Podstawowy',
'developing': 'Rozwijający się',
'established': 'Ugruntowany',
'advanced': 'Zaawansowany'
}
audit_data = {
'id': audit.id,
'overall_score': audit.overall_score,
'security_score': audit.security_score,
'collaboration_score': audit.collaboration_score,
'completeness_score': audit.completeness_score,
'maturity_level': audit.maturity_level,
'maturity_label': maturity_labels.get(audit.maturity_level, 'Nieznany'),
'audit_date': audit.audit_date,
'audit_source': audit.audit_source,
# Technology flags
'has_azure_ad': audit.has_azure_ad,
'has_m365': audit.has_m365,
'has_google_workspace': audit.has_google_workspace,
'has_local_ad': audit.has_local_ad,
'has_edr': audit.has_edr,
'has_mfa': audit.has_mfa,
'has_vpn': audit.has_vpn,
'has_proxmox_pbs': audit.has_proxmox_pbs,
'has_dr_plan': audit.has_dr_plan,
'has_mdm': audit.has_mdm,
# Solutions
'antivirus_solution': audit.antivirus_solution,
'backup_solution': audit.backup_solution,
'monitoring_solution': audit.monitoring_solution,
'virtualization_platform': audit.virtualization_platform,
# Collaboration flags
'open_to_shared_licensing': audit.open_to_shared_licensing,
'open_to_backup_replication': audit.open_to_backup_replication,
'open_to_teams_federation': audit.open_to_teams_federation,
'open_to_shared_monitoring': audit.open_to_shared_monitoring,
'open_to_collective_purchasing': audit.open_to_collective_purchasing,
'open_to_knowledge_sharing': audit.open_to_knowledge_sharing,
# Recommendations
'recommendations': audit.recommendations
}
# Determine if user can edit audit (user with company edit rights)
can_edit = current_user.can_edit_company(company.id)
logger.info(f"IT audit dashboard viewed by {current_user.email} for company: {company.name}")
return render_template('it_audit.html',
company=company,
audit_data=audit_data,
can_edit=can_edit
)
finally:
db.close()