diff --git a/models.py b/models.py index 74562b0..5b2b8a4 100644 --- a/models.py +++ b/models.py @@ -38,4 +38,4 @@ class Task(Base): description = Column(String) status = Column(String, default="pending") project_id = Column(Integer, ForeignKey("projects.id")) - project = relationship("Project", back_populates="tasks") \ No newline at end of file + project_user = relationship("Project", back_populates="tasks") \ No newline at end of file diff --git a/routers/auth.py b/routers/auth.py index 6c28901..d2d7190 100644 --- a/routers/auth.py +++ b/routers/auth.py @@ -7,7 +7,6 @@ from datetime import datetime, timedelta, timezone import models import schemas.users as user_schemas -import routers.users as user_router from pyargon2 import hash diff --git a/routers/me.py b/routers/me.py index 06c7e57..1ab8509 100644 --- a/routers/me.py +++ b/routers/me.py @@ -23,11 +23,13 @@ def get_me(request: Request, db: db_dependency): payload = jwt.decode(token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM]) user_id: str = str(payload.get("sub")) if user_id is None: + request.cookies.clear() ## removing invalid auth cookie raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Not logged in" ) except JWTError: + request.cookies.clear() ## removing invalid auth cookie raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials" @@ -35,6 +37,7 @@ def get_me(request: Request, db: db_dependency): db_user = db.query(models.User).filter(models.User.id == int(user_id)).first() if db_user is None: + request.cookies.clear() ## removing invalid auth cookie raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found" @@ -42,7 +45,7 @@ def get_me(request: Request, db: db_dependency): return db_user -@router.post("/logout") +@router.get("/logout", tags=["me", "auth"]) def logout(request: Request,response: Response): """Logout by clearing the JWT cookie""" @@ -96,5 +99,4 @@ def delete_me(request: Request, db: db_dependency): ## Logout user by clearing cookie request.cookies.clear() - return {"message": "User deleted successfully"} diff --git a/routers/projects.py b/routers/projects.py index 01a0d0f..dff9c62 100644 --- a/routers/projects.py +++ b/routers/projects.py @@ -1,11 +1,15 @@ -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, HTTPException, Depends, Request from typing import List, Annotated +from httpx import request +from jose import JWTError, jwt + +from routers import auth from database import db_dependency -import schemas.tasks as tasks -import schemas.projects as projects -import schemas.users as users +import schemas.tasks as tasks_schemas +import schemas.projects as projects_schemas +import schemas.users as users_schemas router = APIRouter(prefix="/projects", tags=["projects"]) @@ -13,169 +17,266 @@ router = APIRouter(prefix="/projects", tags=["projects"]) ## GET endpoints ## +@router.get("/", response_model=List[projects_schemas.ProjectBase], tags=["projects", "me"]) +def get_projects(db: db_dependency, request: Request): + """Get a user's projects""" -"""Get a project by ID""" + ## User retrieval from JWT token in cookies + get_token = request.cookies.get("access_token") + if not get_token: + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + + try: + payload = jwt.decode(get_token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM]) + user_id: str = str(payload.get("sub")) + if user_id is None: + request.cookies.clear() ## User in cookies not found, clear cookies + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + except JWTError: + request.cookies.clear() ## Probably an invalid token, clear cookies + raise HTTPException( + status_code=401, + detail="Could not validate credentials" + ) -@router.get("/{project_id}", response_model=projects.ProjectBase) -def read_project(project_id: int, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() + ## fetching projects for the user + projects: projects_schemas.ProjectBase | None = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "users.id") == int(user_id)).first() + if projects is None: + return [] + return projects + + +@router.get("/{project_id}", response_model=projects_schemas.ProjectBase) +def get_project(project_id: int, request:Request, db: db_dependency): + """Get a project by ID""" + + get_token = request.cookies.get("access_token") + if not get_token: + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + + try: + payload = jwt.decode(get_token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM]) + user_id: str = str(payload.get("sub")) + if user_id is None: + request.cookies.clear() ## User in cookies not found, clear cookies + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + except JWTError: + request.cookies.clear() ## Probably an invalid token, clear cookies + raise HTTPException( + status_code=401, + detail="Could not validate credentials" + ) + user = db.query(users_schemas.UserBase).filter(getattr(users_schemas.UserBase, "id") == user_id).first() + + db_project = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "id") == project_id).first() if db_project is None: raise HTTPException(status_code=404, detail="Project not found") + if user not in db_project.users: + raise HTTPException(status_code=403, detail="Not authorized to access this project") + return db_project -"""Get tasks from a specified project""" -@router.get("/{project_id}/tasks", response_model=List[tasks.TaskBase], tags=["tasks"]) -def read_tasks_from_project(project_id: int, db: db_dependency): - db_tasks = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id).all() - return db_tasks +@router.get("/{project_id}/users", response_model=List[users_schemas.UserBase], tags=["users", "projects"]) +def get_project_users(project_id: int, request:Request, db: db_dependency): + """Get users from a specified project""" + + get_token = request.cookies.get("access_token") + if not get_token: + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + + try: + payload = jwt.decode(get_token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM]) + user_id: str = str(payload.get("sub")) + if user_id is None: + request.cookies.clear() ## User in cookies not found, clear cookies + raise HTTPException( + status_code=401, + detail="Not logged in" + ) + except JWTError: + request.cookies.clear() ## Probably an invalid token, clear cookies + raise HTTPException( + status_code=401, + detail="Could not validate credentials" + ) + user = db.query(users_schemas.UserBase).filter(getattr(users_schemas.UserBase, "id") == user_id).first() -"""Get a specific task from a specified project""" -@router.get("/{project_id}/tasks/{task_id}", response_model=tasks.TaskBase, tags=["tasks"]) -def read_task_from_project(project_id: int, task_id: int, db: db_dependency): - db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() - if db_task is None: - raise HTTPException(status_code=404, detail="Task not found in the specified project") - return db_task - - -"""Get users from a specified project""" - -@router.get("/{project_id}/users", response_model=List[users.UserBase], tags=["users"]) -def read_users_from_project(project_id: int, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() + db_project = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "id") == project_id).first() if db_project is None: raise HTTPException(status_code=404, detail="Project not found") + if user not in db_project.users: + raise HTTPException(status_code=403, detail="Not authorized to access this project's users") + return db_project.users +# """Get tasks from a specified project""" +# @router.get("/{project_id}/tasks", response_model=List[tasks_schemas.TaskBase], tags=["tasks"]) +# def read_tasks_from_project(project_id: int, db: db_dependency): +# db_tasks = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id).all() +# return db_tasks -## -## POST endpoints -## +# """Get a specific task from a specified project""" +# @router.get("/{project_id}/tasks/{task_id}", response_model=tasks_schemas.TaskBase, tags=["tasks"]) +# def read_task_from_project(project_id: int, task_id: int, db: db_dependency): +# db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() +# if db_task is None: +# raise HTTPException(status_code=404, detail="Task not found in the specified project") +# return db_task -"""Create a new project""" +# """Get users from a specified project""" -@router.post("/", response_model=projects.ProjectCreate) -def create_project(project: projects.ProjectCreate, db: db_dependency): - db_project = projects.models.Project( - name=project.name, - description=project.description - ) - db.add(db_project) - db.commit() - db.refresh(db_project) - return db_project +# @router.get("/{project_id}/users", response_model=List[users_schemas.UserBase], tags=["users"]) +# def read_users_from_project(project_id: int, db: db_dependency): +# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() +# if db_project is None: +# raise HTTPException(status_code=404, detail="Project not found") +# return db_project.users -"""Create a new task in a specified project""" - -@router.post("/{project_id}/tasks", response_model=tasks.TaskBase, tags=["tasks"]) -def create_task_in_project(project_id: int, task: tasks.TaskBase, db: db_dependency): - db_task = tasks.models.Task( - title=task.title, - description=task.description, - status=task.status, - project_id=project_id - ) - db.add(db_task) - db.commit() - db.refresh(db_task) - return db_task +# ## +# ## POST endpoints +# ## -"""Add users to a specified project using their IDs""" +# """Create a new project""" -@router.post("/{project_id}/users", response_model=projects.ProjectAddUsers, tags=["users"]) -def add_users_to_project(project_id: int, user_data: projects.ProjectAddUsers, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() - if db_project is None: - raise HTTPException(status_code=404, detail="Project not found") - for user_id in user_data.user_ids: - db_user = db.query(users.models.User).filter(users.models.User.id == user_id).first() - if db_user: - db_project.users.append(db_user) - db.commit() - db.refresh(db_project) - return db_project +# @router.post("/", response_model=projects.ProjectCreate) +# def create_project(project: projects.ProjectCreate, db: db_dependency): +# db_project = projects( +# name=project.name, +# description=project.description +# ) +# db.add(db_project) +# db.commit() +# db.refresh(db_project) +# return db_project -## -## PUT endpoints -## +# """Create a new task in a specified project""" + +# @router.post("/{project_id}/tasks", response_model=tasks.TaskBase, tags=["tasks"]) +# def create_task_in_project(project_id: int, task: tasks.TaskBase, db: db_dependency): +# db_task = tasks.models.Task( +# title=task.title, +# description=task.description, +# status=task.status, +# project_id=project_id +# ) +# db.add(db_task) +# db.commit() +# db.refresh(db_task) +# return db_task -"""Update a project by ID""" +# """Add users to a specified project using their IDs""" -@router.put("/{project_id}", response_model=projects.ProjectUpdate) -def update_project(project_id: int, project: projects.ProjectUpdate, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() - if db_project is None: - raise HTTPException(status_code=404, detail="Project not found") - if project.name is not None: - db_project.name = project.name - if project.description is not None: - db_project.description = project.description - db.commit() - db.refresh(db_project) - return db_project +# @router.post("/{project_id}/users", response_model=projects.ProjectAddUsers, tags=["users"]) +# def add_users_to_project(project_id: int, user_data: projects.ProjectAddUsers, db: db_dependency): +# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() +# if db_project is None: +# raise HTTPException(status_code=404, detail="Project not found") +# for user_id in user_data.user_ids: +# db_user = db.query(users.models.User).filter(users.models.User.id == user_id).first() +# if db_user: +# db_project.users.append(db_user) +# db.commit() +# db.refresh(db_project) +# return db_project -"""Update a task in a specified project""" - -@router.put("/{project_id}/tasks/{task_id}", response_model=tasks.TaskUpdate, tags=["tasks"]) -def update_task_in_project(project_id: int, task_id: int, task: tasks.TaskUpdate, db: db_dependency): - db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() - if db_task is None: - raise HTTPException(status_code=404, detail="Task not found in the specified project") - if task.title is not None: - db_task.title = task.title - if task.description is not None: - db_task.description = task.description - if task.status is not None: - db_task.status = task.status - db.commit() - db.refresh(db_task) - return db_task - -## -## DELETE endpoints -## - -"""Delete a project by ID""" - -@router.delete("/{project_id}") -def delete_project(project_id: int, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() - if db_project is None: - raise HTTPException(status_code=404, detail="Project not found") - db.delete(db_project) - db.commit() - return {"detail": "Project deleted successfully"} +# ## +# ## PUT endpoints +# ## -"""Delete a task from a specified project""" +# """Update a project by ID""" -@router.delete("/{project_id}/tasks/{task_id}" , tags=["tasks"]) -def delete_task_from_project(project_id: int, task_id: int, db: db_dependency): - db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() - if db_task is None: - raise HTTPException(status_code=404, detail="Task not found in the specified project") - db.delete(db_task) - db.commit() - return {"detail": "Task deleted successfully"} +# @router.put("/{project_id}", response_model=projects.ProjectUpdate) +# def update_project(project_id: int, project: projects.ProjectUpdate, db: db_dependency): +# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() +# if db_project is None: +# raise HTTPException(status_code=404, detail="Project not found") +# if project.name is not None: +# db_project.name = project.name +# if project.description is not None: +# db_project.description = project.description +# db.commit() +# db.refresh(db_project) +# return db_project -"""Remove users from a specified project using their IDs""" +# """Update a task in a specified project""" -@router.delete("/{project_id}/users/{user_id}", tags=["users"]) -def remove_user_from_project(project_id: int, user_id: int, db: db_dependency): - db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() - if db_project is None: - raise HTTPException(status_code=404, detail="Project not found") - db_user = db.query(users.models.User).filter(users.models.User.id == user_id).first() - if db_user is None or db_user not in db_project.users: - raise HTTPException(status_code=404, detail="User not found in the specified project") - db_project.users.remove(db_user) - db.commit() - db.refresh(db_project) - return {"detail": "User removed from project successfully"} +# @router.put("/{project_id}/tasks/{task_id}", response_model=tasks.TaskUpdate, tags=["tasks"]) +# def update_task_in_project(project_id: int, task_id: int, task: tasks.TaskUpdate, db: db_dependency): +# db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() +# if db_task is None: +# raise HTTPException(status_code=404, detail="Task not found in the specified project") +# if task.title is not None: +# db_task.title = task.title +# if task.description is not None: +# db_task.description = task.description +# if task.status is not None: +# db_task.status = task.status +# db.commit() +# db.refresh(db_task) +# return db_task + +# ## +# ## DELETE endpoints +# ## + +# """Delete a project by ID""" + +# @router.delete("/{project_id}") +# def delete_project(project_id: int, db: db_dependency): +# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() +# if db_project is None: +# raise HTTPException(status_code=404, detail="Project not found") +# db.delete(db_project) +# db.commit() +# return {"detail": "Project deleted successfully"} + + +# """Delete a task from a specified project""" + +# @router.delete("/{project_id}/tasks/{task_id}" , tags=["tasks"]) +# def delete_task_from_project(project_id: int, task_id: int, db: db_dependency): +# db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() +# if db_task is None: +# raise HTTPException(status_code=404, detail="Task not found in the specified project") +# db.delete(db_task) +# db.commit() +# return {"detail": "Task deleted successfully"} + + +# """Remove users from a specified project using their IDs""" + +# @router.delete("/{project_id}/users/{user_id}", tags=["users"]) +# def remove_user_from_project(project_id: int, user_id: int, db: db_dependency): +# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() +# if db_project is None: +# raise HTTPException(status_code=404, detail="Project not found") +# db_user = db.query(users.models.User).filter(users.models.User.id == user_id).first() +# if db_user is None or db_user not in db_project.users: +# raise HTTPException(status_code=404, detail="User not found in the specified project") +# db_project.users.remove(db_user) +# db.commit() +# db.refresh(db_project) +# return {"detail": "User removed from project successfully"}