Skip to content

Commit 66f6050

Browse files
authored
Merge pull request #47 from pattern-tech/feat/wallet-accounting
fix[api]: query usage route edited
2 parents b763f3f + c535de0 commit 66f6050

12 files changed

Lines changed: 170 additions & 90 deletions

File tree

api/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ langchain-groq==0.2.4
3232
langchain-huggingface==0.1.2
3333
sentry-sdk==2.22.0
3434
siwe==4.4.0
35+
bcrypt==4.0.1

api/src/agentflow/providers/goldrush_tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def get_wallet_activity(wallet_address: str, output_include: list[str]) -> List[
6868
def get_balance_for_address(wallet_address: str, output_include: list[str]) -> str:
6969
"""
7070
fetch the native, fungible (ERC20), and non-fungible (ERC721 & ERC1155) tokens held by an address
71+
apply decimal conversion for balance
7172
7273
Args:
7374
wallet_address (str): The wallet address to retrieve balance for.

api/src/agentflow/providers/moralis_tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def get_wallet_active_chains(wallet_address: str, output_include: list[str]) ->
5454
def get_wallet_token_balances(wallet_address: str, output_include: list[str], cursor: str = None) -> dict:
5555
"""
5656
Get token balances for a specific wallet address and their token prices in USD. (paginated)
57+
apply decimal conversion for balance
5758
5859
Args:
5960
wallet_address (str): Ethereum wallet address

api/src/auth/services/auth_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def authenticate_user(self, email: str, password: str, db: Session):
115115
"""
116116
# Fetch the user from the database using the provided email
117117
user = db.query(UserModel).filter_by(email=email).first()
118-
print(password, user.password)
118+
119119
if not user:
120120
raise HTTPException(
121121
status_code=401, detail="Incorrect email or password")

api/src/conversation/routers/playground_conversation_router.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from src.db.sql_alchemy import Database
1010
from src.util.response import global_response
1111
from src.auth.utils.get_token import authenticate_user
12+
from src.query_usage.services.query_usage_service import QueryUsageService
1213
from src.conversation.services.conversation_service import ConversationService
1314

1415
router = APIRouter(prefix="/playground/conversation")
@@ -33,6 +34,13 @@ def get_conversation_service() -> ConversationService:
3334
return ConversationService()
3435

3536

37+
def get_query_usage_service() -> QueryUsageService:
38+
"""
39+
Dependency to instantiate the QueryUsageService.
40+
"""
41+
return QueryUsageService()
42+
43+
3644
class CreateConversationInput(BaseModel):
3745
"""
3846
Schema for creating a conversation.
@@ -41,7 +49,7 @@ class CreateConversationInput(BaseModel):
4149
project_id: UUID
4250

4351
class Config:
44-
orm_mode = True
52+
from_attributes = True
4553

4654

4755
class ConversationOutput(BaseModel):
@@ -53,7 +61,7 @@ class ConversationOutput(BaseModel):
5361
project_id: UUID
5462

5563
class Config:
56-
orm_mode = True
64+
from_attributes = True
5765

5866

5967
class MessageType(str, Enum):
@@ -254,7 +262,9 @@ async def send_message(
254262
conversation_id: UUID,
255263
project_id: UUID,
256264
db: Session = Depends(get_db),
257-
service: ConversationService = Depends(get_conversation_service),
265+
query_usage_service: QueryUsageService = Depends(get_query_usage_service),
266+
conversation_service: ConversationService = Depends(
267+
get_conversation_service),
258268
user_id: UUID = Depends(authenticate_user),
259269
):
260270
"""
@@ -272,21 +282,29 @@ async def send_message(
272282
dict: A JSON response containing the complete message data if `stream` is false.
273283
"""
274284
try:
275-
service.check_user_eligibility(db, user_id)
285+
286+
max_query_allowance = query_usage_service.get_user_max_query_allowance(
287+
db, user_id)
288+
is_eligible = query_usage_service.check_user_eligibility(
289+
db, user_id, max_query_allowance)
290+
if not is_eligible:
291+
raise Exception(
292+
"You have reached your daily query limit. Please try again tomorrow or stake more to get more queries."
293+
)
276294

277295
if input.stream:
278296
return StreamingResponse(
279-
service.send_message(db,
280-
input.message,
281-
user_id,
282-
conversation_id,
283-
input.message_type,
284-
input.stream),
297+
conversation_service.send_message(db,
298+
input.message,
299+
user_id,
300+
conversation_id,
301+
input.message_type,
302+
input.stream),
285303
media_type="text/plain"
286304
)
287305
else:
288306
response = None
289-
async for item in service.send_message(
307+
async for item in conversation_service.send_message(
290308
db,
291309
input.message,
292310
user_id,

api/src/conversation/services/conversation_service.py

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from src.agentflow.agents.hub import AgentHub
1010
from src.db.models import Conversation, QueryUsage
1111
from src.user.services.user_service import UserService
12-
from src.share.staked_tokens import get_user_staked_tokens
1312
from src.agent.services.memory_service import MemoryService
1413
from src.project.services.project_service import ProjectService
1514
from src.agent.services.agent_service import RouterAgentService
@@ -138,56 +137,6 @@ def get_project_associated_with_conversation(self, db_session: Session, conversa
138137
"""
139138
return self.repository.get_project_associated_with_conversation(db_session, conversation_id)
140139

141-
def check_user_eligibility(self, db_session: Session, user_id: UUID) -> bool:
142-
"""
143-
Checks if a user is eligible to use the service by checking their payment status.
144-
145-
Args:
146-
db_session (Session): The database session.
147-
user_id (UUID): The ID of the user to check.
148-
149-
Returns:
150-
bool: True if the user is eligible, False otherwise.
151-
152-
Raises:
153-
Exception: If the user is not eligible, an exception is raised with a message explaining why.
154-
"""
155-
user = self.user_service.get_user(db_session, user_id)
156-
157-
whitelist = self.user_service.get_whitelist(db_session)
158-
159-
# check user payment
160-
for wl in whitelist:
161-
if str(user_id) == str(wl.user_id):
162-
max_allowed_query = wl.max_query
163-
break
164-
else:
165-
staked_morpheus = get_user_staked_tokens(
166-
wallet_address=user.wallet_address, provider="morpheus")
167-
168-
if staked_morpheus == 0:
169-
raise Exception(
170-
"You need to stake Morpheus tokens to use this service")
171-
172-
usage_setting = self.query_usage_service.get_usage_setting(
173-
db_session)
174-
max_allowed_query = 0
175-
for setting in usage_setting:
176-
if setting.provider == "morpheus":
177-
max_allowed_query = setting.max_query * \
178-
(int(staked_morpheus) / 1e18)
179-
180-
user_query_usage_until_previous_24h = self.query_usage_service.get_all_query_usages(
181-
db_session, user_id, "morpheus",
182-
timedelta(hours=24))
183-
184-
if len(user_query_usage_until_previous_24h) >= max_allowed_query:
185-
raise Exception(
186-
"You have reached your daily query limit. Please try again tomorrow or stake more to get more queries."
187-
)
188-
189-
return True
190-
191140
async def send_message(
192141
self,
193142
db_session: Session,

api/src/project/routers/project_router.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CreateProjectInput(BaseModel):
3939
workspace_id: UUID
4040

4141
class Config:
42-
orm_mode = True
42+
from_attributes = True
4343

4444

4545
class ProjectOutput(BaseModel):
@@ -51,7 +51,7 @@ class ProjectOutput(BaseModel):
5151
workspace_id: UUID
5252

5353
class Config:
54-
orm_mode = True
54+
from_attributes = True
5555

5656

5757
@router.post(

api/src/query_usage/routers/query_usage_router.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from uuid import UUID
2+
from datetime import timedelta
23
from typing import List, Optional
34
from sqlalchemy.orm import Session
45
from pydantic import BaseModel, Field
5-
from datetime import datetime, timedelta
66
from fastapi import APIRouter, Depends, HTTPException, status
77

88
from src.db.sql_alchemy import Database
@@ -40,7 +40,7 @@ class CreateQueryUsageInput(BaseModel):
4040
provider: str = Field(..., example="morpheus")
4141

4242
class Config:
43-
orm_mode = True
43+
from_attributes = True
4444

4545

4646
class QueryUsageOutput(BaseModel):
@@ -51,7 +51,7 @@ class QueryUsageOutput(BaseModel):
5151
provider: str = Field(..., example="morpheus")
5252

5353
class Config:
54-
orm_mode = True
54+
from_attributes = True
5555

5656

5757
@router.get(
@@ -89,29 +89,35 @@ def get_query_usage(
8989
@router.get(
9090
"",
9191
response_model=List[QueryUsageOutput],
92-
summary="List All Query Usages",
93-
description="Lists all query usage records for the authenticated user.",
94-
response_description="A list of all query usage records."
92+
summary="Get user query usage",
93+
description="Get number of used and total number of allowed query for a user",
94+
response_description="Number of used and total number of allowed query for a user"
9595
)
96-
def get_all_query_usages(
96+
def get_user_query_usages(
9797
provider: Optional[str] = None,
9898
duration: Optional[timedelta] = timedelta(hours=24),
9999
user_id: UUID = Depends(authenticate_user),
100100
db: Session = Depends(get_db),
101101
service: QueryUsageService = Depends(get_query_usage_service),
102102
):
103103
"""
104-
List all query usage records for the authenticated user.
104+
Get user query usage
105105
106106
- **provider**: Optional filter by provider.
107107
- **duration**: Optional filter by a specific datetime.
108108
- **user_id**: The authenticated user's ID.
109109
- **db**: Database session.
110110
- **service**: QueryUsage service handling business logic.
111+
- **conversation_service**: Conversation service handling business logic.
111112
112113
Returns:
113-
List[QueryUsageOutput]: A list of all the user's query usage records.
114+
dict: A dictionary containing the number of used and total number of allowed query.
114115
"""
115116
query_usages = service.get_all_query_usages(
116117
db, user_id, provider, duration)
117-
return global_response(query_usages)
118+
119+
data = {
120+
"query_usages": len(query_usages),
121+
"max_query_allowance_per_day": service.get_user_max_query_allowance(db, user_id)
122+
}
123+
return global_response(data)

api/src/query_usage/services/query_usage_service.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
from uuid import UUID
2-
from datetime import datetime, timedelta
32
from typing import List, Optional
43
from sqlalchemy.orm import Session
4+
from datetime import datetime, timedelta
55

66
from src.db.models import QueryUsage
77
from src.share.base_service import BaseService
8+
from src.user.services.user_service import UserService
9+
from src.share.staked_tokens import get_user_staked_tokens
810
from src.query_usage.repositories.query_usage_repository import QueryUsageRepository
911

1012

1113
class QueryUsageService(BaseService):
1214
def __init__(self):
1315
self.repository = QueryUsageRepository()
16+
self.user_service = UserService()
1417

1518
def create_query_usage(self, db_session: Session, query_usage: QueryUsage) -> QueryUsage:
1619
"""
@@ -65,3 +68,67 @@ def get_usage_setting(self, db_session: Session) -> List[QueryUsage]:
6568
List[UsageSetting]: A list of all usage settings.
6669
"""
6770
return self.repository.get_usage_setting(db_session)
71+
72+
def get_user_max_query_allowance(self, db_session: Session, user_id: UUID) -> int:
73+
"""
74+
Retrieves the maximum number of queries a user is allowed to make.
75+
76+
Args:
77+
db_session (Session): The database session.
78+
user_id (UUID): The ID of the user.
79+
80+
Returns:
81+
int: The maximum number of queries the user is allowed to make.
82+
83+
Raises:
84+
Exception: If the user has not staked any Morpheus tokens.
85+
"""
86+
user = self.user_service.get_user(db_session, user_id)
87+
88+
whitelist = self.user_service.get_whitelist(db_session)
89+
90+
# check user payment
91+
for wl in whitelist:
92+
if str(user_id) == str(wl.user_id):
93+
max_allowed_query = wl.max_query
94+
return max_allowed_query
95+
96+
staked_morpheus = get_user_staked_tokens(
97+
wallet_address=user.wallet_address, provider="morpheus")
98+
99+
if staked_morpheus == 0:
100+
raise Exception(
101+
"You need to stake Morpheus tokens to use this service")
102+
103+
usage_setting = self.get_usage_setting(
104+
db_session)
105+
106+
max_allowed_query = 0
107+
for setting in usage_setting:
108+
if setting.provider == "morpheus":
109+
max_allowed_query = setting.max_query * \
110+
(int(staked_morpheus) / 1e18)
111+
break
112+
113+
return max_allowed_query
114+
115+
def check_user_eligibility(self, db_session: Session, user_id: UUID, max_query_allowance: int) -> bool:
116+
"""
117+
Checks if a user is eligible to make a query based on their daily query limit.
118+
119+
Args:
120+
db_session (Session): The database session.
121+
user_id (UUID): The ID of the user.
122+
max_query_allowance (int): The number of queries the user is allowed to make.
123+
124+
Returns:
125+
bool: True if the user is eligible, False otherwise.
126+
"""
127+
user_query_usage_until_previous_24h = self.get_all_query_usages(
128+
db_session, user_id, "morpheus", timedelta(hours=24)
129+
)
130+
131+
if len(user_query_usage_until_previous_24h) >= max_query_allowance:
132+
return False
133+
134+
return True

0 commit comments

Comments
 (0)