ramanjitsingh1368 commited on
Commit
a3aa6c1
·
1 Parent(s): f0c93f5

Refactor Dockerfile and clean up unused schemas; update Redis client initialization and environment configuration

Browse files
.env.example CHANGED
@@ -1,18 +1,33 @@
1
  # Environment file configuration
2
  CORS_ALLOW_ORIGINS="http://localhost, http://127.0.0.1"
3
 
 
4
  LOG_FILE=app.log
5
  LOG_RETENTION="90 days"
6
 
7
- OPENAI_API_KEY=your-openai-api-key
8
- PINECONE_API_KEY=your-pinecone-api-key
 
 
 
 
9
 
10
- PINECONE_INDEX_NAME=your-pinecone-index-name
 
 
11
 
12
- MONGO_DB_URI=your-mongo-db-uri
13
- MONGO_DB_NAME=your-mongo-db-name
 
14
 
 
15
  JWT_SECRET_KEY=your-jwt-secret-key
16
 
17
- HUBSPOT_API_KEY=your-hubspot-api-key
18
- REDIS_URI=your-redis-uri
 
 
 
 
 
 
 
1
  # Environment file configuration
2
  CORS_ALLOW_ORIGINS="http://localhost, http://127.0.0.1"
3
 
4
+ # Logging configuration
5
  LOG_FILE=app.log
6
  LOG_RETENTION="90 days"
7
 
8
+ # OPENAI Configuration
9
+ OPENAI_API_KEY=
10
+ OPENAI_BASE_URL=
11
+ OPENAI_WS_BASE_URL=
12
+ OPENAI_REALTIME_MODEL=
13
+ OPENAI_CHAT_COMPLETION_MODEL=
14
 
15
+ # PINECONE Configuration
16
+ PINECONE_API_KEY=
17
+ PINECONE_INDEX_NAME=
18
 
19
+ # MONGO Configuration
20
+ MONGO_DB_URI=
21
+ MONGO_DB_NAME=
22
 
23
+ # JWT Configuration
24
  JWT_SECRET_KEY=your-jwt-secret-key
25
 
26
+ # HUBSPOT Configuration
27
+ HUBSPOT_API_KEY=
28
+ HUBSPOT_BASE_URL=
29
+
30
+ # REDIS Configuration
31
+ REDIS_URI=
32
+ REDIS_SESSION_EXPIRY=
33
+
Dockerfile CHANGED
@@ -16,10 +16,6 @@ RUN chown -R user /app
16
 
17
  USER user
18
 
19
- COPY --chown=user alembic.ini /app/
20
-
21
- COPY --chown=user migrations /app/migrations
22
-
23
  COPY --chown=user src /app/src
24
 
25
  EXPOSE 8000
 
16
 
17
  USER user
18
 
 
 
 
 
19
  COPY --chown=user src /app/src
20
 
21
  EXPOSE 8000
src/__init__.py CHANGED
@@ -15,15 +15,55 @@ if not os.getenv("CORS_ALLOW_ORIGINS"):
15
  "CORS_ALLOW_ORIGINS environment not set. Allowing localhost by default."
16
  )
17
 
 
 
 
 
 
 
18
  if not os.getenv("OPENAI_API_KEY"):
19
  raise ValueError("OPENAI_API_KEY environment not set.")
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  if not os.getenv("MONGO_DB_URI"):
22
  raise ValueError("MONGO_DB_URI environment not set.")
23
 
24
  if not os.getenv("MONGO_DB_NAME"):
25
  raise ValueError("MONGO_DB_NAME environment not set.")
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  __all__ = ["app"]
28
  __version__ = "0.1.0"
29
  __author__ = "Ramanjit Singh"
 
15
  "CORS_ALLOW_ORIGINS environment not set. Allowing localhost by default."
16
  )
17
 
18
+ if not os.getenv("LOG_FILE"):
19
+ raise ValueError("LOG_FILE environment not set.")
20
+
21
+ if not os.getenv("LOG_RETENTION"):
22
+ logger.warning("LOG_RETENTION environment not set. Defaulting to 90 days.")
23
+
24
  if not os.getenv("OPENAI_API_KEY"):
25
  raise ValueError("OPENAI_API_KEY environment not set.")
26
 
27
+ if not os.getenv("OPENAI_BASE_URL"):
28
+ raise ValueError("OPENAI_BASE_URL environment not set.")
29
+
30
+ if not os.getenv("OPENAI_WS_BASE_URL"):
31
+ raise ValueError("OPENAI_WS_BASE_URL environment not set.")
32
+
33
+ if not os.getenv("OPENAI_REALTIME_MODEL"):
34
+ raise ValueError("OPENAI_REALTIME_MODEL environment not set.")
35
+
36
+ if not os.getenv("OPENAI_CHAT_COMPLETION_MODEL"):
37
+ raise ValueError("OPENAI_CHAT_COMPLETION_MODEL environment not set.")
38
+
39
+ if not os.getenv("PINECONE_API_KEY"):
40
+ raise ValueError("PINECONE_API_KEY environment not set.")
41
+
42
+ if not os.getenv("PINECONE_INDEX_NAME"):
43
+ raise ValueError("PINECONE_INDEX_NAME environment not set.")
44
+
45
  if not os.getenv("MONGO_DB_URI"):
46
  raise ValueError("MONGO_DB_URI environment not set.")
47
 
48
  if not os.getenv("MONGO_DB_NAME"):
49
  raise ValueError("MONGO_DB_NAME environment not set.")
50
 
51
+ if not os.getenv("JWT_SECRET_KEY"):
52
+ raise ValueError("JWT_SECRET_KEY environment not set.")
53
+
54
+ if not os.getenv("HUBSPOT_API_KEY"):
55
+ raise ValueError("HUBSPOT_API_KEY environment not set.")
56
+
57
+ if not os.getenv("HUBSPOT_BASE_URL"):
58
+ raise ValueError("HUBSPOT_BASE_URL environment not set.")
59
+
60
+ if not os.getenv("REDIS_URI"):
61
+ raise ValueError("REDIS_URI environment not set.")
62
+
63
+ if not os.getenv("REDIS_SESSION_EXPIRY"):
64
+ raise ValueError("REDIS_SESSION_EXPIRY environment not set.")
65
+
66
+
67
  __all__ = ["app"]
68
  __version__ = "0.1.0"
69
  __author__ = "Ramanjit Singh"
src/config/__init__.py CHANGED
@@ -1,13 +1,6 @@
1
  from ._logger import logger
2
  from ._database import DatabaseConfig
3
- from ._redis import RedisConfig, redis_client, REDIS_SESSION_EXPIRY
4
 
5
- __all__ = [
6
- "logger",
7
- "DatabaseConfig",
8
- "RedisConfig",
9
- "redis_client",
10
- "REDIS_SESSION_EXPIRY",
11
- ]
12
  __version__ = "0.1.0"
13
  __author__ = "Ramanjit Singh"
 
1
  from ._logger import logger
2
  from ._database import DatabaseConfig
 
3
 
4
+ __all__ = ["logger", "DatabaseConfig"]
 
 
 
 
 
 
5
  __version__ = "0.1.0"
6
  __author__ = "Ramanjit Singh"
src/controllers/__init__.py CHANGED
@@ -4,14 +4,12 @@ from fastapi.security import APIKeyHeader
4
  from ._auth_controller import AuthController
5
  from ._file_controller import FileController
6
  from ._conversation_controller import ConversationController
7
- from ._meeting_controller import MeetingController
8
 
9
  api_router = APIRouter()
10
 
11
  auth_router = AuthController().api_router
12
  conversation_router = ConversationController().api_router
13
  file_router = FileController().api_router
14
- meeting_router = MeetingController().api_router
15
 
16
  API_KEY_HEADER = APIKeyHeader(name="Authorization", auto_error=False)
17
 
@@ -33,12 +31,6 @@ api_router.include_router(
33
  tags=["Semantic Search"],
34
  dependencies=[Depends(API_KEY_HEADER)],
35
  )
36
- api_router.include_router(
37
- meeting_router,
38
- prefix="/v1",
39
- tags=["Meeting"],
40
- dependencies=[Depends(API_KEY_HEADER)],
41
- )
42
 
43
  __all__ = ["api_router"]
44
  __version__ = "0.1.0"
 
4
  from ._auth_controller import AuthController
5
  from ._file_controller import FileController
6
  from ._conversation_controller import ConversationController
 
7
 
8
  api_router = APIRouter()
9
 
10
  auth_router = AuthController().api_router
11
  conversation_router = ConversationController().api_router
12
  file_router = FileController().api_router
 
13
 
14
  API_KEY_HEADER = APIKeyHeader(name="Authorization", auto_error=False)
15
 
 
31
  tags=["Semantic Search"],
32
  dependencies=[Depends(API_KEY_HEADER)],
33
  )
 
 
 
 
 
 
34
 
35
  __all__ = ["api_router"]
36
  __version__ = "0.1.0"
src/controllers/_conversation_controller.py CHANGED
@@ -1,4 +1,4 @@
1
- import re
2
  from fastapi import (
3
  APIRouter,
4
  HTTPException,
@@ -9,7 +9,6 @@ from fastapi import (
9
  from typing import List
10
 
11
  from src.config import logger
12
- from src.config import redis_client, REDIS_SESSION_EXPIRY
13
  from src.services import ConversationService
14
  from src.schemas import (
15
  CreateConversationSchema,
@@ -19,7 +18,7 @@ from src.schemas import (
19
  CreateConversationSummaryResponse,
20
  CreateWebrtcConnectionResponse,
21
  )
22
- from src.utils import JWTUtil
23
 
24
 
25
  class ConnectionManager:
@@ -27,9 +26,10 @@ class ConnectionManager:
27
  self.jwt = JWTUtil()
28
  self.active_connections: List[WebSocket] = []
29
 
30
- async def connect(self, websocket: WebSocket):
31
  await websocket.accept()
32
  token = websocket.query_params.get("token")
 
33
  user = self.jwt.validate_jwt(token)
34
 
35
  if not user:
@@ -37,30 +37,41 @@ class ConnectionManager:
37
  return None
38
 
39
  self.active_connections.append(websocket)
40
- await self._update_redis_status(user["user_id"], "active")
 
 
 
 
41
  return user
42
 
43
- async def disconnect(self, websocket: WebSocket, user_id: str):
 
 
44
  if websocket in self.active_connections:
45
  self.active_connections.remove(websocket)
46
 
47
- await self._update_redis_status(user_id, None)
 
 
 
 
48
 
49
- async def _update_redis_status(self, user_id: str, status: str):
50
  if status:
51
  await redis_client.set(
52
- f"session:{user_id}", status, ex=REDIS_SESSION_EXPIRY
53
  )
54
  else:
55
- await redis_client.delete(f"session:{user_id}")
56
 
57
- redis_status = await redis_client.get(f"session:{user_id}")
58
- logger.info(f"Redis status for user {user_id}: {redis_status}")
59
 
60
 
61
  class ConversationController:
62
  def __init__(self):
63
  self.websocket_connection_manager = ConnectionManager()
 
64
  self.service = ConversationService
65
  self.api_router = APIRouter()
66
  self.api_router.add_api_route(
@@ -70,7 +81,7 @@ class ConversationController:
70
  response_model=CreateConversationResponse,
71
  )
72
  self.api_router.add_api_route(
73
- "/conversations/{conversation_id}/webrtc",
74
  self.create_webrtc_connection,
75
  methods=["POST"],
76
  response_model=CreateWebrtcConnectionResponse,
@@ -92,6 +103,9 @@ class ConversationController:
92
  return await service.create_conversation(
93
  user_id=user_id, modality=data.modality
94
  )
 
 
 
95
  except Exception as e:
96
  logger.error(f"Error creating conversation: {str(e)}")
97
  raise HTTPException(status_code=500, detail="Failed to create conversation")
@@ -109,6 +123,9 @@ class ConversationController:
109
  conversation_id=data.conversation_id,
110
  offer=data.offer.model_dump(),
111
  )
 
 
 
112
  except Exception as e:
113
  logger.error(f"Error creating WebRTC connection: {str(e)}")
114
  raise HTTPException(
@@ -116,7 +133,9 @@ class ConversationController:
116
  )
117
 
118
  async def conversation(self, websocket: WebSocket):
119
- user = await self.websocket_connection_manager.connect(websocket)
 
 
120
  if not user:
121
  return
122
  user_id = user["user_id"]
@@ -129,15 +148,28 @@ class ConversationController:
129
  user_id=user_id,
130
  conversation_id=conversation_id,
131
  modality=modality,
132
- redis_client=redis_client,
133
  )
134
  except WebSocketDisconnect:
135
- await self.websocket_connection_manager.disconnect(websocket, user_id)
 
 
 
 
 
136
  logger.info("WebSocket connection closed")
137
 
 
 
 
138
  except Exception as e:
139
  logger.error(f"Error in WebSocket conversation: {str(e)}")
140
- await self.websocket_connection_manager.disconnect(websocket, user_id)
 
 
 
 
 
141
 
142
  async def create_conversation_summary(
143
  self, request: Request, data: CreateConversationSummarySchema
@@ -148,6 +180,10 @@ class ConversationController:
148
  return await service.create_conversation_summary(
149
  user_id=user_id, conversation_id=data.conversation_id
150
  )
 
 
 
 
151
  except Exception as e:
152
  logger.error(f"Error creating conversation summary: {str(e)}")
153
  raise HTTPException(
 
1
+ import os
2
  from fastapi import (
3
  APIRouter,
4
  HTTPException,
 
9
  from typing import List
10
 
11
  from src.config import logger
 
12
  from src.services import ConversationService
13
  from src.schemas import (
14
  CreateConversationSchema,
 
18
  CreateConversationSummaryResponse,
19
  CreateWebrtcConnectionResponse,
20
  )
21
+ from src.utils import JWTUtil, RedisClient
22
 
23
 
24
  class ConnectionManager:
 
26
  self.jwt = JWTUtil()
27
  self.active_connections: List[WebSocket] = []
28
 
29
+ async def connect(self, websocket: WebSocket, redis_client):
30
  await websocket.accept()
31
  token = websocket.query_params.get("token")
32
+ conversation_id = websocket.query_params.get("conversation_id")
33
  user = self.jwt.validate_jwt(token)
34
 
35
  if not user:
 
37
  return None
38
 
39
  self.active_connections.append(websocket)
40
+ await self._update_redis_status(
41
+ unique_id=f"{user["user_id"]}{conversation_id}",
42
+ redis_client=redis_client,
43
+ status="active",
44
+ )
45
  return user
46
 
47
+ async def disconnect(
48
+ self, websocket: WebSocket, redis_client, user_id: str, conversation_id: str
49
+ ):
50
  if websocket in self.active_connections:
51
  self.active_connections.remove(websocket)
52
 
53
+ await self._update_redis_status(
54
+ unique_id=f"{user_id}{conversation_id}",
55
+ redis_client=redis_client,
56
+ status=None,
57
+ )
58
 
59
+ async def _update_redis_status(self, unique_id: str, redis_client, status: str):
60
  if status:
61
  await redis_client.set(
62
+ f"session:{unique_id}", status, ex=os.getenv("REDIS_SESSION_EXPIRY")
63
  )
64
  else:
65
+ await redis_client.delete(f"session:{unique_id}")
66
 
67
+ redis_status = await redis_client.get(f"session:{unique_id}")
68
+ logger.info(f"Redis status for user {unique_id}: {redis_status}")
69
 
70
 
71
  class ConversationController:
72
  def __init__(self):
73
  self.websocket_connection_manager = ConnectionManager()
74
+ self.redis_client = RedisClient().client
75
  self.service = ConversationService
76
  self.api_router = APIRouter()
77
  self.api_router.add_api_route(
 
81
  response_model=CreateConversationResponse,
82
  )
83
  self.api_router.add_api_route(
84
+ "/conversations/{conversation_id}",
85
  self.create_webrtc_connection,
86
  methods=["POST"],
87
  response_model=CreateWebrtcConnectionResponse,
 
103
  return await service.create_conversation(
104
  user_id=user_id, modality=data.modality
105
  )
106
+ except HTTPException as e:
107
+ raise
108
+
109
  except Exception as e:
110
  logger.error(f"Error creating conversation: {str(e)}")
111
  raise HTTPException(status_code=500, detail="Failed to create conversation")
 
123
  conversation_id=data.conversation_id,
124
  offer=data.offer.model_dump(),
125
  )
126
+ except HTTPException as e:
127
+ raise
128
+
129
  except Exception as e:
130
  logger.error(f"Error creating WebRTC connection: {str(e)}")
131
  raise HTTPException(
 
133
  )
134
 
135
  async def conversation(self, websocket: WebSocket):
136
+ user = await self.websocket_connection_manager.connect(
137
+ websocket=websocket, redis_client=self.redis_client
138
+ )
139
  if not user:
140
  return
141
  user_id = user["user_id"]
 
148
  user_id=user_id,
149
  conversation_id=conversation_id,
150
  modality=modality,
151
+ redis_client=self.redis_client,
152
  )
153
  except WebSocketDisconnect:
154
+ await self.websocket_connection_manager.disconnect(
155
+ websocket=websocket,
156
+ user_id=user_id,
157
+ redis_client=self.redis_client,
158
+ conversation_id=conversation_id,
159
+ )
160
  logger.info("WebSocket connection closed")
161
 
162
+ except HTTPException as e:
163
+ raise
164
+
165
  except Exception as e:
166
  logger.error(f"Error in WebSocket conversation: {str(e)}")
167
+ await self.websocket_connection_manager.disconnect(
168
+ websocket=websocket,
169
+ user_id=user_id,
170
+ redis_client=self.redis_client,
171
+ conversation_id=conversation_id,
172
+ )
173
 
174
  async def create_conversation_summary(
175
  self, request: Request, data: CreateConversationSummarySchema
 
180
  return await service.create_conversation_summary(
181
  user_id=user_id, conversation_id=data.conversation_id
182
  )
183
+
184
+ except HTTPException as e:
185
+ raise
186
+
187
  except Exception as e:
188
  logger.error(f"Error creating conversation summary: {str(e)}")
189
  raise HTTPException(
src/controllers/_file_controller.py CHANGED
@@ -2,7 +2,7 @@ from fastapi import APIRouter, HTTPException, Request
2
 
3
  from src.config import logger
4
  from src.services import FileService
5
- from src.schemas import InsertFileSchema, SemanticSearchSchema
6
 
7
 
8
  class FileController:
@@ -10,9 +10,6 @@ class FileController:
10
  self.service = FileService
11
  self.api_router = APIRouter()
12
  self.api_router.add_api_route("/files", self.insert_file, methods=["POST"])
13
- self.api_router.add_api_route(
14
- "/files/search", self.semantic_search, methods=["GET"]
15
- )
16
 
17
  async def insert_file(self, request: Request, data: InsertFileSchema):
18
  try:
@@ -24,12 +21,3 @@ class FileController:
24
  except Exception as e:
25
  logger.error(f"Error while inserting file: {str(e)}")
26
  raise HTTPException(status_code=500, detail=str(e))
27
-
28
- async def semantic_search(self, request: Request, data: SemanticSearchSchema):
29
- try:
30
- query = data.query
31
- async with self.service() as service:
32
- return await service.semantic_search(query=query)
33
- except Exception as e:
34
- logger.error(f"Error while searching for similar data: {str(e)}")
35
- raise HTTPException(status_code=500, detail=str(e))
 
2
 
3
  from src.config import logger
4
  from src.services import FileService
5
+ from src.schemas import InsertFileSchema
6
 
7
 
8
  class FileController:
 
10
  self.service = FileService
11
  self.api_router = APIRouter()
12
  self.api_router.add_api_route("/files", self.insert_file, methods=["POST"])
 
 
 
13
 
14
  async def insert_file(self, request: Request, data: InsertFileSchema):
15
  try:
 
21
  except Exception as e:
22
  logger.error(f"Error while inserting file: {str(e)}")
23
  raise HTTPException(status_code=500, detail=str(e))
 
 
 
 
 
 
 
 
 
src/controllers/_meeting_controller.py DELETED
@@ -1,67 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Request
2
-
3
- from src.config import logger
4
- from src.schemas import DemoBookingSchema
5
- from src.services import MeetingService
6
-
7
-
8
- class MeetingController:
9
- def __init__(self):
10
- self.service = MeetingService
11
- self.api_router = APIRouter()
12
- self.api_router.add_api_route(
13
- "/meetings/slots",
14
- self.get_available_slots,
15
- methods=["GET"],
16
- )
17
- self.api_router.add_api_route(
18
- "/meetings/book",
19
- self.schedule_meeting,
20
- methods=["POST"],
21
- )
22
- self.api_router.add_api_route(
23
- "/meetings/confirm",
24
- self.send_meeting_confirmation,
25
- methods=["POST"],
26
- )
27
-
28
- async def get_available_slots(
29
- self, request: Request, salesperson_meeting_id: str, timezone: str
30
- ):
31
- try:
32
- async with self.service() as service:
33
- return await service.get_available_slots(
34
- salesperson_meeting_id, timezone
35
- )
36
- except Exception as e:
37
- logger.error(f"Error getting available slots: {str(e)}")
38
- raise HTTPException(status_code=500, detail="Failed to get available slots")
39
-
40
- async def schedule_meeting(self, request: Request, data: DemoBookingSchema):
41
- try:
42
- user_id = request.state.user["user_id"]
43
- async with self.service() as service:
44
- return await service.schedule_meeting(
45
- user_id=user_id,
46
- salesperson_meeting_id=data.salesperson_meeting_id,
47
- duration=data.duration,
48
- preferred_start_time=data.preferred_start_time,
49
- timezone=data.timezone,
50
- locale=data.locale,
51
- )
52
- except Exception as e:
53
- logger.error(f"Error booking demo: {str(e)}")
54
- raise HTTPException(
55
- status_code=500, detail=f"Failed to book demo: {str(e)}"
56
- )
57
-
58
- async def send_meeting_confirmation(self, request: Request):
59
- try:
60
- user_id = request.state.user["user_id"]
61
- async with self.service() as service:
62
- return await service.send_meeting_confirmation(user_id=user_id)
63
- except Exception as e:
64
- logger.error(f"Error sending meeting confirmation: {str(e)}")
65
- raise HTTPException(
66
- status_code=500, detail="Failed to send meeting confirmation"
67
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/middlewares/_authentication.py CHANGED
@@ -39,31 +39,29 @@ class AuthenticationMiddleware(BaseHTTPMiddleware):
39
  except HTTPException as e:
40
  # Create a response with CORS headers
41
  response = JSONResponse(
42
- status_code=e.status_code,
43
- content={"detail": e.detail}
44
  )
45
-
46
  origin = request.headers.get("origin", "*")
47
  response.headers["Access-Control-Allow-Origin"] = origin
48
  response.headers["Access-Control-Allow-Credentials"] = "true"
49
  response.headers["Access-Control-Allow-Methods"] = "*"
50
  response.headers["Access-Control-Allow-Headers"] = "*"
51
-
52
  return response
53
  except Exception as e:
54
  response = JSONResponse(
55
- status_code=500,
56
- content={"detail": "Internal server error"}
57
  )
58
-
59
  origin = request.headers.get("origin", "*")
60
  response.headers["Access-Control-Allow-Origin"] = origin
61
  response.headers["Access-Control-Allow-Credentials"] = "true"
62
  response.headers["Access-Control-Allow-Methods"] = "*"
63
  response.headers["Access-Control-Allow-Headers"] = "*"
64
-
65
  return response
66
-
67
  return await call_next(request)
68
 
69
  def _require_auth(self, path: str):
 
39
  except HTTPException as e:
40
  # Create a response with CORS headers
41
  response = JSONResponse(
42
+ status_code=e.status_code, content={"detail": e.detail}
 
43
  )
44
+
45
  origin = request.headers.get("origin", "*")
46
  response.headers["Access-Control-Allow-Origin"] = origin
47
  response.headers["Access-Control-Allow-Credentials"] = "true"
48
  response.headers["Access-Control-Allow-Methods"] = "*"
49
  response.headers["Access-Control-Allow-Headers"] = "*"
50
+
51
  return response
52
  except Exception as e:
53
  response = JSONResponse(
54
+ status_code=500, content={"detail": "Internal server error"}
 
55
  )
56
+
57
  origin = request.headers.get("origin", "*")
58
  response.headers["Access-Control-Allow-Origin"] = origin
59
  response.headers["Access-Control-Allow-Credentials"] = "true"
60
  response.headers["Access-Control-Allow-Methods"] = "*"
61
  response.headers["Access-Control-Allow-Headers"] = "*"
62
+
63
  return response
64
+
65
  return await call_next(request)
66
 
67
  def _require_auth(self, path: str):
src/models/_sessions.py CHANGED
@@ -8,8 +8,8 @@ class Session(Document):
8
  user_id: Link[User]
9
  token: str
10
  device_ipv4_address: str
11
- created_at: datetime = Field(default_factory=datetime.now)
12
- expired_at: datetime = Field(default_factory=datetime.now)
13
 
14
  class Settings:
15
  name = "sessions"
 
8
  user_id: Link[User]
9
  token: str
10
  device_ipv4_address: str
11
+ created_at: datetime = Field(default_factory=datetime.now)
12
+ expired_at: datetime = Field(default_factory=datetime.now)
13
 
14
  class Settings:
15
  name = "sessions"
src/models/_users.py CHANGED
@@ -9,9 +9,9 @@ class User(Document):
9
  first_name: str
10
  last_name: str
11
  hashed_password: str
12
- email: Annotated[ str, Indexed(unique=True)]
13
  created_at: datetime = Field(default_factory=datetime.now)
14
  updated_at: datetime = Field(default_factory=datetime.now)
15
 
16
  class Settings:
17
- name = "users"
 
9
  first_name: str
10
  last_name: str
11
  hashed_password: str
12
+ email: Annotated[str, Indexed(unique=True)]
13
  created_at: datetime = Field(default_factory=datetime.now)
14
  updated_at: datetime = Field(default_factory=datetime.now)
15
 
16
  class Settings:
17
+ name = "users"
src/prompts/system_prompt.md CHANGED
@@ -1,4 +1,4 @@
1
- # System Prompt: Olivia - Your Friendly and Knowledgeable Assistant
2
 
3
  You are Olivia, a dynamic and supportive virtual assistant, here to deliver personalized, context-aware interactions. Below are key attributes to guide your behavior and tone:
4
 
@@ -52,7 +52,7 @@ Always use **get_relevant_information** if the user query relates to **Keepme Sa
52
 
53
  - **Description:** Get relevant information (Context) to better respond to user queries.
54
  - **Parameters:**
55
- - `query` (string): The user query to get relevant information for.
56
 
57
  #### **get_available_salesperson_meetings**
58
 
@@ -62,16 +62,16 @@ Always use **get_relevant_information** if the user query relates to **Keepme Sa
62
 
63
  - **Description:** Get available slots for a salesperson meeting.
64
  - **Parameters:**
65
- - `salesperson_meeting_id` (string): The ID of the salesperson meeting.
66
- - `timezone` (string): The timezone for the available slots.
67
 
68
  #### **schedule_meeting**
69
 
70
  - **Description:** Schedule a meeting.
71
  - **Parameters:**
72
- - `user_id` (string): The ID of the user.
73
- - `salesperson_meeting_id` (string): The ID of the salesperson meeting.
74
- - `preferred_start_time` (string, format: date-time): The preferred start time for the meeting.
75
- - `duration` (integer): The duration of the meeting in minutes.
76
- - `timezone` (string): The timezone for the meeting.
77
- - `locale` (string, default: "en-us"): The locale for the meeting.
 
1
+ # Olivia - Your Friendly and Knowledgeable Assistant
2
 
3
  You are Olivia, a dynamic and supportive virtual assistant, here to deliver personalized, context-aware interactions. Below are key attributes to guide your behavior and tone:
4
 
 
52
 
53
  - **Description:** Get relevant information (Context) to better respond to user queries.
54
  - **Parameters:**
55
+ - `query` (string): The user query to get relevant information for.
56
 
57
  #### **get_available_salesperson_meetings**
58
 
 
62
 
63
  - **Description:** Get available slots for a salesperson meeting.
64
  - **Parameters:**
65
+ - `salesperson_meeting_id` (string): The ID of the salesperson meeting.
66
+ - `timezone` (string): The timezone for the available slots.
67
 
68
  #### **schedule_meeting**
69
 
70
  - **Description:** Schedule a meeting.
71
  - **Parameters:**
72
+ - `user_id` (string): The ID of the user.
73
+ - `salesperson_meeting_id` (string): The ID of the salesperson meeting.
74
+ - `preferred_start_time` (string, format: date-time): The preferred start time for the meeting.
75
+ - `duration` (integer): The duration of the meeting in minutes.
76
+ - `timezone` (string): The timezone for the meeting.
77
+ - `locale` (string, default: "en-us"): The locale for the meeting.
src/repositories/_base_repository.py CHANGED
@@ -1,5 +1,5 @@
1
  from beanie import Document, WriteRules, DeleteRules
2
- from typing import Type, TypeVar, List, Dict
3
 
4
  T = TypeVar("T", bound=Document)
5
 
 
1
  from beanie import Document, WriteRules, DeleteRules
2
+ from typing import Type, TypeVar, Dict
3
 
4
  T = TypeVar("T", bound=Document)
5
 
src/schemas/__init__.py CHANGED
@@ -15,7 +15,7 @@ from ._conversations import (
15
  CreateWebrtcConnectionResponse,
16
  )
17
  from ._meetings import DemoBookingSchema
18
- from ._files import InsertFileSchema, SemanticSearchSchema
19
 
20
 
21
  __all__ = [
@@ -32,7 +32,6 @@ __all__ = [
32
  "CreateConversationSummaryResponse",
33
  "CreateWebrtcConnectionResponse",
34
  "InsertFileSchema",
35
- "SemanticSearchSchema",
36
  "DemoBookingSchema",
37
  ]
38
 
 
15
  CreateWebrtcConnectionResponse,
16
  )
17
  from ._meetings import DemoBookingSchema
18
+ from ._files import InsertFileSchema
19
 
20
 
21
  __all__ = [
 
32
  "CreateConversationSummaryResponse",
33
  "CreateWebrtcConnectionResponse",
34
  "InsertFileSchema",
 
35
  "DemoBookingSchema",
36
  ]
37
 
src/schemas/_files.py CHANGED
@@ -4,7 +4,3 @@ from fastapi import UploadFile, File
4
 
5
  class InsertFileSchema(BaseModel):
6
  file: UploadFile = File(...)
7
-
8
-
9
- class SemanticSearchSchema(BaseModel):
10
- query: str
 
4
 
5
  class InsertFileSchema(BaseModel):
6
  file: UploadFile = File(...)
 
 
 
 
src/schemas/_sessions.py CHANGED
@@ -3,6 +3,7 @@ from datetime import datetime
3
  from pydantic import BaseModel
4
  from src.models import User
5
 
 
6
  class SessionCreateSchema(BaseModel):
7
  user_id: User
8
  token: str
 
3
  from pydantic import BaseModel
4
  from src.models import User
5
 
6
+
7
  class SessionCreateSchema(BaseModel):
8
  user_id: User
9
  token: str
src/schemas/_users.py CHANGED
@@ -35,5 +35,6 @@ class UserSignInSchema(BaseModel):
35
  class UserSignInResponse(BaseModel):
36
  token: str
37
 
 
38
  class UserSignUpResponse(BaseModel):
39
  user_id: str
 
35
  class UserSignInResponse(BaseModel):
36
  token: str
37
 
38
+
39
  class UserSignUpResponse(BaseModel):
40
  user_id: str
src/services/_meeting_service.py CHANGED
@@ -20,7 +20,9 @@ class MeetingService:
20
  pass
21
 
22
  async def get_available_salesperson_meetings(self):
23
- request_url = "https://api.hubapi.com/scheduler/v3/meetings/meeting-links"
 
 
24
  headers = {"authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}"}
25
 
26
  async with aiohttp.ClientSession() as session:
@@ -44,7 +46,10 @@ class MeetingService:
44
  return result
45
 
46
  async def get_available_slots(self, salesperson_meeting_id: str, timezone: str):
47
- request_url = f"https://api.hubapi.com/scheduler/v3/meetings/meeting-links/book/availability-page/{salesperson_meeting_id}?timezone={timezone}"
 
 
 
48
  headers = {"authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}"}
49
 
50
  async with aiohttp.ClientSession() as session:
@@ -98,7 +103,9 @@ class MeetingService:
98
 
99
  duration_ms = duration * 60000
100
 
101
- url = "https://api.hubapi.com/scheduler/v3/meetings/meeting-links/book"
 
 
102
  headers = {
103
  "authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}",
104
  "content-type": "application/json",
 
20
  pass
21
 
22
  async def get_available_salesperson_meetings(self):
23
+ request_url = (
24
+ os.getenv("HUBSPOT_BASE_URL") + "/scheduler/v3/meetings/meeting-links"
25
+ )
26
  headers = {"authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}"}
27
 
28
  async with aiohttp.ClientSession() as session:
 
46
  return result
47
 
48
  async def get_available_slots(self, salesperson_meeting_id: str, timezone: str):
49
+ request_url = (
50
+ os.getenv("HUBSPOT_BASE_URL")
51
+ + "/scheduler/v3/meetings/meeting-links/book/availability-page/{salesperson_meeting_id}?timezone={timezone}"
52
+ )
53
  headers = {"authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}"}
54
 
55
  async with aiohttp.ClientSession() as session:
 
103
 
104
  duration_ms = duration * 60000
105
 
106
+ url = (
107
+ os.getenv("HUBSPOT_BASE_URL") + "/scheduler/v3/meetings/meeting-links/book"
108
+ )
109
  headers = {
110
  "authorization": f"Bearer {os.getenv('HUBSPOT_API_KEY')}",
111
  "content-type": "application/json",
src/services/_session_service.py CHANGED
@@ -25,7 +25,7 @@ class SessionService:
25
 
26
  async def delete_session(self, session_id):
27
  return await self.session_repository.delete(session_id)
28
-
29
  async def get_session_by_token(self, token: str):
30
  session = await self.session_repository.get_all(filter_by={"token": token})
31
  return session[0] if session else None
 
25
 
26
  async def delete_session(self, session_id):
27
  return await self.session_repository.delete(session_id)
28
+
29
  async def get_session_by_token(self, token: str):
30
  session = await self.session_repository.get_all(filter_by={"token": token})
31
  return session[0] if session else None
src/services/_web_rtc_service.py CHANGED
@@ -24,7 +24,7 @@ class WebRTCService:
24
  data = await websocket.receive_text()
25
  message = json.loads(data)
26
 
27
- user_status = await redis_client.get(f"session:{user_id}")
28
  if not user_status:
29
  raise Exception("A max 25 minute session has ended")
30
 
 
24
  data = await websocket.receive_text()
25
  message = json.loads(data)
26
 
27
+ user_status = await redis_client.get(f"session:{user_id}{conversation_id}")
28
  if not user_status:
29
  raise Exception("A max 25 minute session has ended")
30
 
src/services/_websocket_service.py CHANGED
@@ -30,7 +30,11 @@ class WebSocketService:
30
  ):
31
  openai_websocket = None
32
  async with websockets.connect(
33
- uri="wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview-2024-12-17",
 
 
 
 
34
  additional_headers={
35
  "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
36
  "OpenAI-Beta": "realtime=v1",
@@ -122,7 +126,9 @@ class WebSocketService:
122
  user_message = await client_websocket.receive_text()
123
  user_message = json.loads(user_message)["user_message"]
124
 
125
- user_status = await redis_client.get(f"session:{user_id}")
 
 
126
  if not user_status:
127
  raise Exception("A max 25 minute session has ended")
128
 
 
30
  ):
31
  openai_websocket = None
32
  async with websockets.connect(
33
+ uri=(
34
+ os.getenv("OPENAI_WS_BASE_URL")
35
+ + "/v1/realtime?model="
36
+ + os.getenv("OPENAI_REALTIME_MODEL")
37
+ ),
38
  additional_headers={
39
  "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
40
  "OpenAI-Beta": "realtime=v1",
 
126
  user_message = await client_websocket.receive_text()
127
  user_message = json.loads(user_message)["user_message"]
128
 
129
+ user_status = await redis_client.get(
130
+ f"session:{user_id}{conversation_id}"
131
+ )
132
  if not user_status:
133
  raise Exception("A max 25 minute session has ended")
134
 
src/utils/__init__.py CHANGED
@@ -3,6 +3,7 @@ from ._bcrypt_util import BcryptUtil
3
  from ._openai_client import OpenAIClient
4
  from ._pinecone_client import PineconeClient
5
  from ._meeting_utils import MeetingUtils
 
6
 
7
  __all__ = [
8
  "JWTUtil",
@@ -10,6 +11,7 @@ __all__ = [
10
  "OpenAIClient",
11
  "PineconeClient",
12
  "MeetingUtils",
 
13
  ]
14
 
15
  __version__ = "0.1.0"
 
3
  from ._openai_client import OpenAIClient
4
  from ._pinecone_client import PineconeClient
5
  from ._meeting_utils import MeetingUtils
6
+ from ._redis_client import RedisClient
7
 
8
  __all__ = [
9
  "JWTUtil",
 
11
  "OpenAIClient",
12
  "PineconeClient",
13
  "MeetingUtils",
14
+ "RedisClient",
15
  ]
16
 
17
  __version__ = "0.1.0"
src/utils/_bcrypt_util.py CHANGED
@@ -6,12 +6,12 @@ class BcryptUtil:
6
  def hash_password(password: str) -> str:
7
 
8
  salt = bcrypt.gensalt()
9
- hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
10
- return hashed_password.decode('utf-8')
11
 
12
  @staticmethod
13
  def compare_password(stored_password: str, provided_password: str) -> bool:
14
 
15
- stored_password = stored_password.encode('utf-8')
16
- provided_password = provided_password.encode('utf-8')
17
  return bcrypt.checkpw(provided_password, stored_password)
 
6
  def hash_password(password: str) -> str:
7
 
8
  salt = bcrypt.gensalt()
9
+ hashed_password = bcrypt.hashpw(password.encode("utf-8"), salt)
10
+ return hashed_password.decode("utf-8")
11
 
12
  @staticmethod
13
  def compare_password(stored_password: str, provided_password: str) -> bool:
14
 
15
+ stored_password = stored_password.encode("utf-8")
16
+ provided_password = provided_password.encode("utf-8")
17
  return bcrypt.checkpw(provided_password, stored_password)
src/utils/_jwt_util.py CHANGED
@@ -2,19 +2,20 @@ import os
2
  import jwt
3
  from datetime import datetime, timedelta
4
 
 
5
  class JWTUtil:
6
- def __init__(self, algorithm='HS256'):
7
- self.secret_key = os.getenv('JWT_SECRET_KEY')
8
- self.algorithm = algorithm
9
 
10
- def generate_jwt(self, payload, expiration_minutes=60):
11
- payload['exp'] = datetime.now() + timedelta(minutes=expiration_minutes)
12
- return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
13
 
14
- def validate_jwt(self, token):
15
- try:
16
- return jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
17
- except jwt.ExpiredSignatureError:
18
- return None
19
- except jwt.InvalidTokenError:
20
- return None
 
2
  import jwt
3
  from datetime import datetime, timedelta
4
 
5
+
6
  class JWTUtil:
7
+ def __init__(self, algorithm="HS256"):
8
+ self.secret_key = os.getenv("JWT_SECRET_KEY")
9
+ self.algorithm = algorithm
10
 
11
+ def generate_jwt(self, payload, expiration_minutes=60):
12
+ payload["exp"] = datetime.now() + timedelta(minutes=expiration_minutes)
13
+ return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
14
 
15
+ def validate_jwt(self, token):
16
+ try:
17
+ return jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
18
+ except jwt.ExpiredSignatureError:
19
+ return None
20
+ except jwt.InvalidTokenError:
21
+ return None
src/utils/_openai_client.py CHANGED
@@ -5,102 +5,17 @@ from aiortc import RTCPeerConnection, RTCSessionDescription
5
  from openai import AsyncOpenAI
6
 
7
  from src.config import logger
8
-
9
-
10
- _OPENAI_TOOLS = [
11
- {
12
- "type": "function",
13
- "name": "get_relevant_information",
14
- "description": "Get relevant information(Context) to better respond to user queries",
15
- "parameters": {
16
- "type": "object",
17
- "properties": {
18
- "query": {
19
- "type": "string",
20
- "description": "The user query to get relevant information for",
21
- },
22
- },
23
- "required": ["query"],
24
- },
25
- },
26
- {
27
- "type": "function",
28
- "name": "get_available_salesperson_meetings",
29
- "description": "Get all the salespersons ids that can be used to check available slots and to schedule meetings",
30
- },
31
- {
32
- "type": "function",
33
- "name": "get_available_slots",
34
- "description": "Get available slots for a salesperson meeting",
35
- "parameters": {
36
- "type": "object",
37
- "properties": {
38
- "salesperson_meeting_id": {
39
- "type": "string",
40
- "description": "The ID of the salesperson meeting",
41
- },
42
- "timezone": {
43
- "type": "string",
44
- "description": "The timezone for the available slots. It must be a tz identifier like America/New_York, Asia/Kolkata, Cuba, etc.Note: We have regional calenders for APAC, EMEA, and North America. Please use the timezone accordingly. The region must be asked explicitly from the user and hence confirmed too.",
45
- },
46
- },
47
- "required": ["salesperson_meeting_id", "timezone"],
48
- },
49
- },
50
- {
51
- "type": "function",
52
- "name": "schedule_meeting",
53
- "description": "Schedule a meeting",
54
- "parameters": {
55
- "type": "object",
56
- "properties": {
57
- "user_id": {
58
- "type": "string",
59
- "description": "The ID of the user, default: DEFAULT_USER, Do not explicitly ask for this",
60
- "default": "DEFAULT_USER",
61
- },
62
- "salesperson_meeting_id": {
63
- "type": "string",
64
- "description": "The ID of the salesperson meeting, this can be obtained from get_available_salesperson_meetings",
65
- },
66
- "preferred_start_time": {
67
- "type": "string",
68
- "format": "date-time",
69
- "description": "The preferred start time for the meeting. format: YYYY-MM-DD HH:MM:SS, this can be obtained from get_available_slots",
70
- "example": "2022-12-17 12:00:00",
71
- },
72
- "duration": {
73
- "type": "integer",
74
- "description": "The duration of the meeting in minutes",
75
- },
76
- "timezone": {
77
- "type": "string",
78
- "description": "The timezone for the available slots. It must be a tz identifier like America/New_York, Asia/Kolkata, Cuba, etc.Note: We have regional calenders for APAC, EMEA, and North America. Please use the timezone accordingly. The region must be asked explicitly from the user and hence confirmed too.",
79
- },
80
- "locale": {
81
- "type": "string",
82
- "description": "The locale for the meeting",
83
- "default": "en-us",
84
- },
85
- },
86
- "required": [
87
- "user_id",
88
- "salesperson_meeting_id",
89
- "preferred_start_time",
90
- "duration",
91
- "timezone",
92
- ],
93
- },
94
- },
95
- ]
96
 
97
 
98
  class OpenAIClient:
99
  def __init__(self):
100
  self.client = None
101
- self.session_url = "https://api.openai.com/v1/realtime/sessions"
102
- self.model = "gpt-4o-mini-realtime-preview-2024-12-17"
103
- self.webrtc_url = f"https://api.openai.com/v1/realtime?model={self.model}"
 
 
104
  self.modalities = ["text", "audio"]
105
  self.voice = "alloy"
106
  self.transcription_model = "whisper-1"
@@ -126,7 +41,7 @@ class OpenAIClient:
126
 
127
  async def text_generation(self, query: str):
128
  completion = await self.client.chat.completions.create(
129
- model="gpt-4o-mini-2024-07-18",
130
  messages=[
131
  {
132
  "role": "user",
 
5
  from openai import AsyncOpenAI
6
 
7
  from src.config import logger
8
+ from ._openai_tools import _OPENAI_TOOLS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
 
11
  class OpenAIClient:
12
  def __init__(self):
13
  self.client = None
14
+ self.model = os.getenv("OPENAI_REALTIME_MODEL")
15
+ self.session_url = os.getenv("OPENAI_BASE_URL") + "/v1/realtime/sessions"
16
+ self.webrtc_url = (
17
+ os.getenv("OPENAI_BASE_URL") + "/v1/realtime?model={self.model}"
18
+ )
19
  self.modalities = ["text", "audio"]
20
  self.voice = "alloy"
21
  self.transcription_model = "whisper-1"
 
41
 
42
  async def text_generation(self, query: str):
43
  completion = await self.client.chat.completions.create(
44
+ model=os.getenv("OPENAI_CHAT_COMPLETION_MODEL"),
45
  messages=[
46
  {
47
  "role": "user",
src/utils/_openai_tools.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _OPENAI_TOOLS = [
2
+ {
3
+ "type": "function",
4
+ "name": "get_relevant_information",
5
+ "description": "Get relevant information(Context) to better respond to user queries",
6
+ "parameters": {
7
+ "type": "object",
8
+ "properties": {
9
+ "query": {
10
+ "type": "string",
11
+ "description": "The user query to get relevant information for",
12
+ },
13
+ },
14
+ "required": ["query"],
15
+ },
16
+ },
17
+ {
18
+ "type": "function",
19
+ "name": "get_available_salesperson_meetings",
20
+ "description": "Get all the salespersons ids that can be used to check available slots and to schedule meetings",
21
+ },
22
+ {
23
+ "type": "function",
24
+ "name": "get_available_slots",
25
+ "description": "Get available slots for a salesperson meeting",
26
+ "parameters": {
27
+ "type": "object",
28
+ "properties": {
29
+ "salesperson_meeting_id": {
30
+ "type": "string",
31
+ "description": "The ID of the salesperson meeting",
32
+ },
33
+ "timezone": {
34
+ "type": "string",
35
+ "description": "The timezone for the available slots. It must be a tz identifier like America/New_York, Asia/Kolkata, Cuba, etc.Note: We have regional calenders for APAC, EMEA, and North America. Please use the timezone accordingly. The region must be asked explicitly from the user and hence confirmed too.",
36
+ },
37
+ },
38
+ "required": ["salesperson_meeting_id", "timezone"],
39
+ },
40
+ },
41
+ {
42
+ "type": "function",
43
+ "name": "schedule_meeting",
44
+ "description": "Schedule a meeting",
45
+ "parameters": {
46
+ "type": "object",
47
+ "properties": {
48
+ "user_id": {
49
+ "type": "string",
50
+ "description": "The ID of the user, default: DEFAULT_USER, Do not explicitly ask for this",
51
+ "default": "DEFAULT_USER",
52
+ },
53
+ "salesperson_meeting_id": {
54
+ "type": "string",
55
+ "description": "The ID of the salesperson meeting, this can be obtained from get_available_salesperson_meetings",
56
+ },
57
+ "preferred_start_time": {
58
+ "type": "string",
59
+ "format": "date-time",
60
+ "description": "The preferred start time for the meeting. format: YYYY-MM-DD HH:MM:SS, this can be obtained from get_available_slots",
61
+ "example": "2022-12-17 12:00:00",
62
+ },
63
+ "duration": {
64
+ "type": "integer",
65
+ "description": "The duration of the meeting in minutes",
66
+ },
67
+ "timezone": {
68
+ "type": "string",
69
+ "description": "The timezone for the available slots. It must be a tz identifier like America/New_York, Asia/Kolkata, Cuba, etc.Note: We have regional calenders for APAC, EMEA, and North America. Please use the timezone accordingly. The region must be asked explicitly from the user and hence confirmed too.",
70
+ },
71
+ "locale": {
72
+ "type": "string",
73
+ "description": "The locale for the meeting",
74
+ "default": "en-us",
75
+ },
76
+ },
77
+ "required": [
78
+ "user_id",
79
+ "salesperson_meeting_id",
80
+ "preferred_start_time",
81
+ "duration",
82
+ "timezone",
83
+ ],
84
+ },
85
+ },
86
+ ]
src/{config/_redis.py → utils/_redis_client.py} RENAMED
@@ -2,20 +2,13 @@ import os
2
  import redis.asyncio as redis
3
 
4
 
5
- REDIS_SESSION_EXPIRY = 1500
6
-
7
-
8
- class RedisConfig:
9
  def __init__(self):
10
  self.redis_url = os.getenv("REDIS_URI")
 
11
 
12
  def init_redis(self):
13
  redis_client = redis.from_url(
14
- url=self.redis_url,
15
- encoding="utf-8",
16
- decode_responses=True,
17
  )
18
  return redis_client
19
-
20
-
21
- redis_client = RedisConfig().init_redis()
 
2
  import redis.asyncio as redis
3
 
4
 
5
+ class RedisClient:
 
 
 
6
  def __init__(self):
7
  self.redis_url = os.getenv("REDIS_URI")
8
+ self.client = self.init_redis()
9
 
10
  def init_redis(self):
11
  redis_client = redis.from_url(
12
+ url=self.redis_url, encoding="utf-8", decode_responses=True
 
 
13
  )
14
  return redis_client