Skip to content

Commit 2976100

Browse files
authored
Merge pull request #51 from pattern-tech/fix/routes
Fix/routes
2 parents 9be8bb9 + 4f317bb commit 2976100

19 files changed

Lines changed: 545 additions & 153 deletions

File tree

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,158 @@
1+
from pydantic import BaseModel
12
from sqlalchemy.orm import Session
2-
from fastapi import APIRouter, Depends
3+
from fastapi import APIRouter, Depends, HTTPException, status
4+
35
from src.auth.utils.bcrypt_helper import generate_access_token
46
from src.auth.services.auth_service import (
57
AuthService,
68
LoginInput,
79
RegisterInput,
810
VerifyInput
911
)
10-
12+
from src.util.execptions import *
1113
from src.db.sql_alchemy import Database
12-
from src.util.response import global_response
14+
from src.util.response import global_response, GlobalResponse, ExceptionResponse
1315

1416
router = APIRouter(prefix="/auth")
1517
database = Database()
1618

1719
auth = AuthService()
1820

21+
# Create a module-level dependency
22+
get_db_dependency = Depends(database.get_db)
23+
24+
25+
class AuthOutput(BaseModel):
26+
access_token: str
27+
1928

2029
@router.post(
2130
"/register",
31+
response_model=GlobalResponse[AuthOutput, dict],
2232
summary="User Registration",
2333
description="Register a new user with email and password.",
24-
deprecated=True
34+
deprecated=True,
35+
responses={
36+
409: {
37+
"model": ExceptionResponse,
38+
"description": "User or Email already exists"
39+
},
40+
400: {
41+
"model": ExceptionResponse,
42+
"description": "Bad request"
43+
},
44+
}
2545
)
26-
def register(input: RegisterInput, db: Session = Depends(database.get_db)):
46+
def register(input: RegisterInput, db: Session = get_db_dependency):
2747
"""
2848
Register a new user.
2949
3050
- **email**: User's email address
3151
- **password**: User's password
3252
"""
33-
user = auth.register(input, db)
34-
35-
payload = {"id": str(user.id)}
36-
token = generate_access_token(data=payload)
37-
return global_response(
38-
{
39-
"access_token": token,
40-
}
41-
)
53+
try:
54+
user = auth.register(input, db)
55+
56+
payload = {"id": str(user.id)}
57+
token = generate_access_token(data=payload)
58+
return global_response(
59+
{
60+
"access_token": token,
61+
}
62+
)
63+
except AlreadyExistsError as e:
64+
raise HTTPException(
65+
status_code=status.HTTP_409_CONFLICT, detail=str(e))
66+
except Exception as e:
67+
raise HTTPException(
68+
status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
4269

4370

4471
@router.post(
4572
"/login",
73+
response_model=GlobalResponse[AuthOutput, dict],
4674
summary="User Login",
4775
description="Authenticate a user with email and password.",
4876
deprecated=True,
77+
responses={
78+
404: {
79+
"model": ExceptionResponse,
80+
"description": "User not found"
81+
},
82+
401: {
83+
"model": ExceptionResponse,
84+
"description": "Incorrect password"
85+
},
86+
400: {
87+
"model": ExceptionResponse,
88+
"description": "Bad request"
89+
},
90+
}
4991
)
50-
def login(input: LoginInput, db: Session = Depends(database.get_db)):
92+
def login(input: LoginInput, db: Session = get_db_dependency):
5193
"""
5294
Login a user and return an access token.
5395
5496
- **email**: User's email address
5597
- **password**: User's password
5698
"""
57-
user = auth.authenticate_user(input.email.lower(), input.password, db)
58-
59-
payload = {"id": str(user.id)}
60-
token = generate_access_token(data=payload)
61-
return global_response(
62-
{
63-
"access_token": token,
64-
}
65-
)
99+
try:
100+
user = auth.authenticate_user(input.email.lower(), input.password, db)
101+
102+
payload = {"id": str(user.id)}
103+
token = generate_access_token(data=payload)
104+
return global_response(
105+
{
106+
"access_token": token,
107+
}
108+
)
109+
except NotFoundError as e:
110+
raise HTTPException(
111+
status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
112+
except InvalidPasswordError:
113+
raise HTTPException(
114+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect password")
115+
except Exception as e:
116+
raise HTTPException(
117+
status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
66118

67119

68120
@router.post(
69121
"/verify",
122+
response_model=GlobalResponse[AuthOutput, dict],
70123
summary="Verify a Signature",
71124
description="Verify a signature according to SIWE spec",
125+
responses={
126+
400: {
127+
"model": ExceptionResponse,
128+
"description": "Bad request including invalid message or signature"
129+
},
130+
}
72131
)
73-
def verify(input: VerifyInput, db: Session = Depends(database.get_db)):
132+
def verify(input: VerifyInput, db: Session = get_db_dependency):
74133
"""
75134
Verify a signature according to SIWE spec and return an access token
76135
77136
- **message**: A SIWE message
78137
- **signature**: User's signature for the message
79138
"""
80-
81-
user = auth.verify_signature(
82-
input.message, input.signature, db)
83-
84-
payload = {"id": str(user.id)}
85-
token = generate_access_token(data=payload)
86-
return global_response(
87-
{
88-
"access_token": token,
89-
}
90-
)
139+
try:
140+
user = auth.verify_signature(
141+
input.message, input.signature, db)
142+
143+
payload = {"id": str(user.id)}
144+
token = generate_access_token(data=payload)
145+
return global_response(
146+
{
147+
"access_token": token,
148+
}
149+
)
150+
except InvalidMessageError as e:
151+
raise HTTPException(
152+
status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
153+
except InvalidSignatureError as e:
154+
raise HTTPException(
155+
status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
156+
except Exception as e:
157+
raise HTTPException(
158+
status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))

api/src/auth/services/auth_service.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from sqlalchemy.orm import Session
55
from pydantic import BaseModel, Field, EmailStr
66

7+
from src.util.execptions import *
78
from src.db.models import UserModel
89
from src.db.sql_alchemy import Database
910
from src.share.base_types import WalletAddress
@@ -80,15 +81,14 @@ def register(self, input: RegisterInput, db: Session) -> str:
8081
existing_user = db.query(UserModel).filter_by(
8182
wallet_address=input.wallet_address).first()
8283
if existing_user:
83-
raise HTTPException(status_code=400, detail="User already exists")
84+
raise AlreadyExistsError("User already exists")
8485

8586
# # Create a new user record
8687
if input.email and input.password:
8788
existing_user = db.query(UserModel).filter_by(
8889
email=input.email.lower()).first()
8990
if existing_user:
90-
raise HTTPException(
91-
status_code=400, detail="This email is already exists")
91+
raise AlreadyExistsError("This email is already exists")
9292

9393
if input.password:
9494
input.password = hash_password(input.password)
@@ -117,13 +117,11 @@ def authenticate_user(self, email: str, password: str, db: Session):
117117
user = db.query(UserModel).filter_by(email=email).first()
118118

119119
if not user:
120-
raise HTTPException(
121-
status_code=401, detail="Incorrect email or password")
120+
raise NotFoundError("User not found with this email")
122121

123122
# Verify the provided password matches the stored hash
124123
if not verify_password(password, user.password):
125-
raise HTTPException(
126-
status_code=401, detail="Incorrect email or password")
124+
raise InvalidPasswordError("Incorrect email or password")
127125

128126
return user
129127

@@ -146,11 +144,10 @@ def verify_signature(self, message: str, signature: str, db: Session):
146144
siwe_message.verify(signature)
147145
except ValueError:
148146
# ValueError is raised if message is invalid according to SIWE
149-
raise HTTPException(
150-
status_code=400, detail="The provided message is not a valid SIWE message")
147+
raise InvalidMessageError(
148+
"The provided message is not a valid SIWE message")
151149
except:
152-
raise HTTPException(
153-
status_code=401, detail="Signature is not valid")
150+
raise InvalidSignatureError("Signature is not valid")
154151

155152
# check if not exist create new user
156153
user = self.user_service.get_user_by_wallet_address(

api/src/auth/utils/bcrypt_helper.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import os
12
import bcrypt
2-
from fastapi import HTTPException
3+
34
from jose import JWTError, jwt
4-
from passlib.context import CryptContext
5-
import os
65
from dotenv import load_dotenv
6+
from passlib.context import CryptContext
7+
from src.util.execptions import JWTDecodeError
78

89
load_dotenv()
910

@@ -74,4 +75,4 @@ def decode_access_token(token: str) -> dict:
7475
else:
7576
raise JWTError("Required fields not found in token")
7677
except JWTError:
77-
raise HTTPException(status_code=401, detail="Invalid or expired token")
78+
raise JWTDecodeError("Invalid or expired token")

api/src/auth/utils/get_token.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from fastapi import Depends, HTTPException, Request
2-
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
1+
from src.util.execptions import JWTDecodeError
2+
from fastapi import Depends, HTTPException, Request, status
33
from src.auth.utils.bcrypt_helper import decode_access_token
4+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
45

56
security = HTTPBearer()
7+
get_credentials = Depends(security)
68

79

810
def get_jwt_token(request: Request):
@@ -30,26 +32,33 @@ def get_jwt_token(request: Request):
3032

3133

3234
def authenticate_user(
33-
request: Request, authorization: HTTPAuthorizationCredentials = Depends(security)
35+
request: Request,
36+
authorization: HTTPAuthorizationCredentials = get_credentials
3437
):
3538
"""
3639
Authenticate the user by decoding the JWT token and extracting the user ID.
3740
3841
Parameters:
3942
request (Request): The incoming HTTP request object.
40-
authorization (HTTPAuthorizationCredentials): The bearer token credentials,
41-
automatically provided by FastAPI dependency injection.
43+
authorization (HTTPAuthorizationCredentials): The bearer token
44+
credentials, automatically provided by FastAPI dependency injection.
4245
4346
Returns:
4447
str: The user ID extracted from the decoded JWT token.
4548
4649
Raises:
47-
HTTPException: If the token is missing or invalid.
50+
HTTPException: If the token is missing, invalid, or malformed.
4851
"""
4952
token: str = ""
5053
if authorization:
5154
token = authorization.credentials
5255
else:
5356
token = get_jwt_token(request)
54-
decode_token = decode_access_token(token)
55-
return decode_token["user_id"]
57+
try:
58+
decode_token = decode_access_token(token)
59+
return decode_token["user_id"]
60+
except JWTDecodeError as e:
61+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail=str(e))
62+
except Exception as e:
63+
# Handle any other unexpected errors with a 400 Bad Request
64+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))

api/src/conversation/repositories/conversation_repository.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from sqlalchemy.orm import Session
44

55
from src.db.models import Conversation
6+
from src.util.execptions import NotFoundError
67
from src.share.base_repository import BaseRepository
78

8-
99
class ConversationRepository(BaseRepository[Conversation]):
1010
"""
1111
Repository class for handling CRUD operations on the Conversation model.
@@ -76,7 +76,7 @@ def update(self, db_session: Session, id: UUID, conversation_data: dict, user_id
7676
"""
7777
conversation = self.get_by_id(db_session, id, user_id)
7878
if not conversation:
79-
raise Exception("Conversation not found")
79+
raise NotFoundError("Conversation not found")
8080
for key, value in conversation_data.items():
8181
setattr(conversation, key, value)
8282
db_session.commit()
@@ -97,7 +97,7 @@ def delete(self, db_session: Session, id: UUID, user_id: UUID) -> None:
9797
"""
9898
conversation = self.get_by_id(db_session, id, user_id)
9999
if not conversation:
100-
raise Exception("Conversation not found")
100+
raise NotFoundError("Conversation not found")
101101

102102
db_session.delete(conversation)
103103
db_session.commit()

0 commit comments

Comments
 (0)