├── tests ├── __init__.py ├── test_todo.py ├── conftest.py └── test_user.py ├── .gitignore ├── app └── v1 │ ├── scripts │ └── create_tables.py │ ├── schema │ ├── token_schema.py │ ├── todo_schema.py │ └── user_schema.py │ ├── model │ ├── user_model.py │ └── todo_model.py │ ├── utils │ ├── settings.py │ └── db.py │ ├── service │ ├── user_service.py │ ├── todo_service.py │ └── auth_service.py │ └── router │ ├── user_router.py │ └── todo_router.py ├── main.py ├── .env.example ├── README.md └── requirements.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | __pycache__/ 5 | env 6 | .env 7 | .pytest_cache -------------------------------------------------------------------------------- /app/v1/scripts/create_tables.py: -------------------------------------------------------------------------------- 1 | from app.v1.model.user_model import User 2 | from app.v1.model.todo_model import Todo 3 | 4 | from app.v1.utils.db import db 5 | 6 | def create_tables(): 7 | with db: 8 | db.create_tables([User, Todo]) -------------------------------------------------------------------------------- /app/v1/schema/token_schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Optional 3 | 4 | class Token(BaseModel): 5 | access_token: str 6 | token_type: str 7 | 8 | 9 | class TokenData(BaseModel): 10 | username: Optional[str] = None -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from app.v1.router.user_router import router as user_router 4 | from app.v1.router.todo_router import router as todo_router 5 | 6 | app = FastAPI() 7 | 8 | app.include_router(user_router) 9 | app.include_router(todo_router) -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Database connection 2 | DB_NAME= 3 | DB_USER= 4 | DB_PASS= 5 | DB_HOST= 6 | DB_PORT= 7 | 8 | # Auth 9 | ACCESS_TOKEN_EXPIRE_MINUTES=1440 10 | SECRET_KEY= -------------------------------------------------------------------------------- /app/v1/model/user_model.py: -------------------------------------------------------------------------------- 1 | import peewee 2 | 3 | from app.v1.utils.db import db 4 | 5 | class User(peewee.Model): 6 | email = peewee.CharField(unique=True, index=True) 7 | username = peewee.CharField(unique=True, index=True) 8 | password = peewee.CharField() 9 | 10 | class Meta: 11 | database = db -------------------------------------------------------------------------------- /app/v1/model/todo_model.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import peewee 4 | 5 | from app.v1.utils.db import db 6 | from .user_model import User 7 | 8 | 9 | class Todo(peewee.Model): 10 | title = peewee.CharField() 11 | created_at = peewee.DateTimeField(default=datetime.now) 12 | is_done = peewee.BooleanField(default=False) 13 | user = peewee.ForeignKeyField(User, backref="todos") 14 | 15 | class Meta: 16 | database = db -------------------------------------------------------------------------------- /app/v1/schema/todo_schema.py: -------------------------------------------------------------------------------- 1 | # Python 2 | from datetime import datetime 3 | 4 | # Pydantic 5 | from pydantic import BaseModel 6 | from pydantic import Field 7 | 8 | 9 | class TodoCreate(BaseModel): 10 | title: str = Field( 11 | ..., 12 | min_length=1, 13 | max_length=60, 14 | example="My first task" 15 | ) 16 | 17 | 18 | class Todo(TodoCreate): 19 | id: int = Field(...) 20 | is_done: bool = Field(default=False) 21 | created_at: datetime = Field(default=datetime.now()) -------------------------------------------------------------------------------- /app/v1/utils/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pydantic import BaseSettings 4 | from dotenv import load_dotenv 5 | load_dotenv() 6 | 7 | 8 | class Settings(BaseSettings): 9 | 10 | _db_name: str = os.getenv('DB_NAME') 11 | db_user: str = os.getenv('DB_USER') 12 | db_pass: str = os.getenv('DB_PASS') 13 | db_host: str = os.getenv('DB_HOST') 14 | db_port: str = os.getenv('DB_PORT') 15 | 16 | secret_key: str = os.getenv('SECRET_KEY') 17 | token_expire: int = os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES') 18 | 19 | @property 20 | def db_name(self): 21 | if os.getenv('RUN_ENV') == 'test': 22 | return 'test_' + self._db_name 23 | 24 | return self._db_name 25 | -------------------------------------------------------------------------------- /app/v1/schema/user_schema.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from pydantic import Field 3 | from pydantic import EmailStr 4 | 5 | 6 | class UserBase(BaseModel): 7 | email: EmailStr = Field( 8 | ..., 9 | example="myemail@cosasdedevs.com" 10 | ) 11 | username: str = Field( 12 | ..., 13 | min_length=3, 14 | max_length=50, 15 | example="MyTypicalUsername" 16 | ) 17 | 18 | 19 | class User(UserBase): 20 | id: int = Field( 21 | ..., 22 | example="5" 23 | ) 24 | 25 | 26 | class UserRegister(UserBase): 27 | password: str = Field( 28 | ..., 29 | min_length=8, 30 | max_length=64, 31 | example="strongpass" 32 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API REST to-do list con FastAPI 2 | 3 | [Parte 1: Cómo crear una API REST COMPLETA con FastAPI, instalación y estructura](https://cosasdedevs.com/posts/api-rest-fastapi-completa/) 4 | 5 | [Parte 2: Conexiones a bases de datos y creación de modelos con FastAPI](https://cosasdedevs.com/posts/conexion-base-datos-modelos-fastapi/) 6 | 7 | [Parte 3: Creación de modelos de Pydantic y nuestro primer usuario con FastAPI](https://cosasdedevs.com/posts/modelos-pydantic-crear-usuario-fastapi/) 8 | 9 | [Parte 4: Autenticación con JWT en FastAPI](https://cosasdedevs.com/posts/autenticacion-login-jwt-fastapi/) 10 | 11 | [Parte 5: Cómo crear un CRUD con FastAPI](https://cosasdedevs.com/posts/crud-fastapi/) 12 | 13 | [Parte 6: Tests en FastAPI](https://cosasdedevs.com/posts/tests-fastapi/) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.3.4 2 | asgiref==3.4.1 3 | atomicwrites==1.4.0 4 | attrs==21.2.0 5 | bcrypt==3.2.0 6 | certifi==2021.10.8 7 | cffi==1.15.0 8 | charset-normalizer==2.0.9 9 | click==8.0.3 10 | colorama==0.4.4 11 | cryptography==36.0.0 12 | dnspython==2.1.0 13 | ecdsa==0.17.0 14 | email-validator==1.1.3 15 | fastapi==0.70.0 16 | h11==0.12.0 17 | idna==3.3 18 | iniconfig==1.1.1 19 | packaging==21.3 20 | passlib==1.7.4 21 | peewee==3.14.8 22 | pluggy==1.0.0 23 | psycopg2==2.9.2 24 | py==1.11.0 25 | pyasn1==0.4.8 26 | pycparser==2.21 27 | pydantic==1.8.2 28 | pyparsing==3.0.6 29 | pytest==6.2.5 30 | python-dotenv==0.19.2 31 | python-jose==3.3.0 32 | python-multipart==0.0.5 33 | requests==2.26.0 34 | rsa==4.8 35 | six==1.16.0 36 | sniffio==1.2.0 37 | starlette==0.16.0 38 | toml==0.10.2 39 | typing-extensions==3.10.0.2 40 | urllib3==1.26.7 41 | uvicorn==0.15.0 -------------------------------------------------------------------------------- /app/v1/service/user_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, status 2 | 3 | from app.v1.model.user_model import User as UserModel 4 | from app.v1.schema import user_schema 5 | from app.v1.service.auth_service import get_password_hash 6 | 7 | 8 | def create_user(user: user_schema.UserRegister): 9 | 10 | get_user = UserModel.filter((UserModel.email == user.email) | (UserModel.username == user.username)).first() 11 | if get_user: 12 | msg = "Email already registered" 13 | if get_user.username == user.username: 14 | msg = "Username already registered" 15 | raise HTTPException( 16 | status_code=status.HTTP_400_BAD_REQUEST, 17 | detail=msg 18 | ) 19 | 20 | db_user = UserModel( 21 | username=user.username, 22 | email=user.email, 23 | password=get_password_hash(user.password) 24 | ) 25 | 26 | db_user.save() 27 | 28 | return user_schema.User( 29 | id = db_user.id, 30 | username = db_user.username, 31 | email = db_user.email 32 | ) -------------------------------------------------------------------------------- /app/v1/utils/db.py: -------------------------------------------------------------------------------- 1 | import peewee 2 | from contextvars import ContextVar 3 | from fastapi import Depends 4 | 5 | from app.v1.utils.settings import Settings 6 | 7 | settings = Settings() 8 | 9 | DB_NAME = settings.db_name 10 | DB_USER = settings.db_user 11 | DB_PASS = settings.db_pass 12 | DB_HOST = settings.db_host 13 | DB_PORT = settings.db_port 14 | 15 | 16 | db_state_default = {"closed": None, "conn": None, "ctx": None, "transactions": None} 17 | db_state = ContextVar("db_state", default=db_state_default.copy()) 18 | 19 | class PeeweeConnectionState(peewee._ConnectionState): 20 | def __init__(self, **kwargs): 21 | super().__setattr__("_state", db_state) 22 | super().__init__(**kwargs) 23 | 24 | def __setattr__(self, name, value): 25 | self._state.get()[name] = value 26 | 27 | def __getattr__(self, name): 28 | return self._state.get()[name] 29 | 30 | 31 | db = peewee.PostgresqlDatabase(DB_NAME, user=DB_USER, password=DB_PASS, host=DB_HOST, port=DB_PORT) 32 | 33 | db._state = PeeweeConnectionState() 34 | 35 | async def reset_db_state(): 36 | db._state._state.set(db_state_default.copy()) 37 | db._state.reset() 38 | 39 | 40 | def get_db(db_state=Depends(reset_db_state)): 41 | try: 42 | db.connect() 43 | yield 44 | finally: 45 | if not db.is_closed(): 46 | db.close() -------------------------------------------------------------------------------- /tests/test_todo.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from main import app 3 | 4 | 5 | def create_user_and_make_login(username: str): 6 | client = TestClient(app) 7 | 8 | user = { 9 | 'email': f'{username}@cosasdedevs.com', 10 | 'username': username, 11 | 'password': 'admin123' 12 | } 13 | 14 | response = client.post( 15 | '/api/v1/user/', 16 | json=user, 17 | ) 18 | 19 | login = { 20 | 'username': username, 21 | 'password': 'admin123' 22 | } 23 | 24 | response = client.post( 25 | '/api/v1/login/', 26 | data=login, 27 | headers={ 28 | 'Content-Type': 'application/x-www-form-urlencoded' 29 | }, 30 | allow_redirects=True 31 | ) 32 | 33 | data = response.json() 34 | return data['access_token'] 35 | 36 | 37 | def test_create_todo_ok(): 38 | token = create_user_and_make_login('test_create_todo_ok') 39 | 40 | client = TestClient(app) 41 | 42 | todo = { 43 | 'title': 'My first task' 44 | } 45 | 46 | response = client.post( 47 | '/api/v1/to-do/', 48 | json=todo, 49 | headers={ 50 | 'Authorization': f'Bearer {token}' 51 | } 52 | ) 53 | 54 | assert response.status_code == 201, response.text 55 | data = response.json() 56 | assert data['title'] == todo['title'] 57 | assert data['is_done'] == False -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ['RUN_ENV'] = 'test' 3 | 4 | from app.v1.model import user_model, todo_model 5 | from app.v1.utils.settings import Settings 6 | 7 | import psycopg2 8 | from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT 9 | 10 | settings = Settings() 11 | 12 | 13 | def postgresql_connection(): 14 | con = psycopg2.connect(f"user='{settings.db_user}' password='{settings.db_pass}'") 15 | con.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) 16 | return con 17 | 18 | def delete_database(): 19 | 20 | if not settings.db_name.startswith("test_"): 21 | raise Exception(f'Invalid name for database = {settings.db_name}') 22 | 23 | sql_drop_db = f"DROP DATABASE IF EXISTS {settings.db_name}" 24 | con = postgresql_connection() 25 | cursor = con.cursor() 26 | cursor.execute(f"SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '{settings.db_name}' AND pid <> pg_backend_pid();") 27 | cursor.execute(sql_drop_db) 28 | con.close() 29 | 30 | def create_database(): 31 | sql_create_db = f"CREATE DATABASE {settings.db_name} WITH OWNER = {settings.db_user} ENCODING = 'UTF8' CONNECTION LIMIT = -1;" 32 | 33 | con = postgresql_connection() 34 | cursor = con.cursor() 35 | cursor.execute(sql_create_db) 36 | con.close() 37 | 38 | def pytest_sessionstart(session): 39 | 40 | delete_database() 41 | create_database() 42 | 43 | from app.v1.utils.db import db 44 | 45 | with db: 46 | db.create_tables([user_model.User, todo_model.Todo]) 47 | 48 | 49 | def pytest_sessionfinish(session, exitstatus): 50 | delete_database() 51 | 52 | -------------------------------------------------------------------------------- /app/v1/router/user_router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | from fastapi import Depends 3 | from fastapi import status 4 | from fastapi import Body 5 | from fastapi.security import OAuth2PasswordRequestForm 6 | 7 | from app.v1.schema import user_schema 8 | from app.v1.service import user_service 9 | from app.v1.service import auth_service 10 | from app.v1.schema.token_schema import Token 11 | 12 | from app.v1.utils.db import get_db 13 | 14 | 15 | router = APIRouter( 16 | prefix="/api/v1", 17 | tags=["users"] 18 | ) 19 | 20 | @router.post( 21 | "/user/", 22 | status_code=status.HTTP_201_CREATED, 23 | response_model=user_schema.User, 24 | dependencies=[Depends(get_db)], 25 | summary="Create a new user" 26 | ) 27 | def create_user(user: user_schema.UserRegister = Body(...)): 28 | """ 29 | ## Create a new user in the app 30 | 31 | ### Args 32 | The app can recive next fields into a JSON 33 | - email: A valid email 34 | - username: Unique username 35 | - password: Strong password for authentication 36 | 37 | ### Returns 38 | - user: User info 39 | """ 40 | return user_service.create_user(user) 41 | 42 | @router.post( 43 | "/login", 44 | tags=["users"], 45 | response_model=Token 46 | ) 47 | async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): 48 | """ 49 | ## Login for access token 50 | 51 | ### Args 52 | The app can recive next fields by form data 53 | - username: Your username or email 54 | - password: Your password 55 | 56 | ### Returns 57 | - access token and token type 58 | """ 59 | access_token = auth_service.generate_token(form_data.username, form_data.password) 60 | return Token(access_token=access_token, token_type="bearer") -------------------------------------------------------------------------------- /tests/test_user.py: -------------------------------------------------------------------------------- 1 | from fastapi.testclient import TestClient 2 | from main import app 3 | 4 | def test_create_user_ok(): 5 | client = TestClient(app) 6 | 7 | user = { 8 | 'email': 'test_create_user_ok@cosasdedevs.com', 9 | 'username': 'test_create_user_ok', 10 | 'password': 'admin123' 11 | } 12 | 13 | response = client.post( 14 | '/api/v1/user/', 15 | json=user, 16 | ) 17 | assert response.status_code == 201, response.text 18 | data = response.json() 19 | assert data['email'] == user['email'] 20 | assert data['username'] == user['username'] 21 | 22 | def test_create_user_duplicate_email(): 23 | client = TestClient(app) 24 | 25 | user = { 26 | 'email': 'test_create_user_duplicate_email@cosasdedevs.com', 27 | 'username': 'test_create_user_duplicate_email', 28 | 'password': 'admin123' 29 | } 30 | 31 | response = client.post( 32 | '/api/v1/user/', 33 | json=user, 34 | ) 35 | assert response.status_code == 201, response.text 36 | 37 | user['username'] = 'test_create_user_duplicate_email2' 38 | 39 | response = client.post( 40 | '/api/v1/user/', 41 | json=user, 42 | ) 43 | assert response.status_code == 400, response.text 44 | data = response.json() 45 | assert data['detail'] == 'Email already registered' 46 | 47 | 48 | def test_create_user_duplicate_username(): 49 | client = TestClient(app) 50 | 51 | user = { 52 | 'email': 'test_create_user_duplicate_username@cosasdedevs.com', 53 | 'username': 'test_create_user_duplicate_username', 54 | 'password': 'admin123' 55 | } 56 | 57 | response = client.post( 58 | '/api/v1/user/', 59 | json=user, 60 | ) 61 | assert response.status_code == 201, response.text 62 | 63 | response = client.post( 64 | '/api/v1/user/', 65 | json=user, 66 | ) 67 | assert response.status_code == 400, response.text 68 | data = response.json() 69 | assert data['detail'] == 'Username already registered' -------------------------------------------------------------------------------- /app/v1/service/todo_service.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, status 2 | 3 | from app.v1.schema import todo_schema 4 | from app.v1.schema import user_schema 5 | from app.v1.model.todo_model import Todo as TodoModel 6 | 7 | 8 | def create_task(task: todo_schema.TodoCreate, user: user_schema.User): 9 | 10 | db_task = TodoModel( 11 | title=task.title, 12 | user_id=user.id 13 | ) 14 | 15 | db_task.save() 16 | 17 | return todo_schema.Todo( 18 | id = db_task.id, 19 | title = db_task.title, 20 | is_done = db_task.is_done, 21 | created_at = db_task.created_at 22 | ) 23 | 24 | def get_tasks(user: user_schema.User, is_done: bool = None): 25 | 26 | if(is_done is None): 27 | tasks_by_user = TodoModel.filter(TodoModel.user_id == user.id).order_by(TodoModel.created_at.desc()) 28 | else: 29 | tasks_by_user = TodoModel.filter((TodoModel.user_id == user.id) & (TodoModel.is_done == is_done)).order_by(TodoModel.created_at.desc()) 30 | 31 | list_tasks = [] 32 | for task in tasks_by_user: 33 | list_tasks.append( 34 | todo_schema.Todo( 35 | id = task.id, 36 | title = task.title, 37 | is_done = task.is_done, 38 | created_at = task.created_at 39 | ) 40 | ) 41 | 42 | return list_tasks 43 | 44 | def get_task(task_id: int, user: user_schema.User): 45 | task = TodoModel.filter((TodoModel.id == task_id) & (TodoModel.user_id == user.id)).first() 46 | 47 | if not task: 48 | raise HTTPException( 49 | status_code=status.HTTP_404_NOT_FOUND, 50 | detail="Task not found" 51 | ) 52 | 53 | return todo_schema.Todo( 54 | id = task.id, 55 | title = task.title, 56 | is_done = task.is_done, 57 | created_at = task.created_at 58 | ) 59 | 60 | def update_status_task(is_done: bool, task_id: int, user: user_schema.User): 61 | task = TodoModel.filter((TodoModel.id == task_id) & (TodoModel.user_id == user.id)).first() 62 | 63 | if not task: 64 | raise HTTPException( 65 | status_code=status.HTTP_404_NOT_FOUND, 66 | detail="Task not found" 67 | ) 68 | 69 | task.is_done = is_done 70 | task.save() 71 | 72 | return todo_schema.Todo( 73 | id = task.id, 74 | title = task.title, 75 | is_done = task.is_done, 76 | created_at = task.created_at 77 | ) 78 | 79 | def delete_task(task_id: int, user: user_schema.User): 80 | task = TodoModel.filter((TodoModel.id == task_id) & (TodoModel.user_id == user.id)).first() 81 | 82 | if not task: 83 | raise HTTPException( 84 | status_code=status.HTTP_404_NOT_FOUND, 85 | detail="Task not found" 86 | ) 87 | 88 | task.delete_instance() -------------------------------------------------------------------------------- /app/v1/service/auth_service.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from typing import Optional 3 | 4 | from fastapi import Depends, HTTPException, status 5 | from fastapi.security import OAuth2PasswordBearer 6 | 7 | from jose import JWTError, jwt 8 | from passlib.context import CryptContext 9 | 10 | from app.v1.model.user_model import User as UserModel 11 | from app.v1.schema.token_schema import TokenData 12 | from app.v1.utils.settings import Settings 13 | 14 | settings = Settings() 15 | 16 | 17 | SECRET_KEY = settings.secret_key 18 | ALGORITHM = "HS256" 19 | ACCESS_TOKEN_EXPIRE_MINUTES = settings.token_expire 20 | 21 | 22 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 23 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/login") 24 | 25 | 26 | def verify_password(plain_password, password): 27 | return pwd_context.verify(plain_password, password) 28 | 29 | 30 | def get_password_hash(password): 31 | return pwd_context.hash(password) 32 | 33 | 34 | def get_user(username: str): 35 | return UserModel.filter((UserModel.email == username) | (UserModel.username == username)).first() 36 | 37 | 38 | def authenticate_user(username: str, password: str): 39 | user = get_user(username) 40 | if not user: 41 | return False 42 | if not verify_password(password, user.password): 43 | return False 44 | return user 45 | 46 | 47 | def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): 48 | to_encode = data.copy() 49 | if expires_delta: 50 | expire = datetime.utcnow() + expires_delta 51 | else: 52 | expire = datetime.utcnow() + timedelta(minutes=15) 53 | to_encode.update({"exp": expire}) 54 | encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 55 | return encoded_jwt 56 | 57 | 58 | def generate_token(username, password): 59 | user = authenticate_user(username, password) 60 | if not user: 61 | raise HTTPException( 62 | status_code=status.HTTP_401_UNAUTHORIZED, 63 | detail="Incorrect email/username or password", 64 | headers={"WWW-Authenticate": "Bearer"}, 65 | ) 66 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 67 | return create_access_token( 68 | data={"sub": user.username}, expires_delta=access_token_expires 69 | ) 70 | 71 | 72 | async def get_current_user(token: str = Depends(oauth2_scheme)): 73 | credentials_exception = HTTPException( 74 | status_code=status.HTTP_401_UNAUTHORIZED, 75 | detail="Could not validate credentials", 76 | headers={"WWW-Authenticate": "Bearer"}, 77 | ) 78 | try: 79 | payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 80 | username: str = payload.get("sub") 81 | if username is None: 82 | raise credentials_exception 83 | token_data = TokenData(username=username) 84 | except JWTError: 85 | raise credentials_exception 86 | 87 | user = get_user(username=token_data.username) 88 | if user is None: 89 | raise credentials_exception 90 | return user -------------------------------------------------------------------------------- /app/v1/router/todo_router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends, Body 2 | from fastapi import status 3 | 4 | from app.v1.schema import todo_schema 5 | from app.v1.service import todo_service 6 | from app.v1.utils.db import get_db 7 | from app.v1.schema.user_schema import User 8 | from app.v1.service.auth_service import get_current_user 9 | 10 | from fastapi import Query 11 | from fastapi import Path 12 | 13 | from typing import List, Optional 14 | 15 | 16 | router = APIRouter(prefix="/api/v1/to-do") 17 | 18 | @router.post( 19 | "/", 20 | tags=["to-do"], 21 | status_code=status.HTTP_201_CREATED, 22 | response_model=todo_schema.Todo, 23 | dependencies=[Depends(get_db)] 24 | ) 25 | def create_task( 26 | todo: todo_schema.TodoCreate = Body(...), 27 | current_user: User = Depends(get_current_user)): 28 | return todo_service.create_task(todo, current_user) 29 | 30 | @router.get( 31 | "/", 32 | tags=["to-do"], 33 | status_code=status.HTTP_200_OK, 34 | response_model=List[todo_schema.Todo], 35 | dependencies=[Depends(get_db)] 36 | ) 37 | def get_tasks( 38 | is_done: Optional[bool] = Query(None), 39 | current_user: User = Depends(get_current_user) 40 | ): 41 | return todo_service.get_tasks(current_user, is_done) 42 | 43 | 44 | @router.get( 45 | "/{task_id}", 46 | tags=["to-do"], 47 | status_code=status.HTTP_200_OK, 48 | response_model=todo_schema.Todo, 49 | dependencies=[Depends(get_db)] 50 | ) 51 | def get_task( 52 | task_id: int = Path( 53 | ..., 54 | gt=0 55 | ), 56 | current_user: User = Depends(get_current_user) 57 | ): 58 | return todo_service.get_task(task_id, current_user) 59 | 60 | 61 | @router.patch( 62 | "/{task_id}/mark_done", 63 | tags=["to-do"], 64 | status_code=status.HTTP_200_OK, 65 | response_model=todo_schema.Todo, 66 | dependencies=[Depends(get_db)] 67 | ) 68 | def mark_task_done( 69 | task_id: int = Path( 70 | ..., 71 | gt=0 72 | ), 73 | current_user: User = Depends(get_current_user) 74 | ): 75 | return todo_service.update_status_task(True, task_id, current_user) 76 | 77 | @router.patch( 78 | "/{task_id}/unmark_done", 79 | tags=["to-do"], 80 | status_code=status.HTTP_200_OK, 81 | response_model=todo_schema.Todo, 82 | dependencies=[Depends(get_db)] 83 | ) 84 | def unmark_task_done( 85 | task_id: int = Path( 86 | ..., 87 | gt=0 88 | ), 89 | current_user: User = Depends(get_current_user) 90 | ): 91 | return todo_service.update_status_task(False, task_id, current_user) 92 | 93 | 94 | @router.delete( 95 | "/{task_id}/", 96 | tags=["to-do"], 97 | status_code=status.HTTP_200_OK, 98 | dependencies=[Depends(get_db)] 99 | ) 100 | def delete_task( 101 | task_id: int = Path( 102 | ..., 103 | gt=0 104 | ), 105 | current_user: User = Depends(get_current_user) 106 | ): 107 | todo_service.delete_task(task_id, current_user) 108 | 109 | return { 110 | 'msg': 'Task has been deleted sucessfully' 111 | } --------------------------------------------------------------------------------