diff --git a/app.py b/app.py index 4ab9717..5957238 100644 --- a/app.py +++ b/app.py @@ -1248,350 +1248,10 @@ def api_connections(): # ============================================================ -# SEO AUDIT USER-FACING DASHBOARD +# AUDIT DASHBOARDS - MOVED TO: blueprints/audit/routes.py +# Routes: /audit/seo/, /audit/social/, /audit/gbp/, /audit/it/ # ============================================================ -@app.route('/audit/seo/') -@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: admin can view any company, member only their own - if not current_user.is_admin: - if current_user.company_id != 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 (admin or company owner) - can_audit = current_user.is_admin or current_user.company_id == 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 -# ============================================================ - -@app.route('/audit/social/') -@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 - admin can view all, users only their company - if not current_user.is_admin: - if current_user.company_id != 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 (admin or company owner) - can_audit = current_user.is_admin or current_user.company_id == 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() - - -# ============================================================ -# SOCIAL AUDIT API - MOVED TO: blueprints/api/routes_social_audit.py -# ============================================================ - -# ============================================================ -# GBP AUDIT USER-FACING DASHBOARD -# ============================================================ - -@app.route('/audit/gbp/') -@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: admin can view any company, member only their own - if not current_user.is_admin: - if current_user.company_id != 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 (admin or company owner) - can_audit = current_user.is_admin or current_user.company_id == 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 -# ============================================================ - -@app.route('/audit/it/') -@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: - # Import IT audit models - from database import ITAudit - - # 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: admin can view any company, member only their own - if not current_user.is_admin: - if current_user.company_id != 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 (admin or company owner) - can_edit = current_user.is_admin or current_user.company_id == 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() - @app.route('/api/check-email', methods=['POST']) def api_check_email(): diff --git a/blueprints/__init__.py b/blueprints/__init__.py index 0ae90ae..0523696 100644 --- a/blueprints/__init__.py +++ b/blueprints/__init__.py @@ -90,6 +90,25 @@ def register_blueprints(app): except Exception as e: logger.error(f"Error registering it_audit blueprint: {e}") + # Audit dashboards blueprint (user-facing) + try: + from blueprints.audit import bp as audit_bp + app.register_blueprint(audit_bp) + logger.info("Registered blueprint: audit") + + # Create aliases for backward compatibility + _create_endpoint_aliases(app, audit_bp, { + 'seo_audit_dashboard': 'audit.seo_audit_dashboard', + 'social_audit_dashboard': 'audit.social_audit_dashboard', + 'gbp_audit_dashboard': 'audit.gbp_audit_dashboard', + 'it_audit_dashboard': 'audit.it_audit_dashboard', + }) + logger.info("Created audit endpoint aliases") + except ImportError as e: + logger.debug(f"Blueprint audit not yet available: {e}") + except Exception as e: + logger.error(f"Error registering audit blueprint: {e}") + # Phase 2: Auth + Public blueprints (with backward-compatible aliases) try: from blueprints.auth import bp as auth_bp diff --git a/blueprints/audit/__init__.py b/blueprints/audit/__init__.py new file mode 100644 index 0000000..ab38d5e --- /dev/null +++ b/blueprints/audit/__init__.py @@ -0,0 +1,12 @@ +""" +Audit Blueprint +================ + +User-facing audit dashboard pages for SEO, GBP, Social Media, and IT audits. +""" + +from flask import Blueprint + +bp = Blueprint('audit', __name__, url_prefix='/audit') + +from . import routes # noqa: E402, F401 diff --git a/blueprints/audit/routes.py b/blueprints/audit/routes.py new file mode 100644 index 0000000..8d843e4 --- /dev/null +++ b/blueprints/audit/routes.py @@ -0,0 +1,371 @@ +""" +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/') +@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: admin can view any company, member only their own + if not current_user.is_admin: + if current_user.company_id != 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 (admin or company owner) + can_audit = current_user.is_admin or current_user.company_id == 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/') +@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 - admin can view all, users only their company + if not current_user.is_admin: + if current_user.company_id != 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 (admin or company owner) + can_audit = current_user.is_admin or current_user.company_id == 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/') +@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: admin can view any company, member only their own + if not current_user.is_admin: + if current_user.company_id != 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 (admin or company owner) + can_audit = current_user.is_admin or current_user.company_id == 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/') +@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: admin can view any company, member only their own + if not current_user.is_admin: + if current_user.company_id != 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 (admin or company owner) + can_edit = current_user.is_admin or current_user.company_id == 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()