Skip to content
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
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#Enviroment
#ENVIROMENT=production
ENVIROMENT=development

#Auth
TOKEN_EXPIRE_MINUTES=3600
MY_SECRET_KEY=bb635b7c649162d4623307e6633dfb595437bd7cefe86cc63349156cc52b212c

#Database
DB_NAME=root
DB_USER=root
DB_PASS=6UF4Kie7tzLSeThX78tqBUuptI1th8T6
DB_HOST=localhost
DB_PORT=5432

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Follow these steps to set up and run the project on your local machine:
```sh
git clone git@github.com:vcjuliocesar/pynotes-api.git
```
rename env.example by .env
**Create enviroment file**
rename .env.example by .env

**Create a Virtual Environment:**
```sh
Expand Down
4 changes: 4 additions & 0 deletions config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
settings = Settings()

class ProductionConfig:

DATABASE_URI = f"postgresql://{settings.DB_USER}:{settings.DB_PASS}@{settings.DB_HOST}/{settings.DB_NAME}"

DEBUG = False

class DevelopmentConfig:

sqlite_file_name = "../database.sqlite"

base_dir = os.path.dirname(os.path.realpath(__file__))

DATABASE_URI = f"sqlite:///{os.path.join(base_dir,sqlite_file_name)}"

DEBUG = True
3 changes: 2 additions & 1 deletion config/database.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
from sqlalchemy import create_engine
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Expand All @@ -8,11 +7,13 @@
settings = Settings()

if settings.ENVIROMENT == "production":

conf = ProductionConfig

database_url = conf.DATABASE_URI

else:

conf = DevelopmentConfig

database_url = conf.DATABASE_URI
Expand Down
14 changes: 0 additions & 14 deletions env.example

This file was deleted.

5 changes: 1 addition & 4 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from fastapi import FastAPI,HTTPException,status
from fastapi.security import HTTPBearer
from utils.jwt_manager import validate_token
from starlette.requests import Request
from fastapi import FastAPI
from config.database import engine,Base
from middlewares.error_handler import ErrorHandler
from config.config import ProductionConfig,DevelopmentConfig
Expand Down
4 changes: 4 additions & 0 deletions middlewares/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
from starlette.responses import Response

class ErrorHandler(BaseHTTPMiddleware):

def __init__(self, app: FastAPI) -> None:
super().__init__(app)

async def dispatch(self, request: Request, call_next) -> Response | JSONResponse:
try:

return await call_next(request)

except Exception as error:

return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,content={"error":str(error)})
12 changes: 11 additions & 1 deletion middlewares/jwt_bearer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,34 @@
from starlette.requests import Request
from services.user import UserService
from config.database import Session
from datetime import datetime,timedelta
from datetime import datetime

class JWTBearer(HTTPBearer):

async def __call__(self, request: Request):

credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="Ivalid Credentials")

db = Session()

auth = await super().__call__(request)

data = validate_token(auth.credentials)

expire = datetime.fromtimestamp(data['exp'])

user = UserService(db).get_user_by_email(email=data["email"])

if not user:

raise credentials_exception

if expire is None:

raise credentials_exception

if datetime.utcnow() > expire:

raise credentials_exception

return user
3 changes: 3 additions & 0 deletions models/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ class Note(Base):
__tablename__ = "notes"

id = Column(Integer,primary_key=True)

title = Column(String)

content = Column(String)

owner_id = Column(Integer,ForeignKey("users.id"))

owner = relationship("User",back_populates="notes")
3 changes: 3 additions & 0 deletions models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ class User(Base):
__tablename__ = "users"

id = Column(Integer,primary_key=True)

email = Column(String,unique=True)

password = Column(String)

is_active = Column(Boolean,default=True)

notes = relationship("Note",back_populates="owner")
21 changes: 21 additions & 0 deletions routers/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@

@note_router.get('/',tags=['Notes'],response_model=List[Note],status_code=status.HTTP_200_OK,dependencies=[Depends(JWTBearer())])
def get_notes(token:str = Depends(JWTBearer())) -> List[Note]:

db = Session()

result = NoteService(db).get_notes(token.id)

if not result:

return JSONResponse(status_code=status.HTTP_404_NOT_FOUND,content={"message":"Note not found"})

return JSONResponse(status_code=status.HTTP_200_OK,content=jsonable_encoder(result))

@note_router.get('/notes/{id}',tags=['Notes'],response_model=Note,status_code=status.HTTP_200_OK,dependencies=[Depends(JWTBearer())])
def get_note(id:int = Path(le=2000),token:str = Depends(JWTBearer())) ->Note :

db = Session()

result = NoteService(db).get_note(id,token.id)

if not result:

return JSONResponse(status_code=status.HTTP_404_NOT_FOUND,content={"message":"Note not found"})

return JSONResponse(status_code=status.HTTP_200_OK,content=jsonable_encoder(result))
Expand All @@ -32,26 +39,40 @@ def get_note(id:int = Path(le=2000),token:str = Depends(JWTBearer())) ->Note :

@note_router.post('/notes',tags=['Notes'],response_model=dict,status_code=status.HTTP_200_OK,dependencies=[Depends(JWTBearer())])
def create_note(note:NoteCreate,token:str = Depends(JWTBearer())) -> dict:

db = Session()

NoteService(db).create_note(note,token.id)

return JSONResponse(status_code=status.HTTP_201_CREATED,content={"message":"Note created successfully"})

@note_router.put('/notes',tags=['Notes'],response_model=dict,status_code=status.HTTP_200_OK,dependencies=[Depends(JWTBearer())])
def update_note(id:int,note:NoteBase,token:str = Depends(JWTBearer()))->dict:

db = Session()

result = NoteService(db).get_note(id,token.id)

if not result:

return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,content={"message":"Oops, something went wrong! Try again later."})

NoteService(db).update_note(id,note,token.id)

return JSONResponse(status_code=status.HTTP_200_OK,content={"message":"Note updated successfully"})


@note_router.delete('/notes/{id}',tags=['Notes'],response_model=dict,status_code=status.HTTP_200_OK,dependencies=[Depends(JWTBearer())])
def delete_note(id:int,token:str = Depends(JWTBearer())) -> dict:

db = Session()

result = NoteService(db).get_note(id,token.id)

if not result:

return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,content={"message":"Oops, something went wrong! Try again later."})

NoteService(db).delete_note(id,token.id)

return JSONResponse(status_code=status.HTTP_200_OK,content={"message":"Note deleted successfully"})
26 changes: 18 additions & 8 deletions routers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,30 @@

@user_router.post('/users',tags=['Auth'],response_model=User,status_code=status.HTTP_200_OK)
def create_user(user:UserCreate):

db = Session()

result = UserService(db).get_user_by_email(email=user.email)

if not result:
UserService(db).create_user(user)
return JSONResponse(status_code=status.HTTP_200_OK,content={"message":"User created"})
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,content={"message":"User already exists"})
if result:

return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,content={"message":"User already exists"})

UserService(db).create_user(user)

return JSONResponse(status_code=status.HTTP_200_OK,content={"message":"User created"})


@user_router.post('/login',tags=['Auth'],status_code=status.HTTP_200_OK)
def login(user:UserCreate):

db = Session()
result = UserService(db).get_user_by_email(email=user.email)

if result and Auth().verify_password(user.password,result.password):
token:str = create_token(user.dict())
return JSONResponse(status_code=status.HTTP_200_OK,content=token)
return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED,content={"message":"Unauthorized"})
if not (result and Auth().verify_password(user.password,result.password)):

return JSONResponse(status_code=status.HTTP_401_UNAUTHORIZED,content={"message":"Unauthorized"})

token:str = create_token(user.dict())

return JSONResponse(status_code=status.HTTP_200_OK,content=token)
6 changes: 5 additions & 1 deletion schemas/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
from typing import Optional

class NoteBase(BaseModel):
#id:Optional[int] = Field(default=None)

title:str = Field(min_length=5,max_length=15)

content:str = Field(min_length=5,max_length=200)

class Config:
Expand All @@ -13,11 +14,14 @@ class Config:
'content':'my content'
}
}

class NoteCreate(NoteBase):
pass

class Note(NoteBase):

id:Optional[int] = Field(default=None)

owner_id:int

class Config:
Expand Down
8 changes: 8 additions & 0 deletions schemas/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@


class UserBase(BaseModel):

email:str

class Config:

json_schema_extra = {
"example":{
'email':'jhon.doe@fake.com',
'password':'admin123@'
}
}


class UserCreate(UserBase):

password:str = Field(min_length=8,max_length=16)



class User(UserBase):

id:Optional[int] = Field(default=None)

is_active:bool

class Config:
Expand Down
4 changes: 4 additions & 0 deletions services/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from passlib.context import CryptContext

class Auth():

def __init__(self):

self.pwd_context = CryptContext(schemes=['bcrypt'],deprecated="auto")

def verify_password(self,password:str,hash:str):

return self.pwd_context.verify(password,hash)

def get_password(self,password:str):

return self.pwd_context.hash(password)
Loading