Initial MCM project: FastAPI + Textual TUI unified messenger

MultiCustomerMessenger supporting Telegram (python-telegram-bot),
WhatsApp (Green API) and SMS (python-gsmmodem-new). REST API with
Bearer-token auth, SQLAlchemy models for MariaDB, APScheduler for
background polling, and Textual TUI running in same asyncio event-loop.
This commit is contained in:
2026-03-03 14:43:19 +01:00
commit 7f3b4768c3
38 changed files with 2072 additions and 0 deletions

128
schemas.py Normal file
View File

@@ -0,0 +1,128 @@
from __future__ import annotations
from datetime import datetime
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
# ── Enums ──────────────────────────────────────────────────────────────────────
class ChannelType(str, Enum):
telegram = "telegram"
whatsapp = "whatsapp"
sms = "sms"
class MessageStatus(str, Enum):
pending = "pending"
sent = "sent"
delivered = "delivered"
read = "read"
failed = "failed"
class MessageDirection(str, Enum):
inbound = "inbound"
outbound = "outbound"
# ── Contact ────────────────────────────────────────────────────────────────────
class ContactCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
phone: Optional[str] = None
email: Optional[str] = None
telegram_id: Optional[str] = None
telegram_username: Optional[str] = None
whatsapp_phone: Optional[str] = None
notes: Optional[str] = None
class ContactUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=255)
phone: Optional[str] = None
email: Optional[str] = None
telegram_id: Optional[str] = None
telegram_username: Optional[str] = None
whatsapp_phone: Optional[str] = None
notes: Optional[str] = None
class ContactResponse(BaseModel):
model_config = {"from_attributes": True}
id: str
name: str
phone: Optional[str] = None
email: Optional[str] = None
telegram_id: Optional[str] = None
telegram_username: Optional[str] = None
whatsapp_phone: Optional[str] = None
notes: Optional[str] = None
created_at: datetime
updated_at: Optional[datetime] = None
# ── Message ────────────────────────────────────────────────────────────────────
class SendMessageRequest(BaseModel):
channel: ChannelType
# Empfänger je nach Kanal eines der Felder befüllen
recipient_phone: Optional[str] = Field(None, description="Telefonnummer für WhatsApp/SMS (E.164: +49…)")
recipient_telegram_id: Optional[str] = Field(None, description="Telegram Chat-ID")
# Optional: Kontakt-ID aus der DB
contact_id: Optional[str] = None
text: str = Field(..., min_length=1, max_length=4096)
reply_to_id: Optional[str] = None
class MessageResponse(BaseModel):
model_config = {"from_attributes": True}
id: str
conversation_id: str
sender_id: Optional[str] = None
channel: ChannelType
channel_message_id: Optional[str] = None
direction: MessageDirection
text: str
status: MessageStatus
error_message: Optional[str] = None
created_at: datetime
sent_at: Optional[datetime] = None
delivered_at: Optional[datetime] = None
read_at: Optional[datetime] = None
# ── Conversation ───────────────────────────────────────────────────────────────
class ConversationResponse(BaseModel):
model_config = {"from_attributes": True}
id: str
channel: ChannelType
channel_conversation_id: Optional[str] = None
title: Optional[str] = None
is_group: bool
is_archived: bool
last_message_at: Optional[datetime] = None
created_at: datetime
last_message: Optional[MessageResponse] = None
unread_count: int = 0
# ── Channel Status ─────────────────────────────────────────────────────────────
class ChannelStatusResponse(BaseModel):
channel: ChannelType
enabled: bool
connected: bool
detail: Optional[str] = None
class SystemStatusResponse(BaseModel):
channels: list[ChannelStatusResponse]
database: bool
timestamp: datetime