Skip to content

[HAN-91]: utilidades #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .env.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Users service
LOGGING_LEVEL=
PORT=
JWT_SECRET=

# Postgres
POSTGRES_USER=
Expand All @@ -14,4 +13,10 @@ POSTGRES_SCHEMA=
# Google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=
GOOGLE_REDIRECT_URI=

# JWT System
JWT_SECRET=

# External services
SOCIAL_SERVICE_URL=
45 changes: 29 additions & 16 deletions app/controller/Users.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from service.Social import SocialService
from service.Users import UsersService


Expand All @@ -18,37 +19,49 @@ def handle_get_user(self, user_id: int):
})
)

def handle_get_all_users(self):
users = self.users_service.get_all_users()
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder({
"message": users,
"status": status.HTTP_200_OK,
})
)
def handle_get_all_users(self, ids: list | None = None):
if ids:
ids = ids[0].split(",")
ids = [x for x in ids if x.isdigit()]
users = self.users_service.get_users_by_ids(ids)
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder({
"message": users,
"status": status.HTTP_200_OK,
})
)
else:
users = self.users_service.get_all_users()
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder({
"message": users,
"status": status.HTTP_200_OK,
}),
)

def handle_create_user(self, user_data: dict):
self.users_service.create_user(user_data)
async def handle_create_user(self, user_data: dict):
result = self.users_service.create_user(user_data)
await SocialService.create_social_user(user_id=result.id)
return JSONResponse(
status_code=status.HTTP_201_CREATED,
content=jsonable_encoder({
"message": "User created successfully",
"status": status.HTTP_201_CREATED,
})
}),
)

def handle_login(self, auth_code: str):
async def handle_login(self, auth_code: str):
user, jwt_token = self.users_service.login(auth_code)
await SocialService.create_social_user(user_id=user.get("id"))
return JSONResponse(
status_code=status.HTTP_200_OK,
content=jsonable_encoder({
"message": user,
"status": status.HTTP_200_OK,
}),
headers={
"x-access-token": f"{jwt_token}"
},
headers={"x-access-token": f"{jwt_token}"},
)

def handle_update_user(self, user_id: int, update_data: dict):
Expand Down
17 changes: 17 additions & 0 deletions app/exceptions/InternalServerErrorException.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from fastapi import HTTPException, status
from typing import Optional


class InternalServerErrorExcep(HTTPException):
def __init__(
self,
microservice: Optional[str] = "User service",
detail: Optional[str] = None,
):
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
message = f"An error occurred in the {microservice}"

if detail:
message += f": {detail.lower()}"

super().__init__(status_code=status_code, detail=message)
3 changes: 1 addition & 2 deletions app/exceptions/UserException.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ class InvalidData(HTTPException):
def __init__(self):
status_code = status.HTTP_400_BAD_REQUEST
super().__init__(
status_code=status_code,
detail="Invalid user data was provided"
status_code=status_code, detail="Invalid user data was provided"
)


Expand Down
16 changes: 8 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from fastapi import FastAPI
from controller.Users import UsersController
from service.Users import UsersService
from repository.Users import UsersRepository
from schemas.Schemas import CreateUserSchema, UpdateUserSchema
from schemas.Schemas import LoginRequest

from typing import List, Annotated, Union
from fastapi import FastAPI, Query

app = FastAPI()
users_repository = UsersRepository()
Expand All @@ -23,18 +23,18 @@ def get_users(user_id: int):


@app.get("/users")
def get_all_users():
return users_controller.handle_get_all_users()
def get_all_users(ids: Annotated[Union[List[str], None], Query()] = None):
return users_controller.handle_get_all_users(ids)


@app.post("/users")
def create_user(user_data: CreateUserSchema):
return users_controller.handle_create_user(user_data.dict())
async def create_user(user_data: CreateUserSchema):
return await users_controller.handle_create_user(user_data.dict())


@app.post("/login")
def login_with_google(request: LoginRequest):
return users_controller.handle_login(request.auth_code)
async def login_with_google(request: LoginRequest):
return await users_controller.handle_login(request.auth_code)


@app.patch("/users/{user_id}")
Expand Down
5 changes: 2 additions & 3 deletions app/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

class User(Base):
__tablename__ = "users"
__table_args__ = {"schema": environ.get(
"POSTGRES_SCHEMA",
"users_service")}
__table_args__ = {"schema": environ.get("POSTGRES_SCHEMA",
"users_service")}
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, nullable=True)
email = Column(String, nullable=False)
Expand Down
17 changes: 9 additions & 8 deletions app/repository/Users.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@


class UsersRepository:
db_url = environ.get(
"DATABASE_URL").replace(
"postgres://",
"postgresql://",
1)
db_url = environ.get("DATABASE_URL").replace("postgres://",
"postgresql://",
1)

engine = create_engine(db_url)

Expand Down Expand Up @@ -43,11 +41,14 @@ def get_user_by_email(self, email: str):
user = self.session.query(User).filter_by(email=email).first()
return user.__dict__ if user else None

@withSQLExceptionsHandle()
def get_all_users(self):
def get_all_users(self, ids: list = None):
users = self.session.query(User).all()
return self.__parse_result(users)

def get_users_by_ids(self, ids: list):
users = self.session.query(User).filter(User.id.in_(ids)).all()
return self.__parse_result(users)

@withSQLExceptionsHandle()
def create_user(
self,
Expand All @@ -58,7 +59,7 @@ def create_user(
nickname: Optional[str] = None,
biography: Optional[str] = None,
location: Optional[dict] = None,
birthdate: Optional[date] = None
birthdate: Optional[date] = None,
) -> User:
user_data = {"email": email}

Expand Down
46 changes: 46 additions & 0 deletions app/service/Social.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging
from httpx import (
AsyncClient,
Response,
AsyncHTTPTransport
)
from os import environ

from exceptions.InternalServerErrorException import InternalServerErrorExcep
from exceptions.UserException import InvalidData

logger = logging.getLogger("social")
logger.setLevel("DEBUG")

SOCIAL_SERVICE_URL = environ["SOCIAL_SERVICE_URL"]

# Heroku dyno plan has a limit of 30 seconds...
# so, assign 3 retries of 10 seconds each :)
# https://devcenter.heroku.com/articles/request-timeout
NUMBER_OF_RETRIES = 3
TIMEOUT = 10


class SocialService:
@staticmethod
async def post(path: str, body: dict) -> Response:
async with AsyncClient(
transport=AsyncHTTPTransport(retries=NUMBER_OF_RETRIES),
timeout=TIMEOUT
) as client:
url = SOCIAL_SERVICE_URL + path
response = await client.post(url, json=body)
return response

@staticmethod
async def create_social_user(user_id: int):
try:
response = await SocialService.post("/social/users",
body={"id": user_id})
if response.status_code == 201:
return
else:
raise InvalidData()
except Exception as e:
print(f"Unexpected error: {e}")
raise InternalServerErrorExcep("Social service")
17 changes: 10 additions & 7 deletions app/service/Users.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ def get_user(self, user_id: int):
def get_all_users(self):
return self.user_repository.get_all_users()

def get_users_by_ids(self, ids: list):
return self.user_repository.get_users_by_ids(ids)

def create_user(self, user_data: dict):
if not self._validate_location(user_data.get("location")):
raise InvalidData()
Expand Down Expand Up @@ -74,7 +77,7 @@ def _get_access_token(self, authorization_code):
"client_secret": os.environ["GOOGLE_CLIENT_SECRET"],
"code": authorization_code,
"grant_type": "authorization_code",
"redirect_uri": os.environ["GOOGLE_REDIRECT_URI"]
"redirect_uri": os.environ["GOOGLE_REDIRECT_URI"],
}
response = requests.post(token_url, data=payload)
if response.status_code == 200:
Expand All @@ -91,18 +94,18 @@ def _get_user_info(self, access_token):
if response.status_code != 200:
raise AuthenticationError()

user_data = {'email': response.json().get("email")}
user_data = {"email": response.json().get("email")}
if response.json().get("gender") is not None:
user_data['gender'] = response.json().get("gender")
user_data["gender"] = response.json().get("gender")
if response.json().get("name") is not None:
user_data['name'] = response.json().get("name")
user_data["name"] = response.json().get("name")
if response.json().get("picture") is not None:
user_data['photo'] = response.json().get("picture")
user_data["photo"] = response.json().get("picture")
return user_data

def _validate_location(self, location):
if "lat" in location and "long" in location:
if -90 <= location["lat"] <= 90 and \
-180 <= location["long"] <= 180:
if -90 <= location["lat"] <= 90 and -180 \
<= location["long"] <= 180:
return True
return False
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ psycopg2-binary
SQLAlchemy
requests_oauthlib
pyjwt
httpx
Loading