ABAO77 commited on
Commit
3973360
·
verified ·
1 Parent(s): a9da4a4

Upload 141 files

Browse files

init : set all data

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +29 -0
  2. app.py +27 -0
  3. requirements.txt +21 -0
  4. src/.DS_Store +0 -0
  5. src/__pycache__/__init__.cpython-311.pyc +0 -0
  6. src/__pycache__/app.cpython-311.pyc +0 -0
  7. src/__pycache__/state.cpython-311.pyc +0 -0
  8. src/apis/.DS_Store +0 -0
  9. src/apis/__pycache__/__init__.cpython-311.pyc +0 -0
  10. src/apis/__pycache__/create_app.cpython-311.pyc +0 -0
  11. src/apis/controllers/.DS_Store +0 -0
  12. src/apis/controllers/__pycache__/__init__.cpython-311.pyc +0 -0
  13. src/apis/controllers/__pycache__/auth_controller.cpython-311.pyc +0 -0
  14. src/apis/controllers/__pycache__/chat_controller.cpython-311.pyc +0 -0
  15. src/apis/controllers/__pycache__/destination_controller.cpython-311.pyc +0 -0
  16. src/apis/controllers/__pycache__/hotel_controller.cpython-311.pyc +0 -0
  17. src/apis/controllers/__pycache__/location_controller.cpython-311.pyc +0 -0
  18. src/apis/controllers/__pycache__/planner_controller.cpython-311.pyc +0 -0
  19. src/apis/controllers/__pycache__/post_controller.cpython-311.pyc +0 -0
  20. src/apis/controllers/__pycache__/scheduling_controller.cpython-311.pyc +0 -0
  21. src/apis/controllers/auth_controller.py +38 -0
  22. src/apis/controllers/chat_controller.py +240 -0
  23. src/apis/controllers/destination_controller.py +37 -0
  24. src/apis/controllers/hotel_controller.py +109 -0
  25. src/apis/controllers/location_controller.py +77 -0
  26. src/apis/controllers/planner_controller.py +57 -0
  27. src/apis/controllers/post_controller.py +122 -0
  28. src/apis/controllers/scheduling_controller.py +238 -0
  29. src/apis/create_app.py +64 -0
  30. src/apis/interfaces/__pycache__/api_interface.cpython-311.pyc +0 -0
  31. src/apis/interfaces/__pycache__/auth_interface.cpython-311.pyc +0 -0
  32. src/apis/interfaces/api_interface.py +106 -0
  33. src/apis/interfaces/auth_interface.py +18 -0
  34. src/apis/middlewares/__pycache__/auth_middleware.cpython-311.pyc +0 -0
  35. src/apis/middlewares/auth_middleware.py +40 -0
  36. src/apis/models/BaseModel.py +17 -0
  37. src/apis/models/__pycache__/BaseModel.cpython-311.pyc +0 -0
  38. src/apis/models/__pycache__/destination_models.cpython-311.pyc +0 -0
  39. src/apis/models/__pycache__/hotel_models.cpython-311.pyc +0 -0
  40. src/apis/models/__pycache__/post_models.cpython-311.pyc +0 -0
  41. src/apis/models/__pycache__/schedule_models.cpython-311.pyc +0 -0
  42. src/apis/models/__pycache__/user_models.cpython-311.pyc +0 -0
  43. src/apis/models/destination_models.py +28 -0
  44. src/apis/models/hotel_models.py +29 -0
  45. src/apis/models/post_models.py +53 -0
  46. src/apis/models/schedule_models.py +25 -0
  47. src/apis/models/user_models.py +35 -0
  48. src/apis/providers/__pycache__/__init__.cpython-311.pyc +0 -0
  49. src/apis/providers/__pycache__/jwt_provider.cpython-311.pyc +0 -0
  50. src/apis/providers/jwt_provider.py +37 -0
Dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the slim variant of Python 3.11 to reduce the size
2
+ FROM python:3.11-slim
3
+
4
+ # Add a non-root user for better security
5
+ RUN useradd -m -u 1000 user
6
+
7
+ # Switch to non-root user
8
+ USER user
9
+
10
+ # Ensure pip, setuptools, and wheel are up to date
11
+ RUN python -m pip install --upgrade pip setuptools wheel
12
+
13
+ # Set PATH to include user installs
14
+ ENV PATH="/home/user/.local/bin:$PATH"
15
+
16
+ # Set the working directory inside the container
17
+ WORKDIR /app
18
+
19
+ # Copy requirements.txt file and install dependencies
20
+ COPY --chown=user ./requirements.txt /app/requirements.txt
21
+
22
+ # Install only necessary dependencies from the requirements.txt
23
+ RUN pip install --no-cache-dir -r /app/requirements.txt
24
+
25
+ # Copy the rest of the application code to the working directory
26
+ COPY --chown=user . /app
27
+
28
+ # Start the FastAPI application with uvicorn
29
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import base64
3
+ import json
4
+ import os
5
+
6
+ load_dotenv(override=True)
7
+ encoded_env = os.getenv("ENCODED_ENV")
8
+ if encoded_env:
9
+ # Decode the base64 string
10
+ decoded_env = base64.b64decode(encoded_env).decode()
11
+
12
+ # Load it as a dictionary
13
+ env_data = json.loads(decoded_env)
14
+
15
+ # Set environment variables
16
+ for key, value in env_data.items():
17
+ os.environ[key] = value
18
+ from src.apis.create_app import create_app, api_router
19
+ import uvicorn
20
+
21
+
22
+ app = create_app()
23
+
24
+ # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3M2IwZDMzNTQ5OTg5Zjc1NmZhMzk3MCJ9.a3A9B1ZpzkzIPvhLqFpasK4sk2ocqmc1M80rtyAkbmM
25
+ app.include_router(api_router)
26
+ if __name__ == "__main__":
27
+ uvicorn.run("app:app", host="0.0.0.0", port=3002)
requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ langgraph
2
+ langchain
3
+ python-dotenv
4
+ motor
5
+ langchain-community
6
+ langchain-mongodb
7
+ fastapi
8
+ uvicorn
9
+ pytz
10
+ PyJWT==2.8.0
11
+ python_jose==3.3.0
12
+ pydantic[email]
13
+ jose
14
+ langchain-google-genai
15
+ python-dateutil
16
+ pandas
17
+ openpyxl
18
+ langchain-redis
19
+ redis
20
+ bs4
21
+ duckduckgo-search
src/.DS_Store ADDED
Binary file (8.2 kB). View file
 
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (221 Bytes). View file
 
src/__pycache__/app.cpython-311.pyc ADDED
Binary file (958 Bytes). View file
 
src/__pycache__/state.cpython-311.pyc ADDED
Binary file (763 Bytes). View file
 
src/apis/.DS_Store ADDED
Binary file (8.2 kB). View file
 
src/apis/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (166 Bytes). View file
 
src/apis/__pycache__/create_app.cpython-311.pyc ADDED
Binary file (3.14 kB). View file
 
src/apis/controllers/.DS_Store ADDED
Binary file (6.15 kB). View file
 
src/apis/controllers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (217 Bytes). View file
 
src/apis/controllers/__pycache__/auth_controller.cpython-311.pyc ADDED
Binary file (2.36 kB). View file
 
src/apis/controllers/__pycache__/chat_controller.cpython-311.pyc ADDED
Binary file (10.2 kB). View file
 
src/apis/controllers/__pycache__/destination_controller.cpython-311.pyc ADDED
Binary file (3.5 kB). View file
 
src/apis/controllers/__pycache__/hotel_controller.cpython-311.pyc ADDED
Binary file (5.15 kB). View file
 
src/apis/controllers/__pycache__/location_controller.cpython-311.pyc ADDED
Binary file (4.64 kB). View file
 
src/apis/controllers/__pycache__/planner_controller.cpython-311.pyc ADDED
Binary file (2.86 kB). View file
 
src/apis/controllers/__pycache__/post_controller.cpython-311.pyc ADDED
Binary file (6.69 kB). View file
 
src/apis/controllers/__pycache__/scheduling_controller.cpython-311.pyc ADDED
Binary file (7.49 kB). View file
 
src/apis/controllers/auth_controller.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException, status
2
+ from src.apis.models.user_models import User
3
+ from src.utils.mongo import UserCRUD
4
+ from src.apis.providers.jwt_provider import JWTProvider
5
+ from src.utils.logger import logger
6
+ import jwt
7
+
8
+ jwt_provider = JWTProvider()
9
+
10
+
11
+ async def login_control(token):
12
+ if not token:
13
+ raise HTTPException(
14
+ status_code=status.HTTP_401_UNAUTHORIZED,
15
+ detail="Authorization Token is required",
16
+ )
17
+ decoded_token = jwt.decode(token, options={"verify_signature": False})
18
+ decoded_data = {
19
+ "name": decoded_token["name"],
20
+ "email": decoded_token["email"],
21
+ "picture": decoded_token["picture"],
22
+ }
23
+ user = User(**decoded_data)
24
+ logger.info(f"User {user} is logging in.")
25
+ existing_user = await UserCRUD.read_one({"email": user.email})
26
+ if not existing_user:
27
+ user_id = await UserCRUD.create(user.model_dump())
28
+ logger.info(f"User {user.email} created.")
29
+ else:
30
+ user_id = existing_user["_id"]
31
+
32
+ logger.info(f"User {user.email} logged in.")
33
+ token = jwt_provider.encrypt({"id": str(user_id)})
34
+ user_data = user.__dict__
35
+ user_data.pop("created_at", None)
36
+ user_data.pop("updated_at", None)
37
+ user_data.pop("expire_at", None)
38
+ return token, user_data
src/apis/controllers/chat_controller.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.messages import HumanMessage, AIMessage
2
+ from src.langgraph.multi_agent.chat.chat_flow import app as workflow
3
+ from src.utils.mongo import chat_messages_history
4
+ from src.utils.logger import logger
5
+ from src.utils.mongo import chat_history_management_crud
6
+ from src.apis.interfaces.api_interface import Chat
7
+ from src.utils.helper import handle_validator_raise
8
+ from src.utils.redis import get_key_redis, set_key_redis
9
+ import json
10
+ from langchain_core.messages.ai import AIMessageChunk
11
+ from fastapi import BackgroundTasks
12
+ from fastapi.responses import JSONResponse
13
+
14
+
15
+ @handle_validator_raise
16
+ def post_process_history(history):
17
+ processed_history = []
18
+ for entry in history:
19
+ if entry["type"] == "human":
20
+ processed_history.append(HumanMessage(content=entry["content"]))
21
+ elif entry["type"] == "ai":
22
+ processed_history.append(AIMessage(content=entry["content"]))
23
+ return processed_history
24
+
25
+
26
+ async def save_history(user_id, human_message, ai_message, intent):
27
+ messages_add_to_history = [HumanMessage(human_message), AIMessage(ai_message)]
28
+ messages_add_to_history_dict = [
29
+ {"type": "human", "content": human_message},
30
+ {"type": "ai", "content": ai_message},
31
+ ]
32
+ messages_add_to_history_cache = {
33
+ "message": messages_add_to_history_dict,
34
+ "intent": intent,
35
+ }
36
+ history = chat_messages_history(user_id)
37
+ await history.aadd_messages(messages_add_to_history)
38
+ check_exist_history = await chat_history_management_crud.read_one(
39
+ {"session_id": user_id}
40
+ )
41
+ if check_exist_history is None:
42
+ await chat_history_management_crud.create(
43
+ {"user_id": user_id, "session_id": user_id, "intent": intent}
44
+ )
45
+ logger.info("History created")
46
+ else:
47
+ await chat_history_management_crud.update(
48
+ {"session_id": user_id}, {"intent": intent}
49
+ )
50
+ logger.info("History updated")
51
+ history_cache = await get_key_redis(f"chat_history_{user_id}")
52
+ if history_cache is not None:
53
+ history_cache = eval(history_cache)
54
+ history_cache["message"] = (
55
+ history_cache["message"] + messages_add_to_history_dict
56
+ )
57
+ history_cache["intent"] = intent
58
+ await set_key_redis(
59
+ f"chat_history_{user_id}",
60
+ str(history_cache),
61
+ )
62
+ return {"message": "History updated"}
63
+ await set_key_redis(f"chat_history_{user_id}", str(messages_add_to_history_cache))
64
+ return {"message": "History created"}
65
+
66
+
67
+ async def chat_streaming_function(user, data: Chat, background_tasks: BackgroundTasks):
68
+ human_message = data.message
69
+ history = data.history
70
+ lat = data.lat
71
+ long = data.long
72
+ language = data.language
73
+ logger.info(f"Language: {language}")
74
+ process_history = post_process_history(history) if history is not None else None
75
+ config = {
76
+ "configurable": {
77
+ "user_id": user["id"],
78
+ "user_email": user["email"],
79
+ "contact_number": user["contact_number"],
80
+ "session_id": user["id"],
81
+ "lat": lat,
82
+ "long": long,
83
+ }
84
+ }
85
+ # try:
86
+ initial_input = {
87
+ "messages": [("user", human_message)],
88
+ "messages_history": process_history,
89
+ "entry_message": None,
90
+ "manual_save": False,
91
+ "intent": data.intent,
92
+ "language": language,
93
+ "tool_name": None,
94
+ }
95
+ last_output_state = None
96
+ temp = ""
97
+ async for event in workflow.astream(
98
+ input=initial_input,
99
+ config=config,
100
+ stream_mode=["messages", "values"],
101
+ ):
102
+ event_type, event_message = event
103
+ if event_type == "messages":
104
+ message, metadata = event_message
105
+ if (
106
+ isinstance(message, AIMessageChunk)
107
+ and message.tool_calls
108
+ and message.tool_call_chunks[0]["name"] != "ClassifyUserIntent"
109
+ ):
110
+ tool_name = message.tool_call_chunks[0]["name"]
111
+ message_yield = json.dumps(
112
+ {"type": "tool_call", "content": tool_name}, ensure_ascii=False
113
+ )
114
+ print(message_yield)
115
+ yield message_yield + "\n"
116
+ if metadata["langgraph_node"] in [
117
+ "primary_assistant",
118
+ "scheduling_agent",
119
+ "book_hotel_agent",
120
+ ]:
121
+
122
+ if message.content:
123
+ temp += message.content
124
+ message_yield = json.dumps(
125
+ {"type": "message", "content": temp}, ensure_ascii=False
126
+ )
127
+ print(message_yield)
128
+ yield message_yield + "\n"
129
+ if event_type == "values":
130
+ last_output_state = event_message
131
+
132
+ final_ai_output = last_output_state["messages"][-1].content
133
+ final_intent = last_output_state["intent"]
134
+ tool_name_important = last_output_state["tool_name"]
135
+
136
+ final_response = json.dumps(
137
+ {
138
+ "type": "final",
139
+ "content": final_ai_output,
140
+ "intent": final_intent,
141
+ "tool_name": tool_name_important,
142
+ },
143
+ ensure_ascii=False,
144
+ )
145
+ yield final_response
146
+
147
+ background_tasks.add_task(
148
+ save_history, user["id"], human_message, final_ai_output, final_intent
149
+ )
150
+
151
+
152
+ async def chat_function(user, data: Chat, background_tasks: BackgroundTasks):
153
+ message = data.message
154
+ history = data.history
155
+ lat = data.lat
156
+ long = data.long
157
+ language = data.language
158
+ logger.info(f"Language: {language}")
159
+ process_history = post_process_history(history) if history is not None else None
160
+ config = {
161
+ "configurable": {
162
+ "user_id": user["id"],
163
+ "user_email": user["email"],
164
+ "contact_number": user["contact_number"],
165
+ "session_id": user["id"],
166
+ "lat": lat,
167
+ "long": long,
168
+ }
169
+ }
170
+ # try:
171
+ initial_input = {
172
+ "messages": [("user", message)],
173
+ "messages_history": process_history,
174
+ "entry_message": None,
175
+ "manual_save": False,
176
+ "intent": data.intent,
177
+ "language": language,
178
+ "tool_name": None,
179
+ }
180
+ output = await workflow.ainvoke(initial_input, config)
181
+
182
+ final_ai_output = output["messages"][-1].content
183
+ final_intent = output["intent"]
184
+ tool_name = output["tool_name"]
185
+
186
+ if final_ai_output is None:
187
+ return JSONResponse(
188
+ content={"message": "Error in chat_function"}, status_code=500
189
+ )
190
+ background_tasks.add_task(
191
+ save_history, user["id"], data.message, final_ai_output, final_intent
192
+ )
193
+
194
+ response_ouput = {
195
+ "message": final_ai_output,
196
+ "intent": final_intent,
197
+ "tool_name": tool_name,
198
+ }
199
+ return JSONResponse(content=response_ouput, status_code=200)
200
+
201
+
202
+ async def get_intent_function(session_id):
203
+ record = await chat_history_management_crud.read_one({"session_id": session_id})
204
+ if record is None:
205
+ return None
206
+
207
+ return record["intent"]
208
+
209
+
210
+ async def get_history_function(session_id):
211
+ history = chat_messages_history(session_id, 50)
212
+ try:
213
+ history_messages = await get_key_redis(f"chat_history_{session_id}")
214
+ if not history_messages:
215
+ logger.info("History not found in redis")
216
+ history_messages = await history.aget_messages()
217
+ history_messages = [
218
+ i.model_dump(include=["type", "content"]) for i in history_messages
219
+ ]
220
+ intent = await get_intent_function(session_id)
221
+ return {"message": history_messages, "intent": intent}
222
+ history_messages = eval(history_messages)
223
+ return history_messages
224
+ except Exception as e:
225
+ logger.error(f"Error in get_history_function: {e}")
226
+ return {"message": [], "intent": None}
227
+
228
+
229
+ async def list_chat_history_function(user_id: str):
230
+ result = await chat_history_management_crud.read({"user_id": user_id})
231
+ if result is None:
232
+ return []
233
+ result = [i["session_id"] for i in result]
234
+ return result
235
+
236
+
237
+ async def delete_chat_history_function(session_id: str):
238
+ history = chat_messages_history(session_id, 50)
239
+ await history.aclear()
240
+ return {"message": "Chat history has been deleted"}
src/apis/controllers/destination_controller.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Dict, Any
2
+ import aiohttp
3
+ from fastapi import HTTPException
4
+ from src.utils.logger import logger
5
+ async def destination_suggestion_controller(question: str, top_k: int = 5) -> List[Dict[str, Any]]:
6
+ async with aiohttp.ClientSession() as session:
7
+ # Get question tags
8
+ try:
9
+ async with session.get(
10
+ f"https://darkbreakerk-triventure-ai.hf.space/model/get_question_tags/{question}"
11
+ ) as response_tag:
12
+ if response_tag.status == 200:
13
+ tag_data = await response_tag.json()
14
+ tags = " ".join(tag_data["question_tags"])
15
+ logger.info(f"Tags: {tags} for question: {question}")
16
+ else:
17
+ raise HTTPException(
18
+ status_code=response_tag.status,
19
+ detail=f"Tag request failed with status {response_tag.status}"
20
+ )
21
+
22
+ # Get destinations list
23
+ async with session.get(
24
+ f"https://darkbreakerk-triventure-ai.hf.space/model/get_destinations_list/{tags}/{top_k}"
25
+ ) as response:
26
+ if response.status == 200:
27
+ data = await response.json()
28
+ logger.info(f"Destination suggestion for question: {data}")
29
+ return data["destinations_list"]
30
+ else:
31
+ raise HTTPException(
32
+ status_code=response.status,
33
+ detail=f"Destinations request failed with status {response.status}"
34
+ )
35
+
36
+ except aiohttp.ClientError as e:
37
+ raise HTTPException(status_code=500, detail=f"Request failed: {str(e)}")
src/apis/controllers/hotel_controller.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Dict, Union
2
+ from datetime import datetime
3
+ from src.utils.mongo import BookHotelCRUD
4
+ from src.utils.logger import logger
5
+ import smtplib
6
+ from datetime import datetime
7
+ from email.mime.multipart import MIMEMultipart
8
+ from email.mime.text import MIMEText
9
+
10
+
11
+ def send_booking_confirmation_email(
12
+ user_email: str,
13
+ user_contact_number: str,
14
+ hotel_email: str,
15
+ start_time: datetime,
16
+ end_time: datetime,
17
+ ):
18
+ host_email = "htbqn2003@gmail.com"
19
+ msg = MIMEMultipart()
20
+ msg["From"] = host_email
21
+ msg["To"] = hotel_email
22
+ msg["Subject"] = f"TriVenture AI Application Booking from {user_email}"
23
+
24
+ email_content = f"""
25
+ <html>
26
+ <body style="font-family: Arial, sans-serif; color: #333;">
27
+ <h2 style="color: #0073e6;">Booking Confirmation</h2>
28
+ <p style="font-size: 16px;">Dear <strong>Hotel Manager</strong>,</p>
29
+ <p style="font-size: 16px;">
30
+ I would like to book your hotel from <strong>{start_time.strftime('%Y-%m-%d %H:%M:%S')}</strong> to <strong>{end_time.strftime('%Y-%m-%d %H:%M:%S')}</strong>.
31
+ </p>
32
+ <p style="font-size: 16px;">My personal information is as follows:</p>
33
+ <ul style="font-size: 16px;">
34
+ <li><strong>Email:</strong> {user_email}</li>
35
+ <li><strong>Contact Number:</strong> {user_contact_number}</li>
36
+ </ul>
37
+ <p style="font-size: 16px;">With start time: <strong>{start_time.strftime('%Y-%m-%d %H:%M:%S')}</strong> and end time: <strong>{end_time.strftime('%Y-%m-%d %H:%M:%S')}</strong>.</p>
38
+ <br>
39
+ <p style="font-size: 16px;">Best regards,<br><strong>TriVenture AI Application</strong></p>
40
+ </body>
41
+ </html>
42
+ """
43
+ msg.attach(MIMEText(email_content, "html"))
44
+ try:
45
+ server = smtplib.SMTP("smtp.gmail.com", 587)
46
+ server.starttls()
47
+ server.login(host_email, "lvvi ouzk vafe vgem")
48
+ server.sendmail(host_email, hotel_email, msg.as_string())
49
+ server.quit()
50
+ logger.info("Booking confirmation email sent successfully.")
51
+ except Exception as e:
52
+ logger.error(f"Failed to send email: {str(e)}")
53
+
54
+
55
+ async def book_hotel_controller(
56
+ hotel_email: str,
57
+ hotel_name: str,
58
+ address: str,
59
+ phone_number: Optional[str],
60
+ website: Optional[str],
61
+ start_time_str: Optional[datetime],
62
+ end_time_str: Optional[datetime],
63
+ user_id,
64
+ ) -> Union[Dict[str, str], Dict[str, str]]:
65
+ try:
66
+ check_existing = await BookHotelCRUD.read_one(
67
+ {
68
+ "user_id": user_id,
69
+ "$or": [
70
+ {
71
+ "start_time": {"$lte": start_time_str},
72
+ "end_time": {"$gt": start_time_str},
73
+ },
74
+ {
75
+ "start_time": {"$lt": end_time_str},
76
+ "end_time": {"$gte": end_time_str},
77
+ },
78
+ {
79
+ "start_time": {"$gte": start_time_str},
80
+ "end_time": {"$lte": end_time_str},
81
+ },
82
+ ],
83
+ }
84
+ )
85
+
86
+ if check_existing:
87
+ logger.info(f"Existing booking: {check_existing}")
88
+ return {
89
+ "status": "error",
90
+ "message": "In the same time, you have already booked a hotel named: "
91
+ + check_existing["hotel_name"],
92
+ }
93
+
94
+ result = await BookHotelCRUD.create(
95
+ {
96
+ "user_id": user_id,
97
+ "hotel_name": hotel_name,
98
+ "address": address,
99
+ "phone_number": phone_number,
100
+ "hotel_email": hotel_email,
101
+ "website": website,
102
+ "start_time": start_time_str,
103
+ "end_time": end_time_str,
104
+ }
105
+ )
106
+ logger.info(f"Hotel booking result: {result}")
107
+ return {"status": "success", "message": "Hotel booked successfully"}
108
+ except Exception as e:
109
+ return {"status": "error", "message": str(e)}
src/apis/controllers/location_controller.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.utils.logger import logger
2
+ from src.utils.helper import format_geoapify_response, format_weather_data
3
+ from fastapi.responses import JSONResponse
4
+ import requests
5
+ import os
6
+
7
+
8
+ def get_location_details(lat, long):
9
+ api_key = os.getenv("OPENCAGE_API_KEY")
10
+ url = f"https://api.opencagedata.com/geocode/v1/json?q={lat},{long}&pretty=1&key={api_key}"
11
+ response = requests.get(url)
12
+ if response.status_code == 200:
13
+ logger.info("Location details fetched successfully")
14
+ return JSONResponse(
15
+ content={"location": response.json()["results"][0]["formatted"]},
16
+ status_code=200,
17
+ )
18
+ else:
19
+ return JSONResponse(
20
+ content={"error": "Error fetching location details"}, status_code=500
21
+ )
22
+
23
+
24
+ def get_nearby_places(lat, long, radius, kinds):
25
+ api_key = os.getenv("OPENTRIPMAP_API_KEY", None)
26
+ if api_key is None:
27
+ logger.error("OpenTripMap API key not found")
28
+ return JSONResponse(content={"error": "API key not found"}, status=500)
29
+ url = "https://api.opentripmap.com/0.1/en/places/radius"
30
+ params = {
31
+ "radius": radius,
32
+ "lon": long,
33
+ "lat": lat,
34
+ "kinds": kinds,
35
+ "apikey": api_key,
36
+ }
37
+ response = requests.get(url, params=params, headers={"accept": "application/json"})
38
+ if response.status_code == 200:
39
+ logger.info("Places fetched successfully")
40
+ return JSONResponse(
41
+ content={"places": response.json().get("features", [])}, status_code=200
42
+ )
43
+ else:
44
+ return JSONResponse(content={"error": "Error fetching places"}, status_code=500)
45
+
46
+
47
+ def get_places(lat, long, radius, categories, limit=20):
48
+ api_key = os.getenv("GEOAPIFY_API_KEY", None)
49
+ if api_key is None:
50
+ logger.error("Geoapify API key not found")
51
+ return JSONResponse(content={"error": "API key not found"}, status=500)
52
+ url = f"https://api.geoapify.com/v2/places?categories={categories}&filter=circle:{long},{lat},{radius}&limit={limit}&apiKey={api_key}"
53
+ response = requests.get(url)
54
+ if response.status_code == 200:
55
+ response = response.json().get("features", [])
56
+ # logger.info(f"RESPONSE:{response}")
57
+ if response:
58
+ response = format_geoapify_response(response, long, lat)
59
+ return JSONResponse(content=response, status_code=200)
60
+ else:
61
+ return JSONResponse(content={"error": "Error fetching places"}, status_code=500)
62
+
63
+
64
+ def get_weather(lat, long):
65
+ api_key = os.getenv("OPENWEATHER_API_KEY", None)
66
+ if api_key is None:
67
+ logger.error("OpenWeather API key not found")
68
+ return JSONResponse(content={"error": "API key not found"}, status=500)
69
+ url = f"https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={long}&exclude=hourly,daily&appid={api_key}"
70
+ response = requests.get(url)
71
+ if response.status_code == 200:
72
+ response = format_weather_data(response.json())
73
+ return JSONResponse(content=response, status_code=200)
74
+ else:
75
+ return JSONResponse(
76
+ content={"error": "Error fetching weather"}, status_code=500
77
+ )
src/apis/controllers/planner_controller.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from fastapi import BackgroundTasks
3
+ from src.langgraph.multi_agent.planner.planner_flow import planner_app
4
+ from src.utils.helper import parse_itinerary
5
+
6
+
7
+ async def message_generator(input_graph, config, background: BackgroundTasks):
8
+
9
+ last_output_state = None
10
+ temp = ""
11
+ async for event in planner_app.astream(
12
+ input=input_graph,
13
+ config=config,
14
+ stream_mode=["messages", "values"],
15
+ ):
16
+ event_type, event_message = event
17
+ if event_type == "messages":
18
+ message, _ = event_message
19
+ if message.content:
20
+ temp += message.content
21
+ message_yield = json.dumps(
22
+ {"type": "message", "content": temp},
23
+ ensure_ascii=False,
24
+ )
25
+ yield message_yield + "\n"
26
+ if event_type == "values":
27
+ last_output_state = event_message
28
+ parser_ouput = parse_itinerary(last_output_state["final_answer"])
29
+ final_response = json.dumps(
30
+ {
31
+ "type": "final",
32
+ "content": parser_ouput,
33
+ },
34
+ ensure_ascii=False,
35
+ )
36
+ yield final_response + "\n"
37
+
38
+
39
+ from pydantic import BaseModel, Field
40
+ from datetime import datetime
41
+
42
+
43
+ class Activity(BaseModel):
44
+ """Activity model"""
45
+
46
+ description: str = Field(
47
+ ..., description="Short description of the activity can have location"
48
+ )
49
+ start_time: datetime = Field(..., description="Start time of the activity")
50
+ end_time: datetime = Field(..., description="End time of the activity")
51
+
52
+
53
+ class Output(BaseModel):
54
+ """Output model"""
55
+
56
+ activities: list[Activity] = Field(..., description="List of activities")
57
+ note: str = Field(..., description="Note for the user")
src/apis/controllers/post_controller.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Dict, Union
2
+ from datetime import datetime
3
+ from src.utils.mongo import PostCRUD, CommentCRUD, LikeCRUD
4
+ from src.utils.logger import logger
5
+ from datetime import datetime
6
+ from bson import ObjectId
7
+ from typing import Optional, Dict
8
+ from datetime import datetime
9
+ from bson import ObjectId
10
+
11
+
12
+ def serialize_datetime(obj):
13
+ if isinstance(obj, datetime):
14
+ return obj.isoformat()
15
+ if isinstance(obj, ObjectId):
16
+ return str(obj)
17
+ return obj
18
+
19
+
20
+ async def create_a_post_controller(
21
+ content: str, user_id: str, destination_id: str
22
+ ) -> Dict:
23
+ try:
24
+ post = {
25
+ "content": content,
26
+ "user_id": user_id,
27
+ "destination_id": destination_id,
28
+ "comment_ids": [],
29
+ "like": [],
30
+ }
31
+ await PostCRUD.create(post)
32
+ return {"status": "success", "message": "Post created successfully"}
33
+ except Exception as e:
34
+ return {"status": "error", "message": str(e)}
35
+
36
+
37
+ async def get_a_post_controller(post_id: str) -> Dict:
38
+ try:
39
+ post = await PostCRUD.find_by_id(post_id)
40
+ if post is None:
41
+ return {"status": "error", "message": "Post not found"}
42
+
43
+ # Convert datetime objects in the post
44
+ serialized_post = {
45
+ "id": serialize_datetime(post.get("_id")),
46
+ "content": post.get("content"),
47
+ "user_id": post.get("user_id"),
48
+ "destination_id": post.get("destination_id"),
49
+ "comment_ids": post.get("comment_ids", []),
50
+ "like": post.get("like", []),
51
+ "created_at": serialize_datetime(post.get("created_at")),
52
+ "updated_at": serialize_datetime(post.get("updated_at")),
53
+ }
54
+
55
+ return {"status": "success", "message": serialized_post}
56
+ except Exception as e:
57
+ logger.error(f"Error getting post: {str(e)}")
58
+ return {"status": "error", "message": str(e)}
59
+
60
+
61
+ async def list_all_posts_controller():
62
+ try:
63
+ posts = await PostCRUD.find_all()
64
+ serialized_posts = []
65
+ for post in posts:
66
+ serialized_post = {
67
+ "id": serialize_datetime(post.get("_id")),
68
+ "content": post.get("content"),
69
+ "user_id": post.get("user_id"),
70
+ "destination_id": post.get("destination_id"),
71
+ "comment_ids": post.get("comment_ids", []),
72
+ "like": post.get("like", []),
73
+ "created_at": serialize_datetime(post.get("created_at")),
74
+ "updated_at": serialize_datetime(post.get("updated_at")),
75
+ }
76
+ serialized_posts.append(serialized_post)
77
+ return {"status": "success", "message": serialized_posts}
78
+ except Exception as e:
79
+ logger.error(f"Error listing posts: {str(e)}")
80
+ return {"status": "error", "message": str(e)}
81
+
82
+
83
+ async def update_a_post_controller(
84
+ user_id: str, post_id: str, content: str, destination_id: str
85
+ ) -> Dict:
86
+ try:
87
+ exist_data = await PostCRUD.find_by_id(post_id)
88
+ if exist_data["user_id"] != user_id:
89
+ return {
90
+ "status": "error",
91
+ "message": "You are not allowed to update this post",
92
+ }
93
+ if exist_data is None:
94
+ return {"status": "error", "message": "Post not found"}
95
+ await PostCRUD.update(
96
+ {"_id": ObjectId(post_id)},
97
+ {
98
+ "content": content,
99
+ "destination_id": destination_id,
100
+ },
101
+ )
102
+ return {"status": "success", "message": "Post updated successfully"}
103
+ except Exception as e:
104
+ logger.error(f"Error updating post: {str(e)}")
105
+ return {"status": "error", "message": str(e)}
106
+
107
+
108
+ async def delete_a_post_controller(user_id: str, post_id: str) -> Dict:
109
+ try:
110
+ exist_data = await PostCRUD.find_by_id(post_id)
111
+ if exist_data["user_id"] != user_id:
112
+ return {
113
+ "status": "error",
114
+ "message": "You are not allowed to delete this post",
115
+ }
116
+ if exist_data is None:
117
+ return {"status": "error", "message": "Post not found"}
118
+ await PostCRUD.delete({"_id": ObjectId(post_id)})
119
+ return {"status": "success", "message": "Post deleted successfully"}
120
+ except Exception as e:
121
+ logger.error(f"Error deleting post: {str(e)}")
122
+ return {"status": "error", "message": str(e)}
src/apis/controllers/scheduling_controller.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Dict, Union
2
+ from datetime import datetime
3
+ from src.utils.mongo import ScheduleCRUD
4
+ from src.utils.logger import logger
5
+ from datetime import datetime
6
+ from bson import ObjectId
7
+ from typing import Optional, Dict
8
+ from datetime import datetime
9
+ from bson import ObjectId
10
+
11
+
12
+ async def create_a_activity_controller(
13
+ activity_id: Optional[str],
14
+ activity_category: str,
15
+ description: str,
16
+ start_time: datetime,
17
+ end_time: datetime,
18
+ user_id: str,
19
+ ) -> Dict[str, str]:
20
+ try:
21
+ existing_activity = await ScheduleCRUD.read_one(
22
+ {
23
+ "user_id": user_id,
24
+ "$or": [
25
+ {
26
+ "start_time": {"$lte": start_time},
27
+ "end_time": {"$gt": start_time},
28
+ },
29
+ {
30
+ "start_time": {"$lt": end_time},
31
+ "end_time": {"$gte": end_time},
32
+ },
33
+ {
34
+ "start_time": {"$gte": start_time},
35
+ "end_time": {"$lte": end_time},
36
+ },
37
+ ],
38
+ }
39
+ )
40
+ if existing_activity:
41
+ activity_category = existing_activity.get("activity_category", "N/A")
42
+ description = existing_activity.get("description", "N/A")
43
+ start_time = existing_activity.get("start_time", "N/A")
44
+ end_time = existing_activity.get("end_time", "N/A")
45
+ return {
46
+ "status": "error",
47
+ "message": f"""Overlapping activities found:\nDescription: {description}, \nCategory: {activity_category}, \nStart time: {start_time}, \nEnd time: {end_time}. Please update or delete the existing activity to create a new one.""",
48
+ }
49
+ document = {
50
+ "user_id": user_id,
51
+ "activity_category": activity_category,
52
+ "description": description,
53
+ "start_time": start_time,
54
+ "end_time": end_time,
55
+ }
56
+ if activity_id:
57
+ logger.info(f"Create activity with ID: {activity_id}")
58
+ document["id"] = activity_id
59
+ await ScheduleCRUD.create(document)
60
+ return {"status": "success", "message": "Activity created successfully"}
61
+
62
+ except Exception as e:
63
+ logger.error(f"Error creating activity: {e}")
64
+ return {"status": "error", "message": f"Error creating activity: {str(e)}"}
65
+
66
+
67
+ async def search_activities_controller(
68
+ start_time: datetime,
69
+ end_time: datetime,
70
+ user_id: str,
71
+ ) -> Dict[str, Union[str, list[dict]]]:
72
+ try:
73
+ if not start_time or not end_time:
74
+ activities = await ScheduleCRUD.read({"user_id": user_id})
75
+ else:
76
+ activities = await ScheduleCRUD.read(
77
+ {
78
+ "user_id": user_id,
79
+ "$or": [
80
+ {
81
+ "start_time": {"$lte": start_time},
82
+ "end_time": {"$gt": start_time},
83
+ },
84
+ {
85
+ "start_time": {"$lt": end_time},
86
+ "end_time": {"$gte": end_time},
87
+ },
88
+ {
89
+ "start_time": {"$gte": start_time},
90
+ "end_time": {"$lte": end_time},
91
+ },
92
+ ],
93
+ }
94
+ )
95
+ return {"status": "success", "message": activities}
96
+ except Exception as e:
97
+ logger.error(f"Error reading activities: {e}")
98
+ return {"status": "error", "message": f"Error reading activities {e}"}
99
+
100
+
101
+ async def update_a_activity_controller(
102
+ activity_id: Optional[str],
103
+ activity_category: str,
104
+ description: str,
105
+ start_time: datetime,
106
+ end_time: datetime,
107
+ user_id: str,
108
+ ) -> Dict[str, str]:
109
+ try:
110
+ if activity_id:
111
+ existing_activity = await ScheduleCRUD.read_one(
112
+ {"_id": ObjectId(activity_id)}
113
+ )
114
+ if not existing_activity:
115
+ return {
116
+ "status": "error",
117
+ "message": f"Activity with id {activity_id} not found",
118
+ }
119
+ else:
120
+ existing_activity = await ScheduleCRUD.read_one(
121
+ {
122
+ "user_id": user_id,
123
+ "$or": [
124
+ {
125
+ "start_time": {"$lte": start_time},
126
+ "end_time": {"$gt": start_time},
127
+ },
128
+ {
129
+ "start_time": {"$lt": end_time},
130
+ "end_time": {"$gte": end_time},
131
+ },
132
+ {
133
+ "start_time": {"$gte": start_time},
134
+ "end_time": {"$lte": end_time},
135
+ },
136
+ ],
137
+ }
138
+ )
139
+ if not existing_activity:
140
+ return {
141
+ "status": "error",
142
+ "message": f"Activity with id {activity_id} not found",
143
+ }
144
+ activity_id = existing_activity["_id"]
145
+
146
+ await ScheduleCRUD.update(
147
+ {"_id": ObjectId(activity_id)},
148
+ {
149
+ "activity_category": activity_category,
150
+ "description": description,
151
+ "start_time": start_time,
152
+ "end_time": end_time,
153
+ },
154
+ )
155
+ return {"status": "success", "message": "Activity updated successfully"}
156
+ except Exception as e:
157
+ logger.error(f"Error updating activity: {e}")
158
+ return {"status": "error", "message": f"Error updating activity {e}"}
159
+
160
+
161
+ async def delete_activities_controller(
162
+ activity_id: Optional[str],
163
+ start_time: datetime,
164
+ end_time: datetime,
165
+ user_id: str,
166
+ ) -> Dict[str, str]:
167
+ try:
168
+ if activity_id:
169
+ existing_activity = await ScheduleCRUD.read_one(
170
+ {"_id": ObjectId(activity_id)}
171
+ )
172
+ if not existing_activity:
173
+ return {
174
+ "status": "error",
175
+ "message": "Don't have activity at the given time",
176
+ }
177
+ # Delete single activity by ID
178
+ await ScheduleCRUD.delete({"_id": ObjectId(activity_id)})
179
+ return {"status": "success", "message": "Successfully deleted activity"}
180
+ else:
181
+ # Find all activities in the time range
182
+ existing_activities = await ScheduleCRUD.read(
183
+ {
184
+ "user_id": user_id,
185
+ "$or": [
186
+ {
187
+ "start_time": {"$lte": start_time},
188
+ "end_time": {"$gt": start_time},
189
+ },
190
+ {
191
+ "start_time": {"$lt": end_time},
192
+ "end_time": {"$gte": end_time},
193
+ },
194
+ {
195
+ "start_time": {"$gte": start_time},
196
+ "end_time": {"$lte": end_time},
197
+ },
198
+ ],
199
+ }
200
+ )
201
+
202
+ if not existing_activities:
203
+ return {
204
+ "status": "error",
205
+ "message": "Don't have any activities at the given time range",
206
+ }
207
+
208
+ logger.info(f"Found {len(existing_activities)} activities to delete")
209
+
210
+ # Delete all activities in the time range
211
+ await ScheduleCRUD.delete(
212
+ {
213
+ "user_id": user_id,
214
+ "$or": [
215
+ {
216
+ "start_time": {"$lte": start_time},
217
+ "end_time": {"$gt": start_time},
218
+ },
219
+ {
220
+ "start_time": {"$lt": end_time},
221
+ "end_time": {"$gte": end_time},
222
+ },
223
+ {
224
+ "start_time": {"$gte": start_time},
225
+ "end_time": {"$lte": end_time},
226
+ },
227
+ ],
228
+ }
229
+ )
230
+
231
+ return {
232
+ "status": "success",
233
+ "message": f"Successfully deleted {len(existing_activities)} activities",
234
+ }
235
+
236
+ except Exception as e:
237
+ logger.error(f"Error deleting activities: {e}")
238
+ return {"status": "error", "message": f"Error deleting activities: {e}"}
src/apis/create_app.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, APIRouter
2
+ from contextlib import asynccontextmanager
3
+ import base64
4
+ import json
5
+ import os
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from src.apis.routes.chat_route import router as router_chat
8
+ from src.apis.routes.auth_route import router as router_auth
9
+ from src.apis.routes.location_route import router as router_location
10
+ from src.apis.routes.hotel_route import router as router_hotel
11
+ from src.apis.routes.travel_dest_route import router as router_travel_dest
12
+ from src.apis.routes.scheduling_router import router as router_scheduling
13
+ from src.apis.routes.planner_route import router as router_planner
14
+ from src.apis.routes.post_router import router as router_post
15
+ from src.utils.logger import logger
16
+
17
+ api_router = APIRouter()
18
+ api_router.include_router(router_chat)
19
+ api_router.include_router(router_auth)
20
+ api_router.include_router(router_location)
21
+ api_router.include_router(router_hotel)
22
+ api_router.include_router(router_travel_dest)
23
+ api_router.include_router(router_scheduling)
24
+ api_router.include_router(router_planner)
25
+ api_router.include_router(router_post)
26
+
27
+
28
+ @asynccontextmanager
29
+ async def lifespan(app: FastAPI):
30
+ logger.info("Starting the app")
31
+ # Load the ML model
32
+ encoded_env = os.getenv("ENCODED_ENV")
33
+
34
+ if encoded_env:
35
+ # Decode the base64 string
36
+ decoded_env = base64.b64decode(encoded_env).decode()
37
+
38
+ # Load it as a dictionary
39
+ env_data = json.loads(decoded_env)
40
+
41
+ # Set environment variables
42
+ for key, value in env_data.items():
43
+ os.environ[key] = value
44
+
45
+ # Verify by printing an environment variable (for testing)
46
+ print(os.getenv("MONGODB_URL"))
47
+ yield
48
+
49
+
50
+ def create_app():
51
+ app = FastAPI(
52
+ docs_url="/",
53
+ title="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3M2IwZDMzNTQ5OTg5Zjc1NmZhMzk3MCJ9.a3A9B1ZpzkzIPvhLqFpasK4sk2ocqmc1M80rtyAkbmM",
54
+ )
55
+
56
+ app.add_middleware(
57
+ CORSMiddleware,
58
+ allow_origins=["*"],
59
+ allow_credentials=True,
60
+ allow_methods=["*"],
61
+ allow_headers=["*"],
62
+ )
63
+
64
+ return app
src/apis/interfaces/__pycache__/api_interface.cpython-311.pyc ADDED
Binary file (6.82 kB). View file
 
src/apis/interfaces/__pycache__/auth_interface.cpython-311.pyc ADDED
Binary file (1.74 kB). View file
 
src/apis/interfaces/api_interface.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+ from src.apis.models.BaseModel import BaseDocument
4
+ from typing import List
5
+
6
+ class Chat(BaseModel):
7
+ message: str = Field(..., title="Message from user")
8
+ session_id: Optional[str] = Field("6701fe32d76fde9d8df1de8e", title="Session Id")
9
+ history: Optional[list] = Field(None, title="Chat history")
10
+ lat: Optional[float] = Field(13.717162954654036, title="Latitude")
11
+ long: Optional[float] = Field(109.21054173319894, title="Longitude")
12
+ intent: Optional[str] = Field(None, title="Intent")
13
+ language: Optional[str] = Field("en", title="Language")
14
+
15
+ class Config:
16
+ json_schema_extra = {
17
+ "example": {
18
+ "message": "Đề xuất cho 1 địa điểm",
19
+ "session_id": "6d16c975e8b74d979d6d680e6ff536eb",
20
+ "history": [
21
+ {"content": "tìm khách sạn xịn ở QUy Nhơn", "type": "human"},
22
+ {
23
+ "content": "search_hotels_luxury on frontend for user to select",
24
+ "type": "ai",
25
+ },
26
+ ],
27
+ "lat": 13.717162954654036,
28
+ "long": 109.21054173319894,
29
+ "intent": None,
30
+ "language": "Vietnamese",
31
+ }
32
+ }
33
+
34
+
35
+ class ChatHistory(BaseModel):
36
+ session_id: Optional[str] = Field(None, title="Session Id")
37
+
38
+ class Config:
39
+ json_schema_extra = {"example": {"session_id": "6701fe32d76fde9d8df1de8e"}}
40
+
41
+
42
+ class ChatHistoryManagement(BaseDocument):
43
+ session_id: str = Field("6701fe32d76fde9d8df1de8e", title="Session Id")
44
+ user_id: str = Field("6701fe32d76fde9d8df1de8e", title="User Id")
45
+ intent: Optional[str] = Field(None, title="Intent")
46
+
47
+ class Config:
48
+ json_schema_extra = {
49
+ "example": {
50
+ "session_id": "6701fe32d76fde9d8df1de8e",
51
+ "user_id": "6701fe32d76fde9d8df1de8e",
52
+ "intent": "greeting",
53
+ }
54
+ }
55
+
56
+
57
+ class Location(BaseModel):
58
+ lat: float = Field(13.717162954654036, title="Latitude")
59
+ long: float = Field(109.21054173319894, title="Longitude")
60
+ radius: Optional[int] = Field(5000, title="Radius in meters")
61
+ location_text: Optional[str] = Field("Hanoi", title="Location text")
62
+ categories: Optional[str] = Field("interesting_places", title="Type of places")
63
+
64
+ class Config:
65
+ json_schema_extra = {
66
+ "example": {
67
+ "lat": 13.717162954654036,
68
+ "long": 109.21054173319894,
69
+ "radius": 5000,
70
+ "location_text": "Hanoi",
71
+ "categories": "interesting_places",
72
+ }
73
+ }
74
+
75
+
76
+ class Destination(BaseModel):
77
+ id: int = Field(..., title="Destination Id", gt=0)
78
+ name: str = Field(..., title="Destination Name", min_length=1)
79
+ location: str = Field(..., title="Location", min_length=1)
80
+ description: str = Field(..., title="Description", min_length=1)
81
+
82
+
83
+ class Planning(BaseModel):
84
+ duration: str = Field("7", title="Duration")
85
+ start_date: str = Field("June 1-7", title="Start date")
86
+ location: str = Field("Quy Nhon, Vietnam", title="Location")
87
+ interests: str = Field("natural, cultural", title="Interests")
88
+ nation: str = Field("Vietnamese", title="Nation")
89
+ include_destination: Optional[List[Destination]] = Field([], title="Include destinations")
90
+ limit_interation: Optional[int] = Field(3, title="Limit interation")
91
+
92
+ class Config:
93
+ json_schema_extra = {
94
+ "example": {
95
+ "duration": "7",
96
+ "start_date": "June 1-7",
97
+ "location": "Quy Nhon, Vietnam",
98
+ "interests": "natural, cultural",
99
+ "nation": "nation",
100
+ "include_destination": {
101
+ "destination": "Ky Co Beach",
102
+ "description": "Ky Co Beach is a beautiful beach in Quy Nhon, Vietnam",
103
+ },
104
+ "limit_interation": 3,
105
+ }
106
+ }
src/apis/interfaces/auth_interface.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class Credential(BaseModel):
5
+ credential: str = Field(..., example="F9P/3?@q2!vq")
6
+
7
+
8
+ class _LoginResponseInterface(BaseModel):
9
+ token: str = Field(..., title="JWT Token")
10
+
11
+
12
+ class LoginResponseInterface(BaseModel):
13
+ msg: str = Field(..., title="Message")
14
+ data: _LoginResponseInterface = Field(..., title="User Data")
15
+
16
+
17
+ class AuthInterface(BaseModel):
18
+ gtoken: str = Field(..., title="Google Access-Token")
src/apis/middlewares/__pycache__/auth_middleware.cpython-311.pyc ADDED
Binary file (2.19 kB). View file
 
src/apis/middlewares/auth_middleware.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated
2
+ from fastapi import Depends
3
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
4
+ from fastapi.responses import JSONResponse
5
+ from src.apis.providers.jwt_provider import jwt_provider as jwt
6
+ from src.apis.models.user_models import get_user
7
+ from src.utils.mongo import UserCRUD
8
+ from bson import ObjectId
9
+ from jose import JWTError
10
+ from src.utils.logger import logger
11
+
12
+ security = HTTPBearer()
13
+
14
+
15
+ async def get_current_user(
16
+ credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)]
17
+ ):
18
+
19
+ try:
20
+ token = credentials.credentials
21
+ if not token:
22
+ return JSONResponse(
23
+ content={"msg": "Authentication failed"}, status_code=401
24
+ )
25
+ payload = jwt.decrypt(token)
26
+ user_id: str = payload["id"]
27
+ if not user_id:
28
+ return JSONResponse(
29
+ content={"msg": "Authentication failed"}, status_code=401
30
+ )
31
+ user = await UserCRUD.read_one({"_id": ObjectId(user_id)})
32
+ user_email = user.get("email", None)
33
+ logger.info(f"Request of user: {user_email}")
34
+ if not user:
35
+ return JSONResponse(
36
+ content={"msg": "Authentication failed"}, status_code=401
37
+ )
38
+ return get_user(user)
39
+ except JWTError:
40
+ return JSONResponse(content={"msg": "Authentication failed"}, status_code=401)
src/apis/models/BaseModel.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+
4
+ from datetime import datetime, timezone
5
+
6
+
7
+ class BaseDocument(BaseModel):
8
+ created_at: Optional[datetime] = Field(
9
+ default_factory=lambda: datetime.now(timezone.utc)
10
+ )
11
+ updated_at: Optional[datetime] = Field(
12
+ default_factory=lambda: datetime.now(timezone.utc)
13
+ )
14
+ expire_at: Optional[datetime] = None
15
+
16
+ class Config:
17
+ arbitrary_types_allowed = True
src/apis/models/__pycache__/BaseModel.cpython-311.pyc ADDED
Binary file (1.57 kB). View file
 
src/apis/models/__pycache__/destination_models.cpython-311.pyc ADDED
Binary file (1.61 kB). View file
 
src/apis/models/__pycache__/hotel_models.cpython-311.pyc ADDED
Binary file (2.14 kB). View file
 
src/apis/models/__pycache__/post_models.cpython-311.pyc ADDED
Binary file (2.55 kB). View file
 
src/apis/models/__pycache__/schedule_models.cpython-311.pyc ADDED
Binary file (1.8 kB). View file
 
src/apis/models/__pycache__/user_models.cpython-311.pyc ADDED
Binary file (2.46 kB). View file
 
src/apis/models/destination_models.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field
2
+ from typing import Literal
3
+ from .BaseModel import BaseDocument
4
+ from bson import ObjectId
5
+
6
+
7
+ class Destination(BaseDocument):
8
+ manager_id: str = Field("", description="Manager's id")
9
+ address: str = Field("", description="Destination's address")
10
+ name: str = Field("", description="Destination's name")
11
+ picture: list[str] = Field([], description="Destination's picture")
12
+ type: Literal["hotel", "restaurant", "attraction"] = Field(
13
+ "", description="Destination's type"
14
+ )
15
+ status: int = Field(0, description="Destination's status")
16
+
17
+ model_config = {
18
+ "json_schema_extra": {
19
+ "example": {
20
+ "manager_id": "1234567890",
21
+ "address": "1234567890",
22
+ "name": "ABAO Hotel",
23
+ "picture": ["https://example.com/picture.jpg"],
24
+ "type": "hotel",
25
+ "status": 0,
26
+ }
27
+ }
28
+ }
src/apis/models/hotel_models.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field, EmailStr
2
+ from typing import Optional
3
+ from datetime import datetime
4
+ from .BaseModel import BaseDocument
5
+
6
+
7
+ class BookHotel(BaseDocument):
8
+ hotel_name: str = Field("", description="Hotel's name")
9
+ address: str = Field("", description="Hotel's address")
10
+ phone_number: str = Field("", description="Hotel's phone number")
11
+ hotel_email: EmailStr = Field("", description="Hotel's email")
12
+ start_time: Optional[datetime] = Field("", description="Start time of the booking")
13
+ end_time: Optional[datetime] = Field("", description="End time of the booking")
14
+ rating: str = Field("", description="Hotel's rating")
15
+ website: str = Field("", description="Hotel's website")
16
+
17
+ class Config:
18
+ json_schema_extra = {
19
+ "example": {
20
+ "hotel_name": "Blue Lagoon Resort",
21
+ "address": "123 Beachside Blvd, Paradise City, Island Nation 54321",
22
+ "phone_number": "+1234567890",
23
+ "hotel_email": "baohtqe170017@fpt.edu.vn",
24
+ "start_time": "2025-01-05T14:00:00.000+00:00",
25
+ "end_time": "2025-01-10T11:00:00.000+00:00",
26
+ "rating": "4.5",
27
+ "website": "https://www.bluelagoonresort.com",
28
+ }
29
+ }
src/apis/models/post_models.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field
2
+ from .BaseModel import BaseDocument
3
+
4
+ class Comment(BaseDocument):
5
+ content: str = Field("", description="Post's content")
6
+ user_id: str = Field("", description="User's id")
7
+ post_id: str = Field("", description="Post's id")
8
+
9
+ model_config = {
10
+ "json_schema_extra": {
11
+ "example": {
12
+ "content": "John Doe",
13
+ "user_id": "1234567890",
14
+ "destination_id": "1234567890",
15
+ "comment_ids": ["1234567890"],
16
+ }
17
+ }
18
+ }
19
+
20
+
21
+ class Like(BaseDocument):
22
+ user_id: str = Field("", description="User's id")
23
+ post_id: str = Field("", description="Post's id")
24
+ comment_id: str = Field("", description="Comment's id")
25
+ type: int = Field(0, description="Type of like", gt=0, lt=3)
26
+
27
+ model_config = {
28
+ "json_schema_extra": {
29
+ "example": {
30
+ "user_id": "1234567890",
31
+ "post_id": "1234567890",
32
+ "comment_id": "1234567890",
33
+ }
34
+ }
35
+ }
36
+
37
+
38
+ class Post(BaseDocument):
39
+ content: str = Field("", description="Post's content")
40
+ user_id: str = Field("", description="User's id")
41
+ destination_id: str = Field("", description="Destination's id")
42
+ comment_ids: list[str] = Field([], description="Comment's id")
43
+ like: list[str] = Field([], description="User's id who like this post")
44
+ model_config = {
45
+ "json_schema_extra": {
46
+ "example": {
47
+ "content": "John Doe",
48
+ "user_id": "1234567890",
49
+ "destination_id": "1234567890",
50
+ "comment_ids": ["1234567890"],
51
+ }
52
+ }
53
+ }
src/apis/models/schedule_models.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field
2
+ from typing import Optional
3
+ from datetime import datetime
4
+ from .BaseModel import BaseDocument
5
+
6
+
7
+ class Schedule(BaseDocument):
8
+ id: Optional[str] = Field("", description="Activity's id")
9
+ user_id: str = Field("", description="User's id")
10
+ activity_category: str = Field("", description="Activity's category")
11
+ description: str = Field("", description="Activity's description")
12
+ start_time: Optional[datetime] = Field("", description="Activity's start time")
13
+ end_time: Optional[datetime] = Field("", description="Activity's end time")
14
+
15
+ class Config:
16
+ json_schema_extra = {
17
+ "example": {
18
+ "id": "61f7b1b7b3b3b3b3b3b3b3",
19
+ "user_id": "61f7b1b7b3b3b3b3b3b3b3",
20
+ "activity_category": "Study",
21
+ "description": "Study for the final exam",
22
+ "start_time": "2025-01-05T14:00:00.000+00:00",
23
+ "end_time": "2025-01-05T16:00:00.000+00:00",
24
+ }
25
+ }
src/apis/models/user_models.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import Field, EmailStr
2
+ from .BaseModel import BaseDocument
3
+ from bson import ObjectId
4
+
5
+
6
+ def get_user(user) -> dict:
7
+ return {
8
+ "id": str(user["_id"]),
9
+ "name": user["name"],
10
+ "email": user["email"],
11
+ "picture": user["picture"],
12
+ "contact_number": user["contact_number"],
13
+ }
14
+
15
+
16
+ def list_serial(users) -> list:
17
+ return [get_user(user) for user in users]
18
+
19
+
20
+ class User(BaseDocument):
21
+ id: str = Field(default_factory=lambda: str(ObjectId()), alias="_id")
22
+ name: str = Field("", description="User's name")
23
+ email: EmailStr = Field("", description="User's email")
24
+ picture: str = Field("", title="User Picture")
25
+ contact_number: str = Field("", description="User's contact number")
26
+
27
+ class Config:
28
+ json_schema_extra = {
29
+ "example": {
30
+ "name": "John Doe",
31
+ "email": "johnUS192@gmail.com",
32
+ "picture": "https://example.com/picture.jpg",
33
+ "contact_number": "1234567890",
34
+ }
35
+ }
src/apis/providers/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (271 Bytes). View file
 
src/apis/providers/__pycache__/jwt_provider.cpython-311.pyc ADDED
Binary file (2.23 kB). View file
 
src/apis/providers/jwt_provider.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import AnyStr, Dict, Union
2
+ import os
3
+ from fastapi import HTTPException, status
4
+ from jose import jwt, JWTError
5
+
6
+
7
+ class JWTProvider:
8
+ """
9
+ Perform JWT Encryption and Decryption
10
+ """
11
+
12
+ def __init__(
13
+ self, secret: AnyStr = os.environ.get("JWT_SECRET"), algorithm: AnyStr = "HS256"
14
+ ):
15
+ self.secret = secret
16
+ self.algorithm = algorithm
17
+
18
+ def encrypt(self, data: Dict) -> AnyStr:
19
+ """
20
+ Encrypt the data with JWT
21
+ """
22
+ return jwt.encode(data, self.secret, algorithm=self.algorithm)
23
+
24
+ def decrypt(self, token: AnyStr) -> Union[Dict, None]:
25
+ """
26
+ Decrypt the token with JWT
27
+ """
28
+ try:
29
+ return jwt.decode(token, self.secret, algorithms=[self.algorithm])
30
+ except JWTError as e:
31
+ raise HTTPException(
32
+ status_code=status.HTTP_401_UNAUTHORIZED,
33
+ detail=f"Could not validate credentials. {str(e)}",
34
+ )
35
+
36
+
37
+ jwt_provider = JWTProvider()