from __future__ import annotations from fastapi import APIRouter, Form, Request from fastapi.responses import RedirectResponse from sqlalchemy import select from app.core.db.common import CommonSessionLocal from app.core.security.csrf import get_or_create_csrf_token, validate_csrf from app.core.security.session_auth import get_current_user from app.core.templating import templates from app.modules.core.rbac.deps import get_user_permissions, get_user_roles from app.modules.domain_management.models import DomainMapping from app.modules.domain_management.coolify_client import get_coolify_application_domains, sync_domain_to_coolify from app.modules.domain_management.services import ( DomainMappingPayload, create_domain_mapping, create_or_get_tenant_subdomain_mapping, create_or_get_firm_custom_domain_mapping, create_or_get_consultant_domain_mapping, list_consultant_domain_candidates, list_domain_mappings, list_domains_requiring_verification, verify_all_pending_domains, verify_domain_dns_txt, build_ssl_proxy_snippets, check_domain_ssl_certificate, list_ssl_domains, mark_ssl_managed, list_tenant_subdomain_candidates, mark_verified, reference_data, regenerate_verification_token, update_domain_mapping, validate_domain_payload, ) router = APIRouter(prefix="/domains", tags=["domain-management-ui"]) def _is_domain_admin(db, user) -> bool: roles = set(get_user_roles(db, user.id)) return "System Admin" in roles def _base_ctx(request: Request, db, user, **ctx): base = { "request": request, "current_user": user, "current_user_roles": get_user_roles(db, user.id), "current_user_permissions": get_user_permissions(db, user.id), "csrf_token": get_or_create_csrf_token(request), } base.update(ctx) return base def _require_user(request: Request, db): user = get_current_user(request, db=db) if not user: return None, RedirectResponse(url="/login", status_code=303) if not _is_domain_admin(db, user): return user, RedirectResponse(url="/system-settings", status_code=303) return user, None def _to_int(value: str | int | None) -> int | None: try: n = int(value or 0) return n if n > 0 else None except Exception: return None def _payload_from_form( *, domain_name: str, domain_type: str, tenant_id: str | int | None, branch_id: str | int | None, consultant_id: str | int | None, parent_tenant_id: str | int | None, is_primary: str | None, is_verified: str | None, status: str, ssl_mode: str, notes: str, ) -> DomainMappingPayload: return DomainMappingPayload( domain_name=domain_name, domain_type=domain_type, tenant_id=_to_int(tenant_id), branch_id=_to_int(branch_id), consultant_id=_to_int(consultant_id), parent_tenant_id=_to_int(parent_tenant_id), is_primary=bool(is_primary), is_verified=bool(is_verified), status=status or "draft", ssl_mode=ssl_mode or "manual", notes=notes, ) @router.get("") def domain_list(request: Request, q: str = "", status: str = "", domain_type: str = ""): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mappings = list_domain_mappings(db, q=q, status=status, domain_type=domain_type) refs = reference_data(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/list.html", _base_ctx( request, db, user, title="Domain Mapping", mappings=mappings, filters={"q": q, "status": status, "domain_type": domain_type}, **refs, ), ) finally: db.close() @router.get("/new") def domain_create_page(request: Request): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response return templates.TemplateResponse( "modules/domain_management/templates/domain_management/form.html", _base_ctx( request, db, user, title="Add Domain Mapping", mapping=None, errors=[], form={}, **reference_data(db), ), ) finally: db.close() @router.post("/new") def domain_create_submit( request: Request, csrf_token: str = Form(...), domain_name: str = Form(...), domain_type: str = Form(...), tenant_id: str = Form(""), branch_id: str = Form(""), consultant_id: str = Form(""), parent_tenant_id: str = Form(""), is_primary: str | None = Form(None), is_verified: str | None = Form(None), status: str = Form("draft"), ssl_mode: str = Form("manual"), notes: str = Form(""), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response payload = _payload_from_form( domain_name=domain_name, domain_type=domain_type, tenant_id=tenant_id, branch_id=branch_id, consultant_id=consultant_id, parent_tenant_id=parent_tenant_id, is_primary=is_primary, is_verified=is_verified, status=status, ssl_mode=ssl_mode, notes=notes, ) errors = validate_domain_payload(db, payload) if errors: return templates.TemplateResponse( "modules/domain_management/templates/domain_management/form.html", _base_ctx(request, db, user, title="Add Domain Mapping", mapping=None, errors=errors, form=payload.__dict__, **reference_data(db)), status_code=400, ) mapping = create_domain_mapping(db, payload, user_id=user.id) return RedirectResponse(url=f"/domains/{mapping.id}", status_code=303) finally: db.close() @router.get("/tenant-subdomains") def tenant_subdomain_page(request: Request, base_domain: str = "filingabc.com"): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response refs = reference_data(db) candidates = list_tenant_subdomain_candidates(db, base_domain=base_domain) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/tenant_subdomains.html", _base_ctx( request, db, user, title="Tenant Subdomains", base_domain=base_domain, candidates=candidates, message="", errors=[], **refs, ), ) finally: db.close() @router.post("/tenant-subdomains/create") def tenant_subdomain_create( request: Request, csrf_token: str = Form(...), tenant_id: str = Form(...), base_domain: str = Form("filingabc.com"), branch_id: str = Form(""), mark_verified_active: str | None = Form(None), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response errors: list[str] = [] message = "" try: mapping, created = create_or_get_tenant_subdomain_mapping( db, tenant_id=int(tenant_id), base_domain=base_domain, branch_id=_to_int(branch_id), mark_verified_active=bool(mark_verified_active), user_id=user.id, ) if created: return RedirectResponse(url=f"/domains/{mapping.id}", status_code=303) message = f"Domain mapping already exists: {mapping.domain_name}" except Exception as exc: errors.append(str(exc)) refs = reference_data(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/tenant_subdomains.html", _base_ctx( request, db, user, title="Tenant Subdomains", base_domain=base_domain, candidates=list_tenant_subdomain_candidates(db, base_domain=base_domain), message=message, errors=errors, **refs, ), status_code=400 if errors else 200, ) finally: db.close() @router.get("/firm-domain") def firm_custom_domain_page(request: Request, domain_name: str = "arrr.associates"): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response refs = reference_data(db) existing = None normalized = (domain_name or "arrr.associates").strip().lower().split("/", 1)[0].split(":", 1)[0].rstrip(".") if normalized: existing = db.execute(select(DomainMapping).where(DomainMapping.domain_name == normalized)).scalar_one_or_none() return templates.TemplateResponse( "modules/domain_management/templates/domain_management/firm_domain.html", _base_ctx( request, db, user, title="Audit Firm Custom Domain", domain_name=normalized or "arrr.associates", existing=existing, message="", errors=[], **refs, ), ) finally: db.close() @router.post("/firm-domain/create") def firm_custom_domain_create( request: Request, csrf_token: str = Form(...), tenant_id: str = Form(...), domain_name: str = Form("arrr.associates"), branch_id: str = Form(""), mark_verified_active: str | None = Form(None), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response errors: list[str] = [] message = "" try: mapping, created = create_or_get_firm_custom_domain_mapping( db, tenant_id=int(tenant_id), domain_name=domain_name, branch_id=_to_int(branch_id), mark_verified_active=bool(mark_verified_active), user_id=user.id, ) if created: return RedirectResponse(url=f"/domains/{mapping.id}", status_code=303) message = f"Domain mapping already exists: {mapping.domain_name}" except Exception as exc: errors.append(str(exc)) refs = reference_data(db) normalized = (domain_name or "arrr.associates").strip().lower().split("/", 1)[0].split(":", 1)[0].rstrip(".") existing = None if normalized: existing = db.execute(select(DomainMapping).where(DomainMapping.domain_name == normalized)).scalar_one_or_none() return templates.TemplateResponse( "modules/domain_management/templates/domain_management/firm_domain.html", _base_ctx( request, db, user, title="Audit Firm Custom Domain", domain_name=normalized or "arrr.associates", existing=existing, message=message, errors=errors, **refs, ), status_code=400 if errors else 200, ) finally: db.close() @router.get("/consultant-domains") def consultant_domains_page(request: Request, base_domain: str = "filingabc.com", firm_base_domain: str = "arrr.accountant"): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response refs = reference_data(db) candidates = list_consultant_domain_candidates(db, base_domain=base_domain, firm_base_domain=firm_base_domain) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/consultant_domains.html", _base_ctx( request, db, user, title="Consultant Profile Domains", base_domain=base_domain, firm_base_domain=firm_base_domain, candidates=candidates, message="", errors=[], **refs, ), ) finally: db.close() @router.post("/consultant-domains/create") def consultant_domain_create( request: Request, csrf_token: str = Form(...), consultant_id: str = Form(...), domain_type: str = Form("consultant_marketplace_subdomain"), domain_name: str = Form(""), base_domain: str = Form("filingabc.com"), firm_base_domain: str = Form("arrr.accountant"), parent_tenant_id: str = Form(""), mark_verified_active: str | None = Form(None), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response errors: list[str] = [] message = "" try: mapping, created = create_or_get_consultant_domain_mapping( db, consultant_id=int(consultant_id), domain_type=domain_type, domain_name=domain_name or None, base_domain=base_domain, firm_base_domain=firm_base_domain, parent_tenant_id=_to_int(parent_tenant_id), mark_verified_active=bool(mark_verified_active), user_id=user.id, ) if created: return RedirectResponse(url=f"/domains/{mapping.id}", status_code=303) message = f"Domain mapping already exists: {mapping.domain_name}" except Exception as exc: errors.append(str(exc)) refs = reference_data(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/consultant_domains.html", _base_ctx( request, db, user, title="Consultant Profile Domains", base_domain=base_domain, firm_base_domain=firm_base_domain, candidates=list_consultant_domain_candidates(db, base_domain=base_domain, firm_base_domain=firm_base_domain), message=message, errors=errors, **refs, ), status_code=400 if errors else 200, ) finally: db.close() @router.get("/verification") def domain_verification_page(request: Request): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response pending = list_domains_requiring_verification(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/verification.html", _base_ctx( request, db, user, title="Custom Domain Verification", pending=pending, results=[], ), ) finally: db.close() @router.post("/verification/run") def domain_verification_run(request: Request, csrf_token: str = Form(...), limit: str = Form("25")): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response try: n = int(limit or 25) except Exception: n = 25 results = verify_all_pending_domains(db, user_id=user.id, limit=n) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/verification.html", _base_ctx( request, db, user, title="Custom Domain Verification", pending=list_domains_requiring_verification(db), results=results, ), ) finally: db.close() @router.get("/ssl") def ssl_dashboard(request: Request): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response domains = list_ssl_domains(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/ssl.html", _base_ctx(request, db, user, title="SSL Automation", domains=domains), ) finally: db.close() @router.post("/ssl/check-all") def ssl_check_all(request: Request, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response results = [] for mapping in list_ssl_domains(db)[:25]: if mapping.status == "active" and mapping.is_verified: result = check_domain_ssl_certificate(db, mapping, user_id=user.id) results.append((mapping, result)) domains = list_ssl_domains(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/ssl.html", _base_ctx(request, db, user, title="SSL Automation", domains=domains, ssl_results=results), ) finally: db.close() @router.get("/coolify/status") def coolify_status_page(request: Request): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response result = get_coolify_application_domains() domains = list_ssl_domains(db) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/ssl.html", _base_ctx(request, db, user, title="Coolify Domain Sync", domains=domains, coolify_result=result), status_code=200 if result.ok else 400, ) finally: db.close() @router.get("/{mapping_id}/ssl") def ssl_detail(request: Request, mapping_id: int): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains/ssl", status_code=303) snippets = build_ssl_proxy_snippets(mapping) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/ssl_detail.html", _base_ctx(request, db, user, title=f"SSL: {mapping.domain_name}", mapping=mapping, snippets=snippets), ) finally: db.close() @router.post("/{mapping_id}/coolify/sync") def coolify_sync_one(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) result = sync_domain_to_coolify(db, mapping, user_id=user.id) db.refresh(mapping) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/detail.html", _base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping, coolify_result=result), status_code=200 if result.ok else 400, ) finally: db.close() @router.post("/{mapping_id}/coolify/check") def coolify_check_one(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) result = get_coolify_application_domains() return templates.TemplateResponse( "modules/domain_management/templates/domain_management/detail.html", _base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping, coolify_result=result), status_code=200 if result.ok else 400, ) finally: db.close() @router.post("/{mapping_id}/ssl/check") def ssl_check_one(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains/ssl", status_code=303) result = check_domain_ssl_certificate(db, mapping, user_id=user.id) snippets = build_ssl_proxy_snippets(mapping) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/ssl_detail.html", _base_ctx(request, db, user, title=f"SSL: {mapping.domain_name}", mapping=mapping, snippets=snippets, ssl_result=result), status_code=200 if result.ok else 400, ) finally: db.close() @router.post("/{mapping_id}/ssl/mark-managed") def ssl_mark_managed(request: Request, mapping_id: int, csrf_token: str = Form(...), provider: str = Form("manual")): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if mapping: mark_ssl_managed(db, mapping, provider=provider, user_id=user.id) return RedirectResponse(url=f"/domains/{mapping_id}/ssl", status_code=303) finally: db.close() @router.get("/{mapping_id}") def domain_detail(request: Request, mapping_id: int): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/detail.html", _base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping), ) finally: db.close() @router.get("/{mapping_id}/edit") def domain_edit_page(request: Request, mapping_id: int): db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/form.html", _base_ctx(request, db, user, title="Edit Domain Mapping", mapping=mapping, errors=[], form={}, **reference_data(db)), ) finally: db.close() @router.post("/{mapping_id}/edit") def domain_edit_submit( request: Request, mapping_id: int, csrf_token: str = Form(...), domain_name: str = Form(...), domain_type: str = Form(...), tenant_id: str = Form(""), branch_id: str = Form(""), consultant_id: str = Form(""), parent_tenant_id: str = Form(""), is_primary: str | None = Form(None), is_verified: str | None = Form(None), status: str = Form("draft"), ssl_mode: str = Form("manual"), notes: str = Form(""), ): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) payload = _payload_from_form( domain_name=domain_name, domain_type=domain_type, tenant_id=tenant_id, branch_id=branch_id, consultant_id=consultant_id, parent_tenant_id=parent_tenant_id, is_primary=is_primary, is_verified=is_verified, status=status, ssl_mode=ssl_mode, notes=notes, ) errors = validate_domain_payload(db, payload, mapping_id=mapping.id) if errors: return templates.TemplateResponse( "modules/domain_management/templates/domain_management/form.html", _base_ctx(request, db, user, title="Edit Domain Mapping", mapping=mapping, errors=errors, form=payload.__dict__, **reference_data(db)), status_code=400, ) update_domain_mapping(db, mapping, payload, user_id=user.id) return RedirectResponse(url=f"/domains/{mapping.id}", status_code=303) finally: db.close() @router.post("/{mapping_id}/verify-dns") def domain_verify_dns(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if not mapping: return RedirectResponse(url="/domains", status_code=303) result = verify_domain_dns_txt(db, mapping, user_id=user.id) return templates.TemplateResponse( "modules/domain_management/templates/domain_management/detail.html", _base_ctx(request, db, user, title=mapping.domain_name, mapping=mapping, dns_result=result), status_code=200 if result.ok else 400, ) finally: db.close() @router.post("/{mapping_id}/regenerate-token") def domain_regenerate_token(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if mapping: regenerate_verification_token(db, mapping, user_id=user.id) return RedirectResponse(url=f"/domains/{mapping_id}", status_code=303) finally: db.close() @router.post("/{mapping_id}/mark-verified") def domain_mark_verified(request: Request, mapping_id: int, csrf_token: str = Form(...)): validate_csrf(request, csrf_token) db = CommonSessionLocal() try: user, response = _require_user(request, db) if response: return response mapping = db.execute(select(DomainMapping).where(DomainMapping.id == mapping_id)).scalar_one_or_none() if mapping: mark_verified(db, mapping, user_id=user.id) return RedirectResponse(url=f"/domains/{mapping_id}", status_code=303) finally: db.close()