feat: migrate email send to V3 action; deprecate api.py legacy endpoints
- Add /v3/action/email/send router (api_v3_actions_email.py) replacing /util/email/send - Disable util_email router in registry; register new email action router - Mark /api/request_jwt and /api/temp_token as deprecated (TODO: remove) - Guide: add §8 Email Send Action, mark Axonius section EXPIRED, renumber §9-§11 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,8 @@ async def authenticate_passcode(
|
||||
|
||||
# --- JWT Request ---
|
||||
|
||||
# DEPRECATED — no V3 replacement needed; passcode→JWT is the V3 auth pattern (/api/authenticate_passcode).
|
||||
# No frontend references found. Safe to remove after confirming no live traffic. TODO: remove.
|
||||
@router.get('/request_jwt', response_model=Resp_Body_Base, dependencies=[Depends(DeprecationParams)])
|
||||
async def request_jwt(
|
||||
x_aether_signing_key: Optional[str] = Header(None, min_length=22, max_length=22),
|
||||
@@ -167,6 +169,7 @@ async def request_jwt(
|
||||
token = sign_jwt(secret_key=signing_key, public_key=x_aether_api_key, ttl=max_ttl, max_renew=max_renew, **payload)
|
||||
return mk_resp(data={ 'jwt': token })
|
||||
|
||||
# DEPRECATED — no active use identified. TODO: remove after confirming no live traffic.
|
||||
@router.get('/temp_token', response_model=Resp_Body_Base, dependencies=[Depends(DeprecationParams)])
|
||||
async def get_api_temp_token(
|
||||
x_aether_api_key: Optional[str] = Header(None),
|
||||
|
||||
78
app/routers/api_v3_actions_email.py
Normal file
78
app/routers/api_v3_actions_email.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Aether API V3 - Email Action Router
|
||||
-------------------------------------
|
||||
Handles transactional email sending.
|
||||
|
||||
Routes:
|
||||
POST /send — send a transactional email
|
||||
|
||||
Replaces: POST /util/email/send (legacy — see util_email.py)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Response
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.lib_email import send_email
|
||||
from app.lib_general_v3 import AccountContext, get_account_context
|
||||
from app.models.response_models import Resp_Body_Base, mk_resp
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class EmailSendRequest(BaseModel):
|
||||
from_email: str = Field(..., description="Sender email address")
|
||||
from_name: Optional[str] = None
|
||||
to_email: str = Field(..., description="Recipient email address")
|
||||
to_name: Optional[str] = None
|
||||
cc_email: Optional[str] = None
|
||||
cc_name: Optional[str] = None
|
||||
bcc_email: Optional[str] = None
|
||||
bcc_name: Optional[str] = None
|
||||
subject: str = Field(..., description="Email subject line")
|
||||
body_html: str = Field(..., description="HTML email body")
|
||||
body_text: Optional[str] = None
|
||||
|
||||
|
||||
@router.post('/send', response_model=Resp_Body_Base)
|
||||
async def action_email_send(
|
||||
req: EmailSendRequest,
|
||||
test: bool = Query(False, description="Simulate send without delivering"),
|
||||
account_ctx: AccountContext = Depends(get_account_context),
|
||||
response: Response = Response,
|
||||
):
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
success = send_email(
|
||||
from_email=req.from_email,
|
||||
from_name=req.from_name,
|
||||
to_email=req.to_email,
|
||||
to_name=req.to_name,
|
||||
cc_email=req.cc_email or '',
|
||||
cc_name=req.cc_name or '',
|
||||
bcc_email=req.bcc_email or '',
|
||||
bcc_name=req.bcc_name or '',
|
||||
subject=req.subject,
|
||||
body_text=req.body_text,
|
||||
body_html=req.body_html,
|
||||
test=test,
|
||||
)
|
||||
|
||||
if success:
|
||||
status_code = 200
|
||||
status_message = f'Email sent to <{req.to_email}>.'
|
||||
else:
|
||||
status_code = 400
|
||||
status_message = f'Email failed to send to <{req.to_email}>.'
|
||||
|
||||
log.info(status_message)
|
||||
resp_data = {
|
||||
'from_email': req.from_email,
|
||||
'to_email': req.to_email,
|
||||
'subject': req.subject[:40],
|
||||
}
|
||||
return mk_resp(data=resp_data, status_code=status_code, response=response, status_message=status_message)
|
||||
@@ -5,6 +5,7 @@ from app.routers import (
|
||||
data_store,
|
||||
event_badge_importing,
|
||||
event_importing,
|
||||
api_v3_actions_email,
|
||||
api_v3_actions_hosted_file, api_v3_actions_event_file, api_v3_actions_event_exhibit, api_v3_actions_e_zoom, api_v3_actions_e_novi_mailman, api_v3_actions_user, lookup_v3,
|
||||
user,
|
||||
util_email, websockets_v3, e_confex, e_cvent, e_impexium, e_stripe
|
||||
@@ -51,6 +52,7 @@ def setup_routers(app: FastAPI):
|
||||
app.include_router(api_v3_actions_e_zoom.router, prefix='/v3/action/e_zoom', tags=['Zoom Events (V3 Actions)'])
|
||||
app.include_router(api_v3_actions_e_novi_mailman.router, prefix='/v3/action/e_novi_mailman', tags=['Novi-Mailman Bridge (V3 Actions)'])
|
||||
app.include_router(api_v3_actions_user.router, prefix='/v3/action/user', tags=['User (V3 Actions)'])
|
||||
app.include_router(api_v3_actions_email.router, prefix='/v3/action/email', tags=['Email (V3 Actions)'])
|
||||
# app.include_router(lookup.router, prefix='/lu', tags=['Lookup']) # LEGACY (disabled) - superseded by /v3/lookup
|
||||
app.include_router(lookup_v3.router, prefix='/v3/lookup', tags=['Lookup V3'])
|
||||
|
||||
@@ -63,7 +65,7 @@ def setup_routers(app: FastAPI):
|
||||
# app.include_router(site.router, tags=['Site'], dependencies=[Depends(DeprecationParams)])
|
||||
# app.include_router(site_domain.router, tags=['Site Domain'], dependencies=[Depends(DeprecationParams)])
|
||||
# app.include_router(user.router, tags=['User'], dependencies=[Depends(DeprecationParams)])
|
||||
app.include_router(util_email.router, tags=['Utility: Email'])
|
||||
# app.include_router(util_email.router, tags=['Utility: Email']) # LEGACY (disabled) - superseded by /v3/action/email/send
|
||||
# app.include_router(websockets.router, tags=['Websockets']) # LEGACY (disabled) - superseded by Websockets V3
|
||||
# app.include_router(websockets_redis.router, tags=['Websockets (Redis)']) # LEGACY (disabled) - superseded by Websockets V3
|
||||
app.include_router(websockets_v3.router, prefix='/v3', tags=['Websockets V3'])
|
||||
|
||||
Reference in New Issue
Block a user