Another update to send.py to reflect recent changes.
This commit is contained in:
@@ -5,28 +5,36 @@ from typing import List
|
|||||||
from persistent.list import PersistentList
|
from persistent.list import PersistentList
|
||||||
from persistent.mapping import PersistentMapping
|
from persistent.mapping import PersistentMapping
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import transaction
|
||||||
|
|
||||||
from packetserver.http.dependencies import get_current_http_user
|
from packetserver.http.dependencies import get_current_http_user
|
||||||
from packetserver.http.auth import HttpUser
|
from packetserver.http.auth import HttpUser
|
||||||
from packetserver.server.messages import Message # core Message class
|
from packetserver.server.messages import Message
|
||||||
|
from packetserver.common.util import is_valid_ax25_callsign
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1", tags=["messages"])
|
router = APIRouter(prefix="/api/v1", tags=["messages"])
|
||||||
|
|
||||||
|
|
||||||
class SendMessageRequest(BaseModel):
|
class SendMessageRequest(BaseModel):
|
||||||
to: List[str] = Field(..., description="List of recipient callsigns (uppercase) or ['ALL'] for bulletin")
|
to: List[str] = Field(..., description="List of recipient callsigns or ['ALL'] for bulletin")
|
||||||
subject: str = Field("", description="Optional subject line")
|
|
||||||
text: str = Field(..., min_length=1, description="Message body text")
|
text: str = Field(..., min_length=1, description="Message body text")
|
||||||
|
|
||||||
@validator("to")
|
@validator("to")
|
||||||
def validate_to(cls, v):
|
def validate_to(cls, v):
|
||||||
if not v:
|
if not v:
|
||||||
raise ValueError("At least one recipient required")
|
raise ValueError("At least one recipient required")
|
||||||
# Allow 'ALL' only as single bulletin
|
|
||||||
if len(v) > 1 and "ALL" in [x.upper() for x in v]:
|
validated = []
|
||||||
|
for call in v:
|
||||||
|
call_upper = call.upper().strip()
|
||||||
|
if not is_valid_ax25_callsign(call_upper):
|
||||||
|
raise ValueError(f"Invalid AX.25 callsign: {call}")
|
||||||
|
validated.append(call_upper)
|
||||||
|
|
||||||
|
if len(validated) > 1 and "ALL" in validated:
|
||||||
raise ValueError("'ALL' can only be used alone for bulletins")
|
raise ValueError("'ALL' can only be used alone for bulletins")
|
||||||
return [x.upper() for x in v]
|
|
||||||
|
return validated
|
||||||
|
|
||||||
|
|
||||||
@router.post("/messages")
|
@router.post("/messages")
|
||||||
@@ -36,6 +44,8 @@ async def send_message(
|
|||||||
):
|
):
|
||||||
from packetserver.runners.http_server import get_db_connection
|
from packetserver.runners.http_server import get_db_connection
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
|
root = conn.root()
|
||||||
|
|
||||||
if not current_user.is_rf_enabled(conn):
|
if not current_user.is_rf_enabled(conn):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
@@ -44,35 +54,35 @@ async def send_message(
|
|||||||
|
|
||||||
username = current_user.username
|
username = current_user.username
|
||||||
|
|
||||||
from packetserver.runners.http_server import get_db_connection
|
# Prepare recipients
|
||||||
conn = get_db_connection()
|
to_list = payload.to
|
||||||
root = conn.root()
|
to_tuple = tuple(to_list)
|
||||||
|
if "ALL" in to_list:
|
||||||
# Prepare recipients tuple
|
|
||||||
to_tuple = tuple(payload.to)
|
|
||||||
if "ALL" in payload.to:
|
|
||||||
to_tuple = ("ALL",)
|
to_tuple = ("ALL",)
|
||||||
|
|
||||||
# Create new Message
|
is_bulletin = "ALL" in to_list
|
||||||
|
recipients = to_list if not is_bulletin else list(root.get('users', {}).keys())
|
||||||
|
|
||||||
|
# Create message using only supported core params
|
||||||
new_msg = Message(
|
new_msg = Message(
|
||||||
|
text=payload.text,
|
||||||
msg_from=username,
|
msg_from=username,
|
||||||
msg_to=to_tuple,
|
msg_to=to_tuple,
|
||||||
text=payload.text,
|
attachments=()
|
||||||
attachments=() # empty for now
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Deliver to all recipients (including sender for sent folder)
|
# Deliver to recipients + always sender (sent folder)
|
||||||
recipients = payload.to if "ALL" not in payload.to else list(root.get('users', {}).keys())
|
messages_root = root.setdefault('messages', PersistentMapping())
|
||||||
|
delivered_to = set()
|
||||||
|
|
||||||
for recip in set(recipients + [username]): # always copy to sender
|
for recip in set(recipients) | {username}:
|
||||||
if 'messages' not in root:
|
mailbox = messages_root.setdefault(recip, PersistentList())
|
||||||
root['messages'] = PersistentMapping()
|
|
||||||
mailbox = root['messages'].setdefault(recip, persistent.list.PersistentList())
|
|
||||||
mailbox.append(new_msg)
|
mailbox.append(new_msg)
|
||||||
|
mailbox._p_changed = True
|
||||||
|
delivered_to.add(recip)
|
||||||
|
|
||||||
# Persist
|
messages_root._p_changed = True
|
||||||
conn.root()["messages"]._p_changed = True
|
transaction.commit()
|
||||||
# Note: transaction.commit() not needed here—FastAPI/ZODB handles on response
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "sent",
|
"status": "sent",
|
||||||
@@ -80,5 +90,5 @@ async def send_message(
|
|||||||
"from": username,
|
"from": username,
|
||||||
"to": list(to_tuple),
|
"to": list(to_tuple),
|
||||||
"sent_at": new_msg.sent_at.isoformat() + "Z",
|
"sent_at": new_msg.sent_at.isoformat() + "Z",
|
||||||
"subject": payload.subject
|
"recipients_delivered": len(delivered_to)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user