first commit

This commit is contained in:
debu-2 2026-10-03 21:14:44 +05:30
commit cce9a8c9ba
73 changed files with 8665 additions and 0 deletions

View file

@ -0,0 +1,249 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.config import get_settings
from app.database import get_postgres_session
from app.models import Account, MailDomain
from app.routers.auth import get_current_account
from app.schemas import (
DnsVerificationResponse,
DomainCreate,
MailDomainRead,
SmtpCredentialCreate,
SmtpCredentialCreated,
SmtpCredentialRead,
)
from app.services.dns_verification import verify_company_dns
from app.services.domain_provisioning import (
activate_company_domain,
create_smtp_credential,
ensure_platform_domain,
get_account_company_domain,
provision_mail_domain,
validate_domain,
)
router = APIRouter(prefix="/domains", tags=["domains"])
async def get_domain_or_404(
domain_id: UUID,
session: AsyncSession,
) -> MailDomain:
domain = await session.scalar(
select(MailDomain)
.options(
selectinload(MailDomain.dns_records),
selectinload(MailDomain.smtp_credentials),
)
.where(MailDomain.id == domain_id)
)
if domain is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Domain not found.",
)
return domain
@router.get("/me", response_model=MailDomainRead)
async def get_my_company_domain(
account: Account = Depends(get_current_account),
session: AsyncSession = Depends(get_postgres_session),
) -> MailDomain:
if account.account_type != "company" or not account.company_domain:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No company domain is linked to this account.",
)
domain = await get_account_company_domain(
session,
account.id,
account.company_domain,
)
if domain is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Company domain has not been provisioned yet.",
)
return domain
@router.post("/me/verify-dns", response_model=DnsVerificationResponse)
async def verify_my_company_dns(
account: Account = Depends(get_current_account),
session: AsyncSession = Depends(get_postgres_session),
) -> DnsVerificationResponse:
settings = get_settings()
if account.account_type != "company" or not account.company_domain:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="No company domain is linked to this account.",
)
domain = await get_account_company_domain(
session,
account.id,
account.company_domain,
)
if domain is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Company domain has not been provisioned yet.",
)
if domain.status == "active":
return DnsVerificationResponse(
domain=domain.domain_name,
status=domain.status,
verified=True,
checks=[],
)
checks = verify_company_dns(
domain_name=domain.domain_name,
shared_mail_host=settings.smtp_public_host,
dkim_selector=domain.dkim_selector,
dkim_public_key=domain.dkim_public_key,
platform_spf_include=settings.platform_spf_include,
)
verified = all(check["verified"] for check in checks)
if verified:
await activate_company_domain(session, domain)
await session.commit()
return DnsVerificationResponse(
domain=domain.domain_name,
status=domain.status if not verified else "active",
verified=verified,
checks=checks,
)
@router.get("/platform", response_model=MailDomainRead)
async def get_platform_domain(
session: AsyncSession = Depends(get_postgres_session),
) -> MailDomain:
domain = await ensure_platform_domain(session)
await session.commit()
return await get_domain_or_404(domain.id, session)
@router.get("", response_model=list[MailDomainRead])
async def list_domains(
session: AsyncSession = Depends(get_postgres_session),
) -> list[MailDomain]:
await ensure_platform_domain(session)
await session.commit()
result = await session.scalars(
select(MailDomain)
.options(selectinload(MailDomain.dns_records))
.order_by(MailDomain.created_at.desc())
)
return list(result.all())
@router.post("", response_model=MailDomainRead, status_code=status.HTTP_201_CREATED)
async def register_company_domain(
payload: DomainCreate,
session: AsyncSession = Depends(get_postgres_session),
) -> MailDomain:
settings = get_settings()
try:
normalized = validate_domain(payload.domain_name)
except ValueError as error:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=str(error),
) from error
if normalized == settings.platform_domain:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Use the platform domain endpoint for getall.help.",
)
existing = await session.scalar(
select(MailDomain).where(MailDomain.domain_name == normalized)
)
if existing:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="That domain is already registered.",
)
domain = await provision_mail_domain(
session,
domain_name=normalized,
domain_type="company",
account_id=payload.account_id,
)
await session.commit()
return await get_domain_or_404(domain.id, session)
@router.get("/{domain_id}", response_model=MailDomainRead)
async def get_domain(
domain_id: UUID,
session: AsyncSession = Depends(get_postgres_session),
) -> MailDomain:
return await get_domain_or_404(domain_id, session)
@router.get("/{domain_id}/dns-records", response_model=list)
async def list_dns_records(
domain_id: UUID,
session: AsyncSession = Depends(get_postgres_session),
):
domain = await get_domain_or_404(domain_id, session)
return [
{
"type": record.record_type,
"host": record.host,
"value": record.value,
"purpose": record.purpose,
"ttl": record.ttl,
}
for record in domain.dns_records
]
@router.get("/{domain_id}/smtp-credentials", response_model=list[SmtpCredentialRead])
async def list_smtp_credentials(
domain_id: UUID,
session: AsyncSession = Depends(get_postgres_session),
):
domain = await get_domain_or_404(domain_id, session)
return domain.smtp_credentials
@router.post(
"/{domain_id}/smtp-credentials",
response_model=SmtpCredentialCreated,
status_code=status.HTTP_201_CREATED,
)
async def create_domain_smtp_credential(
domain_id: UUID,
payload: SmtpCredentialCreate,
session: AsyncSession = Depends(get_postgres_session),
):
settings = get_settings()
domain = await get_domain_or_404(domain_id, session)
credential, plain_password = await create_smtp_credential(
session,
domain,
label=payload.label,
)
await session.commit()
return SmtpCredentialCreated(
id=credential.id,
username=credential.username,
label=credential.label,
is_active=credential.is_active,
created_at=credential.created_at,
password=plain_password,
smtp_host=settings.smtp_public_host,
smtp_port=settings.smtp_port,
use_tls=settings.smtp_use_tls,
)