Compare commits

..

No commits in common. "5275838fc73a1fc993fa9422a7fc1bc8d6a57b92" and "4722c4cf99964c7702043627865140ecf5fca25f" have entirely different histories.

14 changed files with 0 additions and 914 deletions

8
.gitignore vendored
View file

@ -1,11 +1,3 @@
*.db
log.app
.idea/
.vscode/
.env*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]

View file

@ -1,32 +0,0 @@
import sqlalchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, ConfigDict
from fastapi import Depends
from sqlalchemy.orm import Session
from typing import Annotated
URL_DATABASE = "sqlite:///./kanban_clone.db"
engine = create_engine(URL_DATABASE, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def init_db() -> None:
# Import models so they are registered with SQLAlchemy metadata
import models # noqa: F401
Base.metadata.create_all(bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
db_dependency = Annotated[Session, Depends(get_db)]

153
main.py
View file

@ -1,153 +0,0 @@
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from routers.projects import router as projects_router
from routers.users import router as users_router
from routers.auth import router as auth_router
from routers.me import router as me_router
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).
## Other projects
Here are some frontend implementations for this API:
- [KanbanCloneAngular](https://github.com/a-mayb3/KanbanCloneAngular) - Angular frontend
- [KanbanCloneAndroid](https://github.com/a-mayb3/KanbanCloneAndroid) - Android frontend
"""
global_logger = logging.getLogger()
global_logger.setLevel(logging.INFO)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='app.log',
encoding='utf-8'
)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
global_logger.addHandler(handler)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger = global_logger
# Place for startup and shutdown events if needed in the future
logger.info("Initializing database...")
init_db()
yield
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(
CORSMiddleware,
allow_origins=["http://localhost:3000", "http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth_router)
app.include_router(users_router)
app.include_router(me_router)
app.include_router(projects_router)
"""ping pong :)"""
@app.get("/ping")
def ping():
return {"message": "pong"}
"""Gives project url"""
@app.get("/sources")
def source():
return {"url": "https://github.com/a-mayb3/Kanban_clone_backend"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
"""Custom HTTP exception handler"""
logger = global_logger
logger.error(f"HTTP error occurred: {exc.detail}")
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"message": exc.detail,
"type": "authentication_error" if exc.status_code == 401 else "authorization_error",
"status_code": exc.status_code
}
},
headers=exc.headers
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
"""Handle validation errors"""
logger = global_logger
logger.error(f"Validation error: {exc.errors()}")
return JSONResponse(
status_code=422,
content={
"error": {
"message": "Validation error",
"type": "validation_error",
"details": exc.errors()
}
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request, exc):
"""Handle all other exceptions"""
logger = global_logger
logger.error(f"Unexpected error: {exc}")
return JSONResponse(
status_code=500,
content={
"error": {
"message": "An unexpected error occurred.",
"type": "internal_server_error",
"details": str(exc)
}
}
)

View file

@ -1,41 +0,0 @@
from sqlalchemy import Column, ForeignKey, String, Integer, Table
from sqlalchemy.dialects.sqlite import BLOB
from sqlalchemy.orm import relationship
from database import Base
from typing import Optional, List
project_user = Table(
"project_user",
Base.metadata,
Column("project_id", Integer, ForeignKey("projects.id"), primary_key=True),
Column("user_id", Integer, ForeignKey("users.id"), primary_key=True)
)
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
password_hash = Column(String)
password_salt = Column(String)
projects = relationship("Project", secondary=project_user, back_populates="users")
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, index=True)
description = Column(String)
users = relationship("User", secondary=project_user, back_populates="projects")
tasks = relationship("Task", back_populates="project")
class Task(Base):
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
title = Column(String, index=True)
description = Column(String)
status = Column(String, default="pending")
project_id = Column(Integer, ForeignKey("projects.id"))
project = relationship("Project", back_populates="tasks")

View file

@ -1,58 +0,0 @@
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
bcrypt==5.0.0
certifi==2026.1.4
cffi==2.0.0
click==8.3.1
cryptography==46.0.4
dnspython==2.8.0
ecdsa==0.19.1
email-validator==2.3.0
fastapi==0.128.0
fastapi-cli==0.0.20
fastapi-cloud-cli==0.11.0
fastar==0.8.0
greenlet==3.3.1
h11==0.16.0
httpcore==1.0.9
httptools==0.7.1
httpx==0.28.1
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.6
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mdurl==0.1.2
passlib==1.7.4
pyargon2==1.1.2
pyasn1==0.6.2
pycparser==3.0
pydantic==2.12.5
pydantic-extra-types==2.11.0
pydantic-settings==2.12.0
pydantic_core==2.41.5
Pygments==2.19.2
python-dotenv==1.2.1
python-jose==3.5.0
python-multipart==0.0.22
PyYAML==6.0.3
rich==14.3.1
rich-toolkit==0.17.1
rignore==0.7.6
rsa==4.9.1
sentry-sdk==2.50.0
shellingham==1.5.4
six==1.17.0
SQLAlchemy==2.0.46
starlette==0.50.0
starlette-session==0.4.3
starlette-session-middleware==0.1.6
typer==0.21.1
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.6.3
uvicorn==0.40.0
uvloop==0.22.1
watchfiles==1.1.1
websockets==16.0

View file

@ -1,142 +0,0 @@
import os
from fastapi import APIRouter, Depends, HTTPException, Request, status, Response
from database import db_dependency
from jose import JWTError, jwt
from datetime import datetime, timedelta, timezone
import models
import schemas.users as user_schemas
from pyargon2 import hash
router = APIRouter(prefix="/auth", tags=["auth"])
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-this-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1440 # 24 hours
def create_access_token(data: dict, expires_delta: timedelta | None = None):
"""Create a JWT token"""
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
to_encode.update({"iat": datetime.now(timezone.utc)})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
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 :
"""Helper function to check for valid JWT token in cookies"""
get_token = request.cookies.get("access_token")
if not get_token or get_token is None:
raise HTTPException(
status_code=401,
detail="Not logged in"
)
try:
user_id: str = verify_jwt_token(get_token) ## verifying token validity
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=401,
detail="Could not verify credentials"
)
return db_user
except HTTPException:
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(
status_code=401,
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")
def login(user_data: user_schemas.UserLogin, request: Request, response: Response, db: db_dependency):
"""Login and receive JWT token in cookie"""
## check if access token already exists
get_token = request.cookies.get("access_token")
if get_token:
try:
user_id = verify_jwt_token(get_token)
return {
"message": "Already logged in",
"user": {
"id": user_id
}
}
except HTTPException:
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()
if db_user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password"
)
verify_user_password(getattr(db_user, "id"), user_data.password, db)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": str(db_user.id)},
expires_delta=access_token_expires
)
# Set JWT in httpOnly cookie
response.set_cookie(
key="access_token",
value=access_token,
httponly=True,
max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
samesite="lax",
secure=False # Set to True in production with HTTPS
)
return {
"message": "Login successful",
"user": {
"id": db_user.id,
"name": db_user.name,
"email": db_user.email
}
}

View file

@ -1,57 +0,0 @@
from fastapi import APIRouter, Depends, HTTPException, status, Response, Request
from database import db_dependency
from jose import JWTError, jwt
import models
from routers import auth
from schemas.users import UserBase
from schemas.projects import ProjectBase
from schemas.projects_users import ProjectUserBase
router = APIRouter(prefix="/me", tags=["me"])
@router.get("/", response_model=ProjectUserBase, tags=["me", "users"])
def get_me(request: Request, db: db_dependency):
"""Get current authenticated user"""
user = auth.get_user_from_jwt(request, db)
return user
@router.get("/logout", tags=["me", "auth"])
def logout(request: Request,response: Response):
"""Logout by clearing the JWT cookie"""
get_token = request.cookies.get("access_token")
if not get_token:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not logged in"
)
response.delete_cookie(key="access_token")
return {"message": "Logout successful"}
@router.delete("/delete-me", tags=["me", "auth", "users"])
def delete_me(request: Request, db: db_dependency):
"""Delete current authenticated user"""
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.commit()
## Logout user by clearing cookie
request.cookies.clear()
return {"message": "User deleted successfully"}

View file

@ -1,234 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends, Request
from typing import List, Annotated
from database import db_dependency
from schemas.tasks import TaskBase, TaskCreate, TaskUpdate
from schemas.projects import ProjectBase, ProjectCreate, ProjectUpdate, ProjectAddUser, ProjectRemoveUsers, ProjectFull
from schemas.users import UserBase
from schemas.projects_users import ProjectUserBase
from schemas.projects_tasks import ProjectTaskBase, ProjectTaskCreate
from models import Project, Task, User
from routers.auth import get_user_from_jwt
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()
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
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[ProjectFull], 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=ProjectFull)
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):
"""Get users from a specified project"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
return db_project.users
@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(User).filter(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")
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):
"""Get tasks from a specified project"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
db_tasks = db.query(Task).filter(Task.project_id == project_id).all()
return db_tasks
@router.post("/", response_model=ProjectCreate)
def create_project(project: ProjectCreate, request:Request, db: db_dependency):
"""Create a new project"""
user = get_user_from_jwt(request, db)
db_project = Project(
name=project.name,
description=project.description,
tasks=[]
)
db_project.users.append(user)
db.add(db_project)
db.commit()
db.refresh(db_project)
return db_project
@router.post("/{project_id}/tasks", response_model=ProjectTaskBase, tags=["tasks"])
def create_project_task(project_id: int, task: TaskCreate, db: db_dependency, request: Request):
"""Create a new task in a specified project"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
db_task = Task(
title=task.title,
description=task.description,
status=task.status,
project=db_project
)
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
@router.post("/{project_id}/users", response_model=ProjectFull, tags=["users"])
def add_project_user(project_id: int, user_data: ProjectAddUser, db: db_dependency, request: Request):
"""Add a user to a specified project using their email address"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
db_user = db.query(User).filter(User.email == user_data.user_email).first()
if not db_user:
raise HTTPException(status_code=404, detail="User with the specified email not found")
if db_user not in db_project.users:
db_project.users.append(db_user)
else:
raise HTTPException(status_code=400, detail="User is already a member of the project")
db.commit()
db.refresh(db_project)
return db_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)
db_project = get_project_by_id_for_user(user, project_id, db)
db_user = db.query(User).filter(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 db_project
@router.put("/{project_id}/tasks/{task_id}", response_model=TaskUpdate, tags=["tasks"])
def update_project_task(project_id: int, task_id: int, task: TaskUpdate, db: db_dependency, request: Request):
"""Update a task in 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)
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
@router.put("/{project_id}", response_model=ProjectUpdate)
def update_project(project_id: int, project: ProjectUpdate, db: db_dependency, request: Request):
"""Update a project by ID"""
user = get_user_from_jwt(request, db)
db_project = get_project_by_id_for_user(user, project_id, db)
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.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)
## Remove dangling tasks and user associations
tasks: List[Task] = db.query(Task).filter(Task.project_id == project_id).all()
for task in tasks:
db.delete(task)
db_project.tasks.remove(task)
db.delete(db_project)
db.commit()
return {"detail": "Project deleted successfully"}
@router.delete("/{project_id}/tasks/{task_id}" , tags=["tasks"])
def delete_project_task(project_id: int, task_id: int, db: db_dependency, request: Request):
"""Delete a 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)
db.delete(db_task)
db_project.tasks.remove(db_task)
db.commit()
return {"detail": "Task deleted successfully"}

View file

@ -1,76 +0,0 @@
import os
from typing import List
from fastapi import APIRouter, HTTPException, Depends, Request
from jose import JWTError, jwt
from database import db_dependency
import models
from routers import auth
import schemas.users as users
import schemas.projects as projects
from routers.auth import get_user_from_jwt
from pyargon2 import hash
router = APIRouter(prefix="/users", tags=["users"])
@router.get("/{user_id}", response_model=users.UserBase)
def read_user(user_id: int, db: db_dependency, request:Request):
"""Get a user by ID"""
get_user_from_jwt(request, db)
db_user = db.query(models.User).filter(models.User.id == user_id).first()
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
@router.get("/{user_id}/projects", response_model=List[projects.ProjectBase])
def read_projects_from_user(user_id: int, db: db_dependency, request: Request):
"""Get projects assigned to a user"""
get_user_from_jwt(request, db)
db_user = db.query(models.User).filter(models.User.id == user_id).first()
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user.projects
##
## POST endpoints
##
@router.post("/", response_model=users.UserBase)
def create_user(user: users.UserCreate, db: db_dependency):
"""Create a new user"""
user_salt = os.urandom(32).hex()
print("Generated salt:", user_salt)
hashed_password = hash(password=user.password, salt=user_salt, variant="id")
db_user = models.User(
name=user.name,
email=user.email,
password_hash=hashed_password,
password_salt=user_salt
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@router.delete("/{user_id}")
def delete_user(user_id: int, db: db_dependency):
db_user = db.query(models.User).filter(models.User.id == user_id).first()
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
db.delete(db_user)
db.commit()
return {"detail": "User deleted"}

View file

@ -1,36 +0,0 @@
from pydantic import BaseModel, ConfigDict
from typing import List, Optional
from schemas.tasks import TaskBase
from schemas.users import UserBase
class ProjectBase(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
description: str
tasks: List[TaskBase]
users: List[UserBase]
class ProjectFull(ProjectBase):
id: int
class ProjectCreate(BaseModel):
model_config = ConfigDict(from_attributes=True)
name: str
description: Optional[str] = None
tasks: List[TaskBase] = []
class ProjectUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
class ProjectAddUser(BaseModel):
user_email: str
class ProjectAddUsers(BaseModel):
user_ids: List[int] = []
class ProjectRemoveUsers(BaseModel):
user_ids: List[int] = []

View file

@ -1,11 +0,0 @@
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

@ -1,11 +0,0 @@
from typing import List
from pydantic import ConfigDict
from schemas.projects import ProjectFull
from schemas.users import UserBase
class ProjectUserBase(UserBase):
model_config = ConfigDict(from_attributes=True)
projects: List[ProjectFull]

View file

@ -1,29 +0,0 @@
from enum import Enum
from pydantic import BaseModel, ConfigDict
from typing import List, Annotated, Optional
class TaskStatus(str, Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
STASHED = "stashed"
class TaskBase(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
title: str
description: Optional[str] = None
status: TaskStatus = TaskStatus.PENDING
class TaskCreate(BaseModel):
title: str
description: Optional[str] = None
status: TaskStatus = TaskStatus.PENDING
class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
status: Optional[TaskStatus] = None

View file

@ -1,26 +0,0 @@
from pydantic import BaseModel, ConfigDict
from typing import List, Optional
class UserBase(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
class UserCreate(BaseModel):
name: str
email: str
password: str
class UserUpdateInfo(BaseModel):
name: Optional[str] = None
email: Optional[str] = None
class UserUpdatePassword(BaseModel):
password: str
new_password: str
class UserLogin(BaseModel):
email: str
password: str