Massive update, schemas and models

This commit is contained in:
Carlos Rivas 2024-08-22 00:49:09 -07:00
parent 877a70d6e1
commit df203943ac
8 changed files with 160 additions and 7 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
WHITELISTED_IPS = 52.88.246.140,52.10.220.50,54.190.46.191,52.43.82.110,127.0.0.1

57
app.py
View file

@ -0,0 +1,57 @@
import logging
import uvicorn
from database import engine, Base
from dotenv import load_dotenv
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from loggerino import timestamp_log_config
from os import getenv
from schemas import SMSMessage
from service import inbound_sms_handler, retrieve_sms_messages_by_phone_number
from sqlalchemy.orm import Session, joinedload
from uvicorn.config import LOGGING_CONFIG
# Create instance of FastAPI
app = FastAPI()
# Load dotenv file (.env)
load_dotenv()
# Load custom logger format
logger = logging.getLogger("uvicorn.error")
# Create tables (if they don't exist)
Base.metadata.create_all(bind=engine)
# Load whitelisted IPs
WHITELISTED_IPS = getenv("WHITELISTED_IPS").split(',')
@app.middleware("http")
async def validate_ip(request: Request, call_next):
ip = str(request.headers.get("x-forwarded-for", str(request.client.host)))
if ip not in WHITELISTED_IPS:
data = {"message": f"IP {ip} is not allowed to access this resource."}
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=data)
return await call_next(request)
@app.post("/sms-message", status_code=status.HTTP_200_OK)
async def receive_sms_message(message: SMSMessage):
inbound_sms_handler(message)
@app.get('/sms-message/{number}')
def get_sms_messages_by_phone_number(number: str, limit: int = 10, page: int = 1):
contact = retrieve_sms_messages_by_phone_number(number, limit, page)
return {'status': 'success', 'results': len(contact.messages), 'response': contact}
if __name__ == "__main__":
uvicorn.run(
app, host="0.0.0.0", port=8000, log_config=timestamp_log_config(LOGGING_CONFIG)
)

55
dtos.py Normal file
View file

@ -0,0 +1,55 @@
from models import SMSMessage as SMSDBMessage, SMSContact, SMSCost, SMSMetadata
from schemas import SMSMessage
def to_db(message: SMSMessage) -> SMSContact:
payload = message.data.attributes
sms_metadata = SMSMetadata()
sms_metadata.message_callback_url = payload.message_callback_url
sms_metadata.message_encoding = payload.message_encoding
sms_metadata.message_type = payload.message_type
sms_metadata.status = payload.status
sms_cost = SMSCost()
sms_cost.amount_display = payload.amount_display
sms_cost.amount_nanodollars = payload.amount_nanodollars
sms_message = SMSDBMessage()
sms_message.from_number = payload.from_number
sms_message.direction = payload.direction
sms_message.is_mms = payload.is_mms
sms_message.message = payload.message
sms_message.timestamp = payload.timestamp
sms_message.cost = sms_cost
sms_message.sms_metadata = sms_metadata
sms_contact = SMSContact()
sms_contact.phone_number = payload.to_number
sms_contact.messages = [sms_message]
return sms_contact
def to_db_existing_contact(message: SMSMessage) -> SMSDBMessage:
payload = message.data.attributes
sms_metadata = SMSMetadata()
sms_metadata.message_callback_url = payload.message_callback_url
sms_metadata.message_encoding = payload.message_encoding
sms_metadata.message_type = payload.message_type
sms_metadata.status = payload.status
sms_cost = SMSCost()
sms_cost.amount_display = payload.amount_display
sms_cost.amount_nanodollars = payload.amount_nanodollars
sms_message = SMSDBMessage()
sms_message.from_number = payload.from_number
sms_message.direction = payload.direction
sms_message.is_mms = payload.is_mms
sms_message.message = payload.message
sms_message.timestamp = payload.timestamp
sms_message.cost = sms_cost
sms_message.sms_metadata = sms_metadata
return sms_message

BIN
flowsms.db Normal file

Binary file not shown.

13
loggerino.py Normal file
View file

@ -0,0 +1,13 @@
from typing import Any, Dict
def timestamp_log_config(uvicorn_log_config: Dict[str, Any]) -> Dict[str, Any]:
datefmt = "%d-%m-%Y %H:%M:%S"
formatters = uvicorn_log_config["formatters"]
formatters["default"]["fmt"] = "%(levelprefix)s [%(asctime)s] %(message)s"
formatters["access"]["fmt"] = (
'%(levelprefix)s [%(asctime)s] %(client_addr)s - "%(request_line)s" %(status_code)s'
)
formatters["access"]["datefmt"] = datefmt
formatters["default"]["datefmt"] = datefmt
return uvicorn_log_config

View file

@ -9,10 +9,9 @@ from uuid import uuid4, UUID
class SMSContact(Base): class SMSContact(Base):
__tablename__ = 'contact' __tablename__ = 'contact'
id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4) id: Mapped[UUID] = mapped_column(primary_key=True, default=uuid4)
phone_number = Column(String, nullable=False) phone_number = Column(String, nullable=False, unique=True)
messages: Mapped[List["SMSMessage"]] = relationship() messages: Mapped[List["SMSMessage"]] = relationship()
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now()) created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
updated_at = Column(TIMESTAMP(timezone=True), default=None, onupdate=func.now())
class SMSMessage(Base): class SMSMessage(Base):
@ -25,9 +24,8 @@ class SMSMessage(Base):
message = Column(String, nullable=False) message = Column(String, nullable=False)
timestamp = Column(DateTime, nullable=False) timestamp = Column(DateTime, nullable=False)
cost: Mapped["SMSCost"] = relationship(back_populates="message") cost: Mapped["SMSCost"] = relationship(back_populates="message")
metadata: Mapped["SMSMetadata"] = relationship(back_populates="message") sms_metadata: Mapped["SMSMetadata"] = relationship(back_populates="message")
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now()) created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
updated_at = Column(TIMESTAMP(timezone=True), default=None, onupdate=func.now())
class SMSCost(Base): class SMSCost(Base):
@ -38,7 +36,6 @@ class SMSCost(Base):
message_id: Mapped[UUID] = mapped_column(ForeignKey("message.id")) message_id: Mapped[UUID] = mapped_column(ForeignKey("message.id"))
message: Mapped["SMSMessage"] = relationship(back_populates="cost") message: Mapped["SMSMessage"] = relationship(back_populates="cost")
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now()) created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
updated_at = Column(TIMESTAMP(timezone=True), default=None, onupdate=func.now())
class SMSMetadata(Base): class SMSMetadata(Base):
@ -49,6 +46,5 @@ class SMSMetadata(Base):
message_type = Column(String, nullable=False) message_type = Column(String, nullable=False)
status = Column(String, nullable=False) status = Column(String, nullable=False)
message_id: Mapped[UUID] = mapped_column(ForeignKey("message.id")) message_id: Mapped[UUID] = mapped_column(ForeignKey("message.id"))
message: Mapped["SMSMessage"] = relationship(back_populates="metadata") message: Mapped["SMSMessage"] = relationship(back_populates="sms_metadata")
created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now()) created_at = Column(TIMESTAMP(timezone=True), nullable=False, server_default=func.now())
updated_at = Column(TIMESTAMP(timezone=True), default=None, onupdate=func.now())

View file

@ -1,3 +1,4 @@
fastapi fastapi
python-dotenv python-dotenv
sqlalchemy sqlalchemy
uvicorn

View file

@ -0,0 +1,30 @@
from database import get_db
from dtos import to_db, to_db_existing_contact
from models import SMSContact, SMSMessage as SMSDBMessage
from schemas import SMSMessage
from sqlalchemy.orm import joinedload
def inbound_sms_handler(message: SMSMessage) -> None:
db = next(get_db())
payload = None
sms_contact_exists = db.query(SMSContact).filter(SMSContact.phone_number == message.data.attributes.to_number).first()
if sms_contact_exists:
payload = to_db_existing_contact(message)
payload.to_number = sms_contact_exists.id
else:
payload = to_db(message)
db.add(payload)
db.commit()
db.refresh(payload)
def retrieve_sms_messages_by_phone_number(number: str, limit: int , page: int) -> SMSContact:
db = next(get_db())
skip = (page - 1) * limit
messages = db.query(SMSContact).filter(SMSContact.phone_number == number).options(joinedload(SMSContact.messages)).limit(limit).offset(skip).first()
return messages