Compare commits

..

14 commits

Author SHA1 Message Date
7b011fd887 removed TODO comments from main.py 2026-02-03 13:37:23 +01:00
b7d24a85e4 Added basic description to FastAPI docs 2026-02-03 13:35:10 +01:00
4a2675a6f3 Added AGPL-3.0-or-later license to FastAPI metadata 2026-02-03 13:30:40 +01:00
e6e285a2c1 fixed some routers/projects.py get endpoint where Schemas and models where confused between themselves. 2026-02-03 13:19:15 +01:00
b34662e877 refactored auth.py to use helper methods 2026-02-03 13:05:10 +01:00
a3fb4903ed Fixed similar to last oversight where deleting a user would let it still in projects assigned users 2026-02-03 12:34:56 +01:00
52f13f5023 fixed oversight where deleting a project would leave dangling tasks and user-to-project relations 2026-02-03 12:33:20 +01:00
e53fb3f773 Refactored routers/projects.py to cut down on per-endpoint LoCs using helper methods 2026-02-03 12:25:22 +01:00
2a9fdc31a6 Refactored routers/projects.py to make way more readable by changing imports 2026-02-03 11:51:30 +01:00
5f7746279b More consistent endpoint methods and ordered them 2026-02-03 11:41:53 +01:00
3580a4f79f Created more endpoints for project managment 2026-02-03 11:30:41 +01:00
7bd2325649 Created more endpoints for project managment 2026-02-03 11:26:17 +01:00
11899d985e Removed useless LoCs (unused imports, useless comments, etc.) 2026-02-03 11:25:37 +01:00
96d833a089 Created schema projects_tasks to avoid circular imports 2026-02-03 11:20:19 +01:00
7 changed files with 263 additions and 233 deletions

31
main.py
View file

@ -10,6 +10,26 @@ from routers.auth import router as auth_router
from routers.me import router as me_router from routers.me import router as me_router
from database import init_db from database import init_db
app_description = """
This API serves as the backend for a Kanban-style project management application.
It allows users to manage projects, tasks, and user assignments with proper authentication and authorization.
## Stack
- FastAPI
- SQLAlchemy
- SQLite
## Features
- User Authentication (JWT and Argon2 Password Hashing)
- Project Management (Create, Read, Update, Delete)
- Task Management within Projects
- User Assignments to Projects
- CORS Configuration for Frontend Integration
## Source Code
The source code for this API can be found on [GitHub](https://github.com/a-mayb3/Kanban_clone_backend) or [my forgejo instance](https://git.vollex.cc/a-mayb3/Kanban_clone_backend).
"""
global_logger = logging.getLogger() global_logger = logging.getLogger()
global_logger.setLevel(logging.INFO) global_logger.setLevel(logging.INFO)
logging.basicConfig( logging.basicConfig(
@ -32,7 +52,12 @@ async def lifespan(app: FastAPI):
init_db() init_db()
yield yield
app = FastAPI(lifespan=lifespan) app = FastAPI(
lifespan=lifespan,
license_info={"name": "AGPL-3.0-or-later", "url": "https://www.gnu.org/licenses/agpl-3.0.en.html"},
description=app_description,
title="Kanban Clone Backend API",
)
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@ -57,10 +82,6 @@ def ping():
def source(): def source():
return {"url": "https://github.com/a-mayb3/Kanban_clone_backend"} return {"url": "https://github.com/a-mayb3/Kanban_clone_backend"}
## TODO: Add root endpoint that gives basic info about the API
## TODO: Add more detailed error handling and logging
## TODO: Implement authentication and authorization mechanisms
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000) uvicorn.run(app, host="0.0.0.0", port=8000)

View file

@ -29,38 +29,63 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None):
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt return encoded_jwt
def verify_jwt_token(token: str):
"""Verify and decode a JWT token"""
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)
return user_id
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)
def get_user_from_jwt(request: Request, db: db_dependency) -> models.User : def get_user_from_jwt(request: Request, db: db_dependency) -> models.User :
"""Helper function to check for valid JWT token in cookies""" """Helper function to check for valid JWT token in cookies"""
token = request.cookies.get("access_token")
if not token: get_token = request.cookies.get("access_token")
if not get_token or get_token is None:
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="Not logged in" detail="Not logged in"
) )
try: try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) user_id: str = verify_jwt_token(get_token) ## verifying token validity
user_id: str = str(payload.get("sub"))
if user_id is None:
request.cookies.clear() ## removing invalid auth cookie
raise HTTPException(
status_code=401,
detail="Not logged in"
)
db_user = db.query(models.User).filter(models.User.id == int(user_id)).first() db_user = db.query(models.User).filter(models.User.id == int(user_id)).first()
if db_user is None: if db_user is None:
request.cookies.clear() ## removing invalid auth cookie request.cookies.clear() ## removing invalid auth cookie
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="User not found" detail="Could not verify credentials"
) )
return db_user return db_user
except HTTPException:
except JWTError:
request.cookies.clear() ## removing invalid auth cookie request.cookies.clear() ## removing invalid auth cookie
raise
def verify_user_password(user_id: int, password: str, db: db_dependency) -> None:
"""Verify user's password"""
db_user = db.query(models.User).filter(models.User.id == user_id).first()
if db_user is None:
raise HTTPException( raise HTTPException(
status_code=401, status_code=401,
detail="Could not validate credentials" detail="Could not verify credentials")
)
hashed_password = hash(password=password, salt=str(getattr(db_user,"password_salt")), variant="id")
if hashed_password != db_user.password_hash:
raise HTTPException(
status_code=401,
detail="Could not verify credentials")
@router.post("/login") @router.post("/login")
def login(user_data: user_schemas.UserLogin, request: Request, response: Response, db: db_dependency): def login(user_data: user_schemas.UserLogin, request: Request, response: Response, db: db_dependency):
@ -78,8 +103,9 @@ def login(user_data: user_schemas.UserLogin, request: Request, response: Respons
} }
} }
except HTTPException: except HTTPException:
pass # Token invalid or expired, proceed to login request.cookies.clear() ## removing invalid auth cookie
## check if user exists
db_user = db.query(models.User).filter(models.User.email == user_data.email).first() db_user = db.query(models.User).filter(models.User.email == user_data.email).first()
if db_user is None: if db_user is None:
raise HTTPException( raise HTTPException(
@ -87,16 +113,12 @@ def login(user_data: user_schemas.UserLogin, request: Request, response: Respons
detail="Incorrect email or password" detail="Incorrect email or password"
) )
if not verify_user_password(getattr(db_user, "id"), user_data.password, db): verify_user_password(getattr(db_user, "id"), user_data.password, db)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password"
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token( access_token = create_access_token(
data={"sub": str(db_user.id)}, expires_delta=access_token_expires data={"sub": str(db_user.id)},
expires_delta=access_token_expires
) )
# Set JWT in httpOnly cookie # Set JWT in httpOnly cookie
@ -118,30 +140,3 @@ def login(user_data: user_schemas.UserLogin, request: Request, response: Respons
} }
} }
def verify_jwt_token(token: str):
"""Verify and decode a JWT token"""
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise credentials_exception
return user_id
except JWTError:
raise credentials_exception
def verify_user_password(user_id: int, password: str, db: db_dependency) -> bool:
"""Verify user's password"""
db_user = db.query(models.User).filter(models.User.id == user_id).first()
if db_user is None:
return False
hashed_password = hash(password=password, salt=str(getattr(db_user,"password_salt")), variant="id")
if hashed_password != db_user.password_hash:
return False
return True

View file

@ -35,6 +35,18 @@ def delete_me(request: Request, db: db_dependency):
"""Delete current authenticated user""" """Delete current authenticated user"""
user = auth.get_user_from_jwt(request, db) user = auth.get_user_from_jwt(request, db)
## Remove user from all projects, delete projects with no users left
projects = user.projects[:]
for project in projects:
project.users.remove(user)
if len(project.users) == 0:
## delete project if no users left
tasks = project.tasks[:]
for task in tasks:
db.delete(task)
db.delete(project)
db.delete(user) db.delete(user)
db.commit() db.commit()
## Logout user by clearing cookie ## Logout user by clearing cookie

View file

@ -1,44 +1,20 @@
from fastapi import APIRouter, HTTPException, Depends, Request from fastapi import APIRouter, HTTPException, Depends, Request
from typing import List, Annotated from typing import List, Annotated
from httpx import request
from jose import JWTError, jwt
from routers import auth
from database import db_dependency from database import db_dependency
import schemas.tasks as tasks_schemas from schemas.tasks import TaskBase, TaskCreate, TaskUpdate
import schemas.projects as projects_schemas from schemas.projects import ProjectBase, ProjectCreate, ProjectUpdate, ProjectAddUsers, ProjectRemoveUsers
import schemas.users as users_schemas from schemas.users import UserBase
from schemas.projects_users import ProjectUserBase
from schemas.projects_tasks import ProjectTaskBase, ProjectTaskCreate
from models import Project from models import Project, Task, User
from routers.auth import get_user_from_jwt from routers.auth import get_user_from_jwt
router = APIRouter(prefix="/projects", tags=["projects"]) def get_project_by_id_for_user(user: UserBase, project_id: int, db: db_dependency) -> ProjectBase:
"""Get a project by ID and verify user has access"""
## db_project = db.query(Project).filter(Project.id == project_id).first()
## 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"""
user = get_user_from_jwt(request, db)
user_id = getattr(user, "id")
## fetching projects for the user
projects = db.query(Project).join(Project.users).filter(getattr(users_schemas.UserBase, "id") == int(user_id)).all()
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"""
user = get_user_from_jwt(request, db)
db_project = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "id") == project_id).first()
if db_project is None: if db_project is None:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")
if user not in db_project.users: if user not in db_project.users:
@ -46,39 +22,85 @@ def get_project(project_id: int, request:Request, db: db_dependency):
return db_project return db_project
@router.get("/{project_id}/users", response_model=List[users_schemas.UserBase], tags=["users", "projects"]) def get_task_by_id_for_project(project: ProjectBase, task_id: int, db: db_dependency) -> TaskBase:
"""
Get a task by ID within a project
Supposes the user has already been verified to have access to the project
"""
db_task = db.query(Task).filter(Task.id == task_id, Task.project_id == getattr(project, "id")).first()
if db_task is None:
raise HTTPException(status_code=404, detail="Task not found in the specified project")
return db_task
router = APIRouter(prefix="/projects", tags=["projects"])
@router.get("/", response_model=List[ProjectBase], tags=["projects", "me"])
def get_projects(db: db_dependency, request: Request):
"""Get a user's projects"""
user = get_user_from_jwt(request, db)
user_id = getattr(user, "id")
## fetching projects for the user
projects = db.query(Project).join(Project.users).filter(User.id == user_id).all()
return projects
@router.get("/{project_id}", response_model=ProjectBase)
def get_project(project_id: int, request:Request, db: db_dependency):
"""Get a project by ID"""
user = get_user_from_jwt(request, db)
project = get_project_by_id_for_user(user, project_id, db)
return project
@router.get("/{project_id}/users", response_model=List[UserBase], tags=["users", "projects"])
def get_project_users(project_id: int, request:Request, db: db_dependency): def get_project_users(project_id: int, request:Request, db: db_dependency):
"""Get users from a specified project""" """Get users from a specified project"""
user = get_user_from_jwt(request, db) user = get_user_from_jwt(request, db)
db_project = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "id") == project_id).first() db_project = get_project_by_id_for_user(user, project_id, db)
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 return db_project.users
@router.get("/{project_id}/tasks", response_model=List[tasks_schemas.TaskBase], tags=["tasks", "projects"])
@router.get("/{project_id}/tasks/{task_id}", response_model=TaskBase, tags=["tasks"])
def get_project_task(project_id: int, task_id: int, db: db_dependency, request: Request):
"""Get a specific task from a specified project"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
db_task = get_task_by_id_for_project(db_project, task_id, db)
return db_task
@router.get("/{project_id}/users/{user_id}", response_model=UserBase, tags=["users"])
def get_project_user(project_id: int, user_id: int, db: db_dependency, request: Request):
"""Get a specific user from a specified project"""
user = get_user_from_jwt(request, db)
db_project : ProjectBase = get_project_by_id_for_user(user, project_id, db)
db_user = db.query(UserBase).filter(getattr(UserBase, "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")
return db_user
@router.get("/{project_id}/tasks", response_model=List[TaskBase], tags=["tasks", "projects"])
def get_project_tasks(project_id: int, request:Request, db: db_dependency): def get_project_tasks(project_id: int, request:Request, db: db_dependency):
"""Get tasks from a specified project""" """Get tasks from a specified project"""
user = get_user_from_jwt(request, db) user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
db_tasks = db.query(TaskBase).filter(getattr(TaskBase, "project_id") == project_id).all()
return db_tasks
db_project = db.query(projects_schemas.ProjectBase).filter(getattr(projects_schemas.ProjectBase, "id") == project_id).first() @router.post("/", response_model=ProjectCreate)
if db_project is None: def create_project(project: ProjectCreate, request:Request, db: db_dependency):
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 tasks")
return db.query(tasks_schemas.TaskBase).filter(getattr(tasks_schemas.TaskBase, "project_id") == project_id).all()
@router.post("/", response_model=projects_schemas.ProjectCreate)
def create_project(project: projects_schemas.ProjectCreate, request:Request, db: db_dependency):
"""Create a new project""" """Create a new project"""
user = get_user_from_jwt(request, db) user = get_user_from_jwt(request, db)
db_project = projects_schemas.ProjectCreate(
db_project = ProjectCreate(
name=project.name, name=project.name,
description=project.description, description=project.description,
tasks=[], tasks=[],
@ -93,159 +115,121 @@ def create_project(project: projects_schemas.ProjectCreate, request:Request, db:
return db_project return db_project
# """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
# """Get a specific task from a specified project""" @router.post("/{project_id}/tasks", response_model=ProjectTaskCreate, tags=["tasks"])
# @router.get("/{project_id}/tasks/{task_id}", response_model=tasks_schemas.TaskBase, tags=["tasks"]) def create_project_task(project_id: int, task: TaskCreate, db: db_dependency, request: Request):
# def read_task_from_project(project_id: int, task_id: int, db: db_dependency): """Create a new task in a specified project"""
# db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() user = get_user_from_jwt(request, db)
# if db_task is None:
# raise HTTPException(status_code=404, detail="Task not found in the specified project")
# return db_task
db_project = get_project_by_id_for_user(user, project_id, db)
# """Get users from a specified project""" db_task = ProjectTaskCreate(
title=task.title,
description=task.description,
status=task.status,
project=db_project
)
# @router.get("/{project_id}/users", response_model=List[users_schemas.UserBase], tags=["users"]) db.add(db_task)
# def read_users_from_project(project_id: int, db: db_dependency): db.commit()
# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() db.refresh(db_task)
# if db_project is None: return db_task
# raise HTTPException(status_code=404, detail="Project not found")
# return db_project.users
@router.post("/{project_id}/users", response_model=ProjectAddUsers, tags=["users"])
def add_project_user(project_id: int, user_data: ProjectAddUsers, db: db_dependency, request: Request):
"""Add users to a specified project using their IDs"""
user = get_user_from_jwt(request, db)
# ## db_project = get_project_by_id_for_user(user, project_id, db)
# ## POST endpoints
# ##
for user_id in user_data.user_ids:
db_user = db.query(UserBase).filter(getattr(UserBase, "id") == user_id).first()
if db_user:
db_project.users.append(db_user)
db.commit()
db.refresh(db_project)
return db_project
# """Create a new project""" @router.delete("/{project_id}/users/{user_id}", response_model=ProjectRemoveUsers, tags=["users"])
def remove_user_from_project(project_id: int, user_id: int, db: db_dependency, request: Request):
"""Remove a user from a specified project using their ID"""
user = get_user_from_jwt(request, db)
# @router.post("/", response_model=projects.ProjectCreate) db_project = get_project_by_id_for_user(user, project_id, db)
# 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
db_user = db.query(UserBase).filter(getattr(UserBase, "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")
# """Create a new task in a specified project""" db_project.users.remove(db_user)
db.commit()
db.refresh(db_project)
return db_project
# @router.post("/{project_id}/tasks", response_model=tasks.TaskBase, tags=["tasks"]) @router.put("/{project_id}/tasks/{task_id}", response_model=TaskUpdate, tags=["tasks"])
# def create_task_in_project(project_id: int, task: tasks.TaskBase, db: db_dependency): def update_project_task(project_id: int, task_id: int, task: TaskUpdate, db: db_dependency, request: Request):
# db_task = tasks.models.Task( """Update a task in a specified project"""
# title=task.title, user = get_user_from_jwt(request, db)
# description=task.description, db_project = get_project_by_id_for_user(user, project_id, db)
# status=task.status, db_task = get_task_by_id_for_project(db_project, task_id, db)
# project_id=project_id
# )
# db.add(db_task)
# db.commit()
# db.refresh(db_task)
# return db_task
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
# """Add users to a specified project using their IDs""" db.commit()
db.refresh(db_task)
return db_task
# @router.post("/{project_id}/users", response_model=projects.ProjectAddUsers, tags=["users"]) @router.put("/{project_id}", response_model=ProjectUpdate)
# def add_users_to_project(project_id: int, user_data: projects.ProjectAddUsers, db: db_dependency): def update_project(project_id: int, project: ProjectUpdate, db: db_dependency, request: Request):
# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() """Update a project by ID"""
# if db_project is None: user = get_user_from_jwt(request, db)
# 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
db_project = get_project_by_id_for_user(user, project_id, db)
# ## if project.name is not None:
# ## PUT endpoints 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
# """Update a project by ID""" @router.delete("/{project_id}", tags=["projects"])
def delete_project(project_id: int, db: db_dependency, request: Request):
"""Delete a project by ID"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
# @router.put("/{project_id}", response_model=projects.ProjectUpdate) ## Remove dangling tasks and user associations
# def update_project(project_id: int, project: projects.ProjectUpdate, db: db_dependency): tasks: List[TaskBase] = db.query(TaskBase).filter(getattr(TaskBase, "project_id") == project_id).all()
# db_project = db.query(projects.models.Project).filter(projects.models.Project.id == project_id).first() for task in tasks:
# if db_project is None: db.delete(task)
# raise HTTPException(status_code=404, detail="Project not found") db_project.tasks.remove(task)
# 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
users: List[ProjectUserBase] = db.query(ProjectUserBase).join(ProjectUserBase).filter(getattr(ProjectBase, "id") == project_id).all()
for proj_user in users:
db_project.users.remove(proj_user)
proj_user.projects.remove(db_project)
# """Update a task in a specified project""" db.delete(db_project)
db.commit()
return {"detail": "Project deleted successfully"}
# @router.put("/{project_id}/tasks/{task_id}", response_model=tasks.TaskUpdate, tags=["tasks"]) @router.delete("/{project_id}/tasks/{task_id}" , tags=["tasks"])
# def update_task_in_project(project_id: int, task_id: int, task: tasks.TaskUpdate, db: db_dependency): def delete_project_task(project_id: int, task_id: int, db: db_dependency, request: Request):
# db_task = db.query(tasks.models.Task).filter(tasks.models.Task.project_id == project_id, tasks.models.Task.id == task_id).first() """Delete a task from a specified project"""
# if db_task is None: user = get_user_from_jwt(request, db)
# raise HTTPException(status_code=404, detail="Task not found in the specified project") db_project = get_project_by_id_for_user(user, project_id, db)
# if task.title is not None: db_task = get_task_by_id_for_project(db_project, task_id, db)
# 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
# ## db.delete(db_task)
# ## DELETE endpoints db_project.tasks.remove(db_task)
# ##
# """Delete a project by ID""" db.commit()
return {"detail": "Task deleted successfully"}
# @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"}

View file

@ -7,12 +7,14 @@ from schemas.users import UserBase
class ProjectBase(BaseModel): class ProjectBase(BaseModel):
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)
id: int
name: str name: str
description: str description: str
tasks: List[TaskBase] tasks: List[TaskBase]
users: List[UserBase] users: List[UserBase]
class ProjectFull(ProjectBase):
id: int
class ProjectCreate(BaseModel): class ProjectCreate(BaseModel):
name: str name: str
description: Optional[str] = None description: Optional[str] = None

11
schemas/projects_tasks.py Normal file
View file

@ -0,0 +1,11 @@
import schemas.projects as project_schemas
import schemas.tasks as task_schemas
from pydantic import ConfigDict
class ProjectTaskBase(task_schemas.TaskBase):
model_config = ConfigDict(from_attributes=True)
project: project_schemas.ProjectBase
class ProjectTaskCreate(task_schemas.TaskCreate):
project: project_schemas.ProjectBase

View file

@ -17,6 +17,11 @@ class TaskBase(BaseModel):
description: Optional[str] = None description: Optional[str] = None
status: TaskStatus = TaskStatus.PENDING status: TaskStatus = TaskStatus.PENDING
class TaskCreate(BaseModel):
title: str
description: Optional[str] = None
status: TaskStatus = TaskStatus.PENDING
class TaskUpdate(BaseModel): class TaskUpdate(BaseModel):
title: Optional[str] = None title: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None