messages
Browse files- App/Messages/MessagesRoute.py +75 -0
- App/Messages/Model.py +27 -0
- App/Messages/Schema.py +38 -0
- App/app.py +2 -0
- App/discovery.py +1 -1
- App/modelInit.py +1 -7
App/Messages/MessagesRoute.py
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# App/Messages/Routes.py
|
2 |
+
from fastapi import APIRouter, HTTPException, Request
|
3 |
+
from .Model import Message
|
4 |
+
from .Schema import MessageCreate, MessageResponse
|
5 |
+
from uuid import UUID
|
6 |
+
from typing import List, Optional
|
7 |
+
import re
|
8 |
+
from datetime import datetime
|
9 |
+
from decimal import Decimal
|
10 |
+
|
11 |
+
message_router = APIRouter(tags=["Messages"], prefix="/messages")
|
12 |
+
|
13 |
+
|
14 |
+
@message_router.post("/sms_received", response_model=MessageResponse)
|
15 |
+
async def receive_message(request: Request):
|
16 |
+
|
17 |
+
message_raw = await request.json()
|
18 |
+
message_data = MessageCreate(**message_raw)
|
19 |
+
try:
|
20 |
+
# Extract data from the message content using regex
|
21 |
+
text = message_data.payload.message
|
22 |
+
parsed_data = parse_message_content(text)
|
23 |
+
|
24 |
+
# Create a new message record with parsed_data
|
25 |
+
new_message = await Message.create(
|
26 |
+
device_id=message_data.deviceId,
|
27 |
+
event=message_data.event,
|
28 |
+
message_id=message_data.id,
|
29 |
+
webhook_id=message_data.webhookId,
|
30 |
+
message_content=text,
|
31 |
+
phone_number=message_data.payload.phoneNumber,
|
32 |
+
received_at=message_data.payload.receivedAt,
|
33 |
+
sim_number=message_data.payload.simNumber,
|
34 |
+
parsed_data=parsed_data,
|
35 |
+
)
|
36 |
+
return MessageResponse.from_orm(new_message)
|
37 |
+
except Exception as e:
|
38 |
+
raise HTTPException(status_code=400, detail=str(e))
|
39 |
+
|
40 |
+
|
41 |
+
def parse_message_content(text: str) -> Optional[dict]:
|
42 |
+
# Regular expression to capture the data from the message
|
43 |
+
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 is Tsh([\d,]+\.\d{2})"
|
44 |
+
|
45 |
+
matches = re.search(pattern, text)
|
46 |
+
if matches:
|
47 |
+
data = {
|
48 |
+
"transaction_id": matches.group(1),
|
49 |
+
"amount_received": parse_decimal(matches.group(2)),
|
50 |
+
"phone_number": matches.group(3),
|
51 |
+
"name": matches.group(4).strip(),
|
52 |
+
"date": parse_date(matches.group(5), matches.group(6)),
|
53 |
+
"new_balance": parse_decimal(matches.group(7)),
|
54 |
+
}
|
55 |
+
return data
|
56 |
+
else:
|
57 |
+
# Return None if the message doesn't match the expected format
|
58 |
+
return None
|
59 |
+
|
60 |
+
|
61 |
+
def parse_decimal(amount_str: str) -> float:
|
62 |
+
# Remove commas and convert to Decimal
|
63 |
+
amount_str = amount_str.replace(",", "")
|
64 |
+
return float(Decimal(amount_str))
|
65 |
+
|
66 |
+
|
67 |
+
def parse_date(date_str: str, time_str: str) -> str:
|
68 |
+
# Combine date and time strings and parse into ISO format
|
69 |
+
datetime_str = f"{date_str} {time_str}"
|
70 |
+
# Adjust the format as per the actual format in the message
|
71 |
+
try:
|
72 |
+
dt = datetime.strptime(datetime_str, "%d/%m/%y %I:%M %p")
|
73 |
+
return dt.isoformat()
|
74 |
+
except ValueError:
|
75 |
+
return datetime_str # Return as-is if parsing fails
|
App/Messages/Model.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# App/Messages/Model.py
|
2 |
+
from tortoise import fields, models
|
3 |
+
from uuid import uuid4
|
4 |
+
from datetime import datetime
|
5 |
+
from tortoise.models import Model
|
6 |
+
|
7 |
+
|
8 |
+
class Message(Model):
|
9 |
+
id = fields.UUIDField(pk=True, default=uuid4)
|
10 |
+
device_id = fields.CharField(max_length=100, null=True)
|
11 |
+
event = fields.CharField(max_length=100, null=True)
|
12 |
+
message_id = fields.CharField(
|
13 |
+
max_length=100, null=True
|
14 |
+
) # Corresponds to 'id' in the JSON
|
15 |
+
webhook_id = fields.CharField(max_length=100, null=True)
|
16 |
+
message_content = fields.TextField()
|
17 |
+
phone_number = fields.CharField(max_length=20)
|
18 |
+
received_at = fields.DatetimeField()
|
19 |
+
sim_number = fields.IntField(null=True)
|
20 |
+
parsed_data = fields.JSONField(null=True) # New field for parsed data
|
21 |
+
created_time = fields.DatetimeField(auto_now_add=True)
|
22 |
+
|
23 |
+
class Meta:
|
24 |
+
table = "messages"
|
25 |
+
|
26 |
+
def __str__(self):
|
27 |
+
return f"Message from {self.phone_number} at {self.received_at}"
|
App/Messages/Schema.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# App/Messages/Schema.py
|
2 |
+
from pydantic import BaseModel, UUID4, Field, ConfigDict
|
3 |
+
from datetime import datetime
|
4 |
+
from typing import Optional
|
5 |
+
|
6 |
+
|
7 |
+
class PayloadSchema(BaseModel):
|
8 |
+
message: str
|
9 |
+
phoneNumber: str
|
10 |
+
receivedAt: datetime
|
11 |
+
simNumber: Optional[int]
|
12 |
+
|
13 |
+
|
14 |
+
class MessageCreate(BaseModel):
|
15 |
+
deviceId: Optional[str]
|
16 |
+
event: Optional[str]
|
17 |
+
id: Optional[str] = Field(alias="messageId") # Renaming to prevent conflict
|
18 |
+
payload: PayloadSchema
|
19 |
+
webhookId: Optional[str]
|
20 |
+
|
21 |
+
|
22 |
+
class MessageResponse(BaseModel):
|
23 |
+
id: UUID4
|
24 |
+
device_id: Optional[str]
|
25 |
+
event: Optional[str]
|
26 |
+
message_id: Optional[str]
|
27 |
+
webhook_id: Optional[str]
|
28 |
+
message_content: str
|
29 |
+
phone_number: str
|
30 |
+
received_at: datetime
|
31 |
+
sim_number: Optional[int]
|
32 |
+
parsed_data: Optional[dict] # Include parsed_data in the response
|
33 |
+
created_time: datetime
|
34 |
+
model_config = ConfigDict(from_attributes=True)
|
35 |
+
|
36 |
+
# class Config:
|
37 |
+
# orm_mode = True
|
38 |
+
# allow_population_by_field_name = True
|
App/app.py
CHANGED
@@ -8,6 +8,7 @@ from .Payments.PaymentsRoutes import payment_router
|
|
8 |
from .Plans.PlanRoutes import plan_router
|
9 |
from .Portals.PortalRoutes import portal_router
|
10 |
from .Metrics.MetricsRoutes import metrics_router
|
|
|
11 |
|
12 |
app = FastAPI()
|
13 |
|
@@ -30,6 +31,7 @@ app.include_router(payment_router)
|
|
30 |
app.include_router(plan_router)
|
31 |
app.include_router(portal_router)
|
32 |
app.include_router(metrics_router)
|
|
|
33 |
|
34 |
|
35 |
if __name__ == "__main__":
|
|
|
8 |
from .Plans.PlanRoutes import plan_router
|
9 |
from .Portals.PortalRoutes import portal_router
|
10 |
from .Metrics.MetricsRoutes import metrics_router
|
11 |
+
from .Messages.MessagesRoute import message_router
|
12 |
|
13 |
app = FastAPI()
|
14 |
|
|
|
31 |
app.include_router(plan_router)
|
32 |
app.include_router(portal_router)
|
33 |
app.include_router(metrics_router)
|
34 |
+
app.include_router(message_router)
|
35 |
|
36 |
|
37 |
if __name__ == "__main__":
|
App/discovery.py
CHANGED
@@ -28,7 +28,7 @@ def discover_models(target_file: str, directory: str = None) -> List[str]:
|
|
28 |
os.sep, "."
|
29 |
) # Correct dot notation
|
30 |
module_name = module_name[:-3] # Remove '.py' extension
|
31 |
-
model_modules.append(module_name)
|
32 |
|
33 |
print("Discovered models:", model_modules)
|
34 |
return model_modules
|
|
|
28 |
os.sep, "."
|
29 |
) # Correct dot notation
|
30 |
module_name = module_name[:-3] # Remove '.py' extension
|
31 |
+
model_modules.append("App." + module_name)
|
32 |
|
33 |
print("Discovered models:", model_modules)
|
34 |
return model_modules
|
App/modelInit.py
CHANGED
@@ -26,13 +26,7 @@ TORTOISE_ORM = {
|
|
26 |
},
|
27 |
"apps": {
|
28 |
"models": {
|
29 |
-
"models":
|
30 |
-
"App.Payments.Model",
|
31 |
-
"App.Plans.Model",
|
32 |
-
"App.Portals.Model",
|
33 |
-
"App.Subscriptions.Model",
|
34 |
-
"App.Users.Model",
|
35 |
-
],
|
36 |
"default_connection": "default",
|
37 |
}
|
38 |
},
|
|
|
26 |
},
|
27 |
"apps": {
|
28 |
"models": {
|
29 |
+
"models": models,
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
"default_connection": "default",
|
31 |
}
|
32 |
},
|