first commit
This commit is contained in:
commit
cce9a8c9ba
73 changed files with 8665 additions and 0 deletions
185
backend/app/main.py
Normal file
185
backend/app/main.py
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Response, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.config import get_settings
|
||||
from app.database import SessionFactory, engine, mongo_client, mongo_db
|
||||
from app.models import Base
|
||||
from app.routers import auth, dns_setup, domains, emails, sticky_notes
|
||||
from app.services.domain_provisioning import ensure_platform_domain
|
||||
from app.services.inbound_smtp import InboundSMTPServer
|
||||
|
||||
settings = get_settings()
|
||||
inbound_server = InboundSMTPServer()
|
||||
|
||||
|
||||
async def migrate_legacy_user_tables(connection) -> None:
|
||||
"""Promote the former auth table to users without discarding old profiles."""
|
||||
tables = (
|
||||
await connection.execute(
|
||||
text(
|
||||
"SELECT to_regclass('public.accounts'), "
|
||||
"to_regclass('public.users'), "
|
||||
"to_regclass('public.legacy_users')"
|
||||
)
|
||||
)
|
||||
).one()
|
||||
accounts_table, users_table, legacy_users_table = tables
|
||||
|
||||
if accounts_table is None:
|
||||
return
|
||||
if users_table is not None:
|
||||
if legacy_users_table is not None:
|
||||
raise RuntimeError(
|
||||
"Cannot migrate accounts: both users and legacy_users already exist."
|
||||
)
|
||||
await connection.execute(text("ALTER TABLE users RENAME TO legacy_users"))
|
||||
await connection.execute(text("ALTER TABLE accounts RENAME TO users"))
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
async with engine.begin() as connection:
|
||||
await migrate_legacy_user_tables(connection)
|
||||
await connection.run_sync(Base.metadata.create_all)
|
||||
async with SessionFactory() as session:
|
||||
await ensure_platform_domain(session)
|
||||
await session.commit()
|
||||
await mongo_db.emails.create_index([("recipients", 1), ("created_at", -1)])
|
||||
await mongo_db.emails.create_index([("cc", 1), ("created_at", -1)])
|
||||
await mongo_db.emails.create_index([("bcc", 1), ("created_at", -1)])
|
||||
await mongo_db.emails.create_index([("sender", 1), ("created_at", -1)])
|
||||
await mongo_db.emails.create_index([("account_id", 1), ("created_at", -1)])
|
||||
await mongo_db.messages.create_index(
|
||||
[("delivery_key", 1)],
|
||||
unique=True,
|
||||
sparse=True,
|
||||
)
|
||||
await mongo_db.message_tombstones.create_index(
|
||||
[("delivery_key", 1)],
|
||||
unique=True,
|
||||
)
|
||||
await mongo_db.messages.create_index(
|
||||
[("subject", "text"), ("snippet", "text"), ("sender.name", "text")]
|
||||
)
|
||||
await mongo_db.mailbox_entries.create_index(
|
||||
[
|
||||
("account_id", 1),
|
||||
("mailbox", 1),
|
||||
("trashed_at", 1),
|
||||
("deletion_state", 1),
|
||||
("created_at", -1),
|
||||
("_id", -1),
|
||||
]
|
||||
)
|
||||
await mongo_db.mailbox_entries.create_index(
|
||||
[("account_id", 1), ("is_read", 1), ("created_at", -1), ("_id", -1)]
|
||||
)
|
||||
await mongo_db.mailbox_entries.create_index(
|
||||
[("account_id", 1), ("message_id", 1), ("mailbox", 1)],
|
||||
unique=True,
|
||||
)
|
||||
await mongo_db.mailbox_entries.create_index(
|
||||
[("account_id", 1), ("is_starred", 1), ("created_at", -1), ("_id", -1)]
|
||||
)
|
||||
await mongo_db.sticky_notes.create_index(
|
||||
[("account_id", 1), ("updated_at", -1)]
|
||||
)
|
||||
await mongo_db.mailbox_entries.create_index(
|
||||
[
|
||||
("account_id", 1),
|
||||
("sender.name", "text"),
|
||||
("sender.address", "text"),
|
||||
("subject", "text"),
|
||||
("snippet", "text"),
|
||||
],
|
||||
name="mailbox_search",
|
||||
)
|
||||
if settings.inbound_smtp_enabled:
|
||||
await inbound_server.start()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await inbound_server.stop()
|
||||
await engine.dispose()
|
||||
await mongo_client.close()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version="0.1.0",
|
||||
description="The API for Agmail — email for people and their AI agents.",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
app.include_router(emails.router, prefix=settings.api_prefix)
|
||||
app.include_router(sticky_notes.router, prefix=settings.api_prefix)
|
||||
app.include_router(auth.router, prefix=settings.api_prefix)
|
||||
app.include_router(domains.router, prefix=settings.api_prefix)
|
||||
app.include_router(dns_setup.router, prefix=settings.api_prefix)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {
|
||||
"name": settings.app_name,
|
||||
"docs": "/docs",
|
||||
"version": "0.1.0",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health(response: Response):
|
||||
checks = {
|
||||
"postgres": False,
|
||||
"mongodb": False,
|
||||
"smtp": False,
|
||||
"inbound_smtp": (
|
||||
not settings.inbound_smtp_enabled
|
||||
or (
|
||||
inbound_server.server is not None
|
||||
and inbound_server.server.is_serving()
|
||||
)
|
||||
),
|
||||
}
|
||||
|
||||
try:
|
||||
async with SessionFactory() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
checks["postgres"] = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
await mongo_db.command("ping")
|
||||
checks["mongodb"] = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
import socket
|
||||
|
||||
with socket.create_connection(
|
||||
(settings.smtp_host, settings.smtp_port),
|
||||
timeout=3,
|
||||
):
|
||||
checks["smtp"] = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
healthy = checks["postgres"] and checks["mongodb"]
|
||||
if settings.inbound_smtp_enabled:
|
||||
healthy = healthy and checks["inbound_smtp"]
|
||||
if settings.smtp_health_required:
|
||||
healthy = healthy and checks["smtp"]
|
||||
if not healthy:
|
||||
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
return {"status": "healthy" if healthy else "degraded", "checks": checks}
|
||||
Loading…
Add table
Add a link
Reference in a new issue