hotspot / App /Messages /MessagesRoute.py
Mbonea's picture
changes
41eef54
raw
history blame
6.66 kB
# App/Messages/Routes.py
from fastapi import APIRouter, HTTPException, Depends
from typing import List
from .Model import Message
from .Schema import MessageCreate, MessageResponse
from typing import Optional
import re
from datetime import datetime
from decimal import Decimal
from App.Payments.PaymentsRoutes import create_payment
from App.Payments.Schema import CreatePaymentRequest
from App.Users.Model import User
from App.Payments.Schema import PaymentMethod
from App.Payments.Model import Payment
from App.Plans.Model import Plan
from tortoise.contrib.pydantic import pydantic_model_creator
import traceback
import logging
from App.Users.dependencies import (
get_current_active_user,
UserType,
) # Assuming you have a dependency to get the current user
message_router = APIRouter(tags=["Messages"])
@message_router.get("/messages", response_model=List[MessageResponse])
async def get_all_messages(current_user: User = Depends(get_current_active_user)):
# Check if the current user is an admin
if current_user.user_type != UserType.ADMIN:
raise HTTPException(
status_code=403,
detail="User does not have permission to access this resource",
)
# Fetch all messages from the database
messages = await Message.all()
# Serialize the messages
return [MessageResponse.from_orm(message) for message in messages]
message_router = APIRouter(tags=["Messages"], prefix="/messages")
logging.basicConfig(level=logging.WARNING)
@message_router.post("/sms_received", response_model=MessageResponse)
async def receive_message(message_data: MessageCreate):
Message_Pydantic = pydantic_model_creator(Message)
try:
# Extract data from the message content using regex
text = message_data.payload.message
parsed_data = parse_message_content(text)
# Validate parsed_data
if not parsed_data:
# Create a new message record with parsed_data as None
message = await Message.create(
device_id=message_data.deviceId,
event=message_data.event,
message_id=message_data.id,
webhook_id=message_data.webhookId,
message_content=message_data.payload.message,
phone_number=message_data.payload.phoneNumber,
received_at=message_data.payload.receivedAt,
sim_number=message_data.payload.simNumber,
parsed_data=None, # Set parsed_data as None
)
# Return the created message
data = await Message_Pydantic.from_tortoise_orm(message)
return data
required_keys = ["phone_number", "amount_received", "transaction_id"]
missing_keys = [key for key in required_keys if key not in parsed_data]
if missing_keys:
raise HTTPException(
status_code=400, detail=f"Missing keys in parsed data: {missing_keys}"
)
# Prevent double entry from the SMS gateway app
message = await Message.get_or_none(message_id=message_data.id)
if not message:
# Process Mpesa Payments
user: User = await User.get_or_none(phoneNumber=parsed_data["phone_number"])
data_plan: Plan = await Plan.get_or_none(
amount=Decimal(parsed_data["amount_received"])
)
payment_details = CreatePaymentRequest(
user_id=user.id if user else None,
plan_id=str(data_plan.id) if data_plan else None,
amount=Decimal(parsed_data["amount_received"]),
payment_method=PaymentMethod.MPESA,
transaction_id=parsed_data["transaction_id"],
)
# Ensure 'create_payment' is an async function
payment: Payment = await create_payment(payment_details, internal=True)
await payment.create_subscription_or_balance()
# Create a new message record with parsed_data
message = await Message.create(
device_id=message_data.deviceId,
event=message_data.event,
message_id=message_data.id,
webhook_id=message_data.webhookId,
message_content=message_data.payload.message,
phone_number=message_data.payload.phoneNumber,
received_at=message_data.payload.receivedAt,
sim_number=message_data.payload.simNumber,
parsed_data=parsed_data,
)
# Serialize the message
data = await Message_Pydantic.from_tortoise_orm(message)
return data
except Exception as e:
# Log the full traceback
traceback_str = "".join(traceback.format_exception(None, e, e.__traceback__))
logging.error(f"An error occurred: {traceback_str}")
# Provide a default error message if 'e' is empty
error_message = str(e) if str(e) else "An unexpected error occurred."
raise HTTPException(status_code=500, detail=error_message)
def parse_message_content(text: str) -> Optional[dict]:
# Regular expression to capture the data from the message
pattern = r"(\w+)\sConfirmed\.You have received Tsh([\d,]+\.\d{2}) from (\d{12}) - ([A-Z ]+) on (\d{1,2}/\d{1,2}/\d{2}) at ([\d:]+ [APM]+).*?balance\sis\sTsh([\d,]+\.\d{2})"
# Replace non-breaking spaces and other whitespace characters with regular spaces
text = re.sub(r"\s+", " ", text)
matches = re.search(pattern, text)
if matches:
data = {
"transaction_id": matches.group(1),
"amount_received": parse_decimal(matches.group(2)),
"phone_number": matches.group(3),
"name": matches.group(4).strip(),
"date": parse_date(matches.group(5), matches.group(6)),
"new_balance": parse_decimal(matches.group(7)),
}
return data
else:
# Return None if the message doesn't match the expected format
return None
def parse_decimal(amount_str: str) -> float:
# Remove commas and convert to float
amount_str = amount_str.replace(",", "")
return float(Decimal(amount_str))
def parse_date(date_str: str, time_str: str) -> str:
# Combine date and time strings and parse into ISO format
datetime_str = f"{date_str} {time_str}"
try:
dt = datetime.strptime(datetime_str, "%d/%m/%y %I:%M %p")
return dt.isoformat()
except ValueError:
# Log the error
logging.error(f"Failed to parse date: {datetime_str}")
# Return current time as fallback
return datetime.now().isoformat()