Refactor backend to SQLModel; reset schema; add Company OS endpoints

This commit is contained in:
Abhimanyu Saharan
2026-02-01 23:16:56 +05:30
parent b37e7dd841
commit aa6b0c807b
56 changed files with 867 additions and 450 deletions

View File

@@ -6,16 +6,18 @@ from alembic import context
from sqlalchemy import engine_from_config, pool
from app.core.config import settings
from app.db.base import Base
from app.models import task # noqa: F401
from sqlmodel import SQLModel
# Import models to register tables in metadata
from app import models # noqa: F401
config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# Use SQLAlchemy models metadata
target_metadata = Base.metadata
target_metadata = SQLModel.metadata
def get_url() -> str:

View File

@@ -0,0 +1,32 @@
"""initial company os (sqlmodel)
Revision ID: 157587037601
Revises:
Create Date: 2026-02-01 23:16:42.890750
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '157587037601'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,43 +0,0 @@
"""create tasks
Revision ID: ce4f1502f674
Revises:
Create Date: 2026-02-01 22:23:55.940851
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ce4f1502f674'
down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tasks',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('status', sa.String(length=32), nullable=False),
sa.Column('assignee', sa.String(length=120), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tasks_id'), 'tasks', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_tasks_id'), table_name='tasks')
op.drop_table('tasks')
# ### end Alembic commands ###

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,30 @@
from __future__ import annotations
import json
from fastapi import APIRouter, Depends
from sqlmodel import Session, select
from app.db.session import get_session
from app.models.activity import Activity
router = APIRouter(prefix="/activities", tags=["activities"])
@router.get("")
def list_activities(limit: int = 50, session: Session = Depends(get_session)):
items = session.exec(select(Activity).order_by(Activity.id.desc()).limit(max(1, min(limit, 200)))).all()
out = []
for a in items:
out.append(
{
"id": a.id,
"actor_employee_id": a.actor_employee_id,
"entity_type": a.entity_type,
"entity_id": a.entity_id,
"verb": a.verb,
"payload": json.loads(a.payload_json) if a.payload_json else None,
"created_at": a.created_at,
}
)
return out

61
backend/app/api/hr.py Normal file
View File

@@ -0,0 +1,61 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from app.api.utils import log_activity
from app.db.session import get_session
from app.models.hr import EmploymentAction, HeadcountRequest
from app.schemas.hr import EmploymentActionCreate, HeadcountRequestCreate, HeadcountRequestUpdate
router = APIRouter(prefix="/hr", tags=["hr"])
@router.get("/headcount", response_model=list[HeadcountRequest])
def list_headcount_requests(session: Session = Depends(get_session)):
return session.exec(select(HeadcountRequest).order_by(HeadcountRequest.id.desc())).all()
@router.post("/headcount", response_model=HeadcountRequest)
def create_headcount_request(payload: HeadcountRequestCreate, session: Session = Depends(get_session)):
req = HeadcountRequest(**payload.model_dump())
session.add(req)
session.commit()
session.refresh(req)
log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="submitted")
session.commit()
return req
@router.patch("/headcount/{request_id}", response_model=HeadcountRequest)
def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, session: Session = Depends(get_session)):
req = session.get(HeadcountRequest, request_id)
if not req:
raise HTTPException(status_code=404, detail="Request not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(req, k, v)
session.add(req)
session.commit()
session.refresh(req)
log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="updated", payload=data)
session.commit()
return req
@router.get("/actions", response_model=list[EmploymentAction])
def list_employment_actions(session: Session = Depends(get_session)):
return session.exec(select(EmploymentAction).order_by(EmploymentAction.id.desc())).all()
@router.post("/actions", response_model=EmploymentAction)
def create_employment_action(payload: EmploymentActionCreate, session: Session = Depends(get_session)):
action = EmploymentAction(**payload.model_dump())
session.add(action)
session.commit()
session.refresh(action)
log_activity(session, actor_employee_id=action.issued_by_employee_id, entity_type="employment_action", entity_id=action.id, verb=action.action_type, payload={"employee_id": action.employee_id})
session.commit()
return action

79
backend/app/api/org.py Normal file
View File

@@ -0,0 +1,79 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from app.api.utils import log_activity
from app.db.session import get_session
from app.models.org import Department, Employee
from app.schemas.org import DepartmentCreate, DepartmentUpdate, EmployeeCreate, EmployeeUpdate
router = APIRouter(tags=["org"])
@router.get("/departments", response_model=list[Department])
def list_departments(session: Session = Depends(get_session)):
return session.exec(select(Department).order_by(Department.name.asc())).all()
@router.post("/departments", response_model=Department)
def create_department(payload: DepartmentCreate, session: Session = Depends(get_session)):
dept = Department(name=payload.name, head_employee_id=payload.head_employee_id)
session.add(dept)
session.commit()
session.refresh(dept)
log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="created", payload={"name": dept.name})
session.commit()
return dept
@router.patch("/departments/{department_id}", response_model=Department)
def update_department(department_id: int, payload: DepartmentUpdate, session: Session = Depends(get_session)):
dept = session.get(Department, department_id)
if not dept:
raise HTTPException(status_code=404, detail="Department not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(dept, k, v)
session.add(dept)
session.commit()
session.refresh(dept)
log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="updated", payload=data)
session.commit()
return dept
@router.get("/employees", response_model=list[Employee])
def list_employees(session: Session = Depends(get_session)):
return session.exec(select(Employee).order_by(Employee.id.asc())).all()
@router.post("/employees", response_model=Employee)
def create_employee(payload: EmployeeCreate, session: Session = Depends(get_session)):
emp = Employee(**payload.model_dump())
session.add(emp)
session.commit()
session.refresh(emp)
log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="created", payload={"name": emp.name, "type": emp.employee_type})
session.commit()
return emp
@router.patch("/employees/{employee_id}", response_model=Employee)
def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session = Depends(get_session)):
emp = session.get(Employee, employee_id)
if not emp:
raise HTTPException(status_code=404, detail="Employee not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(emp, k, v)
session.add(emp)
session.commit()
session.refresh(emp)
log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="updated", payload=data)
session.commit()
return emp

View File

@@ -0,0 +1,45 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from app.api.utils import log_activity
from app.db.session import get_session
from app.models.projects import Project
from app.schemas.projects import ProjectCreate, ProjectUpdate
router = APIRouter(prefix="/projects", tags=["projects"])
@router.get("", response_model=list[Project])
def list_projects(session: Session = Depends(get_session)):
return session.exec(select(Project).order_by(Project.name.asc())).all()
@router.post("", response_model=Project)
def create_project(payload: ProjectCreate, session: Session = Depends(get_session)):
proj = Project(**payload.model_dump())
session.add(proj)
session.commit()
session.refresh(proj)
log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name})
session.commit()
return proj
@router.patch("/{project_id}", response_model=Project)
def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session)):
proj = session.get(Project, project_id)
if not proj:
raise HTTPException(status_code=404, detail="Project not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(proj, k, v)
session.add(proj)
session.commit()
session.refresh(proj)
log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="updated", payload=data)
session.commit()
return proj

View File

@@ -1,64 +0,0 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.db.session import SessionLocal
from app.models.task import Task
from app.schemas.task import TaskCreate, TaskOut, TaskUpdate
router = APIRouter(prefix="/tasks", tags=["tasks"])
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get("", response_model=list[TaskOut])
def list_tasks(db: Session = Depends(get_db)):
return db.query(Task).order_by(Task.id.desc()).all()
@router.post("", response_model=TaskOut)
def create_task(payload: TaskCreate, db: Session = Depends(get_db)):
task = Task(
title=payload.title,
description=payload.description,
status=payload.status,
assignee=payload.assignee,
)
db.add(task)
db.commit()
db.refresh(task)
return task
@router.patch("/{task_id}", response_model=TaskOut)
def update_task(task_id: int, payload: TaskUpdate, db: Session = Depends(get_db)):
task = db.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(task, k, v)
db.add(task)
db.commit()
db.refresh(task)
return task
@router.delete("/{task_id}")
def delete_task(task_id: int, db: Session = Depends(get_db)):
task = db.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
return {"ok": True}

28
backend/app/api/utils.py Normal file
View File

@@ -0,0 +1,28 @@
from __future__ import annotations
import json
from typing import Any
from sqlmodel import Session
from app.models.activity import Activity
def log_activity(
session: Session,
*,
actor_employee_id: int | None,
entity_type: str,
entity_id: int | None,
verb: str,
payload: dict[str, Any] | None = None,
) -> None:
session.add(
Activity(
actor_employee_id=actor_employee_id,
entity_type=entity_type,
entity_id=entity_id,
verb=verb,
payload_json=json.dumps(payload) if payload is not None else None,
)
)

80
backend/app/api/work.py Normal file
View File

@@ -0,0 +1,80 @@
from __future__ import annotations
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session, select
from app.api.utils import log_activity
from app.db.session import get_session
from app.models.work import Task, TaskComment
from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate
router = APIRouter(tags=["work"])
@router.get("/tasks", response_model=list[Task])
def list_tasks(project_id: int | None = None, session: Session = Depends(get_session)):
stmt = select(Task).order_by(Task.id.asc())
if project_id is not None:
stmt = stmt.where(Task.project_id == project_id)
return session.exec(stmt).all()
@router.post("/tasks", response_model=Task)
def create_task(payload: TaskCreate, session: Session = Depends(get_session)):
task = Task(**payload.model_dump())
task.updated_at = datetime.utcnow()
session.add(task)
session.commit()
session.refresh(task)
log_activity(session, actor_employee_id=task.created_by_employee_id, entity_type="task", entity_id=task.id, verb="created", payload={"project_id": task.project_id, "title": task.title})
session.commit()
return task
@router.patch("/tasks/{task_id}", response_model=Task)
def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(get_session)):
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
data = payload.model_dump(exclude_unset=True)
for k, v in data.items():
setattr(task, k, v)
task.updated_at = datetime.utcnow()
session.add(task)
session.commit()
session.refresh(task)
log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task.id, verb="updated", payload=data)
session.commit()
return task
@router.delete("/tasks/{task_id}")
def delete_task(task_id: int, session: Session = Depends(get_session)):
task = session.get(Task, task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
session.delete(task)
session.commit()
log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task_id, verb="deleted")
session.commit()
return {"ok": True}
@router.get("/task-comments", response_model=list[TaskComment])
def list_task_comments(task_id: int, session: Session = Depends(get_session)):
return session.exec(select(TaskComment).where(TaskComment.task_id == task_id).order_by(TaskComment.id.asc())).all()
@router.post("/task-comments", response_model=TaskComment)
def create_task_comment(payload: TaskCommentCreate, session: Session = Depends(get_session)):
c = TaskComment(**payload.model_dump())
session.add(c)
session.commit()
session.refresh(c)
log_activity(session, actor_employee_id=c.author_employee_id, entity_type="task", entity_id=c.task_id, verb="commented")
session.commit()
return c

View File

@@ -1,5 +0,0 @@
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass

View File

@@ -1,9 +1,16 @@
from __future__ import annotations
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlmodel import Session, SQLModel, create_engine
from app.core.config import settings
engine = create_engine(settings.database_url, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
engine = create_engine(settings.database_url, echo=False)
def init_db() -> None:
SQLModel.metadata.create_all(engine)
def get_session():
with Session(engine) as session:
yield session

View File

@@ -3,10 +3,15 @@ from __future__ import annotations
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.tasks import router as tasks_router
from app.api.activities import router as activities_router
from app.api.hr import router as hr_router
from app.api.org import router as org_router
from app.api.projects import router as projects_router
from app.api.work import router as work_router
from app.core.config import settings
from app.db.session import init_db
app = FastAPI(title="OpenClaw Agency API", version="0.1.0")
app = FastAPI(title="OpenClaw Agency API", version="0.3.0")
origins = [o.strip() for o in settings.cors_origins.split(",") if o.strip()]
if origins:
@@ -14,11 +19,21 @@ if origins:
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"] ,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(tasks_router)
@app.on_event("startup")
def on_startup() -> None:
init_db()
app.include_router(org_router)
app.include_router(projects_router)
app.include_router(work_router)
app.include_router(hr_router)
app.include_router(activities_router)
@app.get("/health")

View File

@@ -1,2 +1,17 @@
# Import models here so Alembic can discover them
from .task import Task # noqa: F401
from app.models.org import Department, Employee
from app.models.projects import Project, ProjectMember
from app.models.work import Task, TaskComment
from app.models.hr import HeadcountRequest, EmploymentAction
from app.models.activity import Activity
__all__ = [
"Department",
"Employee",
"Project",
"ProjectMember",
"Task",
"TaskComment",
"HeadcountRequest",
"EmploymentAction",
"Activity",
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,19 @@
from __future__ import annotations
from datetime import datetime
from sqlmodel import Field, SQLModel
class Activity(SQLModel, table=True):
__tablename__ = "activities"
id: int | None = Field(default=None, primary_key=True)
actor_employee_id: int | None = Field(default=None, foreign_key="employees.id")
entity_type: str
entity_id: int | None = None
verb: str
payload_json: str | None = None
created_at: datetime = Field(default_factory=datetime.utcnow)

35
backend/app/models/hr.py Normal file
View File

@@ -0,0 +1,35 @@
from __future__ import annotations
from datetime import datetime
from sqlmodel import Field, SQLModel
class HeadcountRequest(SQLModel, table=True):
__tablename__ = "headcount_requests"
id: int | None = Field(default=None, primary_key=True)
department_id: int = Field(foreign_key="departments.id")
requested_by_manager_id: int = Field(foreign_key="employees.id")
role_title: str
employee_type: str # human | agent
quantity: int = Field(default=1)
justification: str | None = None
status: str = Field(default="submitted")
created_at: datetime = Field(default_factory=datetime.utcnow)
class EmploymentAction(SQLModel, table=True):
__tablename__ = "employment_actions"
id: int | None = Field(default=None, primary_key=True)
employee_id: int = Field(foreign_key="employees.id")
issued_by_employee_id: int = Field(foreign_key="employees.id")
action_type: str # praise|warning|pip|termination
notes: str | None = None
created_at: datetime = Field(default_factory=datetime.utcnow)

27
backend/app/models/org.py Normal file
View File

@@ -0,0 +1,27 @@
from __future__ import annotations
from typing import Optional
from sqlmodel import Field, SQLModel
class Department(SQLModel, table=True):
__tablename__ = "departments"
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
head_employee_id: int | None = Field(default=None, foreign_key="employees.id")
class Employee(SQLModel, table=True):
__tablename__ = "employees"
id: int | None = Field(default=None, primary_key=True)
name: str
employee_type: str # human | agent
department_id: int | None = Field(default=None, foreign_key="departments.id")
manager_id: int | None = Field(default=None, foreign_key="employees.id")
title: str | None = None
status: str = Field(default="active")

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
from sqlmodel import Field, SQLModel
class Project(SQLModel, table=True):
__tablename__ = "projects"
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
status: str = Field(default="active")
class ProjectMember(SQLModel, table=True):
__tablename__ = "project_members"
id: int | None = Field(default=None, primary_key=True)
project_id: int = Field(foreign_key="projects.id")
employee_id: int = Field(foreign_key="employees.id")
role: str | None = None

View File

@@ -1,28 +0,0 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import DateTime, Integer, String, Text
from sqlalchemy.sql import func
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
title: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
# kanban columns: todo | doing | done
status: Mapped[str] = mapped_column(String(32), nullable=False, default="todo")
# simple attribution (no auth)
assignee: Mapped[str | None] = mapped_column(String(120), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from datetime import datetime
from sqlmodel import Field, SQLModel
class Task(SQLModel, table=True):
__tablename__ = "tasks"
id: int | None = Field(default=None, primary_key=True)
project_id: int = Field(foreign_key="projects.id", index=True)
title: str
description: str | None = None
status: str = Field(default="backlog", index=True)
assignee_employee_id: int | None = Field(default=None, foreign_key="employees.id")
reviewer_employee_id: int | None = Field(default=None, foreign_key="employees.id")
created_by_employee_id: int | None = Field(default=None, foreign_key="employees.id")
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
class TaskComment(SQLModel, table=True):
__tablename__ = "task_comments"
id: int | None = Field(default=None, primary_key=True)
task_id: int = Field(foreign_key="tasks.id", index=True)
author_employee_id: int | None = Field(default=None, foreign_key="employees.id")
body: str
created_at: datetime = Field(default_factory=datetime.utcnow)

Binary file not shown.

Binary file not shown.

Binary file not shown.

24
backend/app/schemas/hr.py Normal file
View File

@@ -0,0 +1,24 @@
from __future__ import annotations
from sqlmodel import SQLModel
class HeadcountRequestCreate(SQLModel):
department_id: int
requested_by_manager_id: int
role_title: str
employee_type: str
quantity: int = 1
justification: str | None = None
class HeadcountRequestUpdate(SQLModel):
status: str | None = None
justification: str | None = None
class EmploymentActionCreate(SQLModel):
employee_id: int
issued_by_employee_id: int
action_type: str
notes: str | None = None

View File

@@ -0,0 +1,31 @@
from __future__ import annotations
from sqlmodel import SQLModel
class DepartmentCreate(SQLModel):
name: str
head_employee_id: int | None = None
class DepartmentUpdate(SQLModel):
name: str | None = None
head_employee_id: int | None = None
class EmployeeCreate(SQLModel):
name: str
employee_type: str
department_id: int | None = None
manager_id: int | None = None
title: str | None = None
status: str = "active"
class EmployeeUpdate(SQLModel):
name: str | None = None
employee_type: str | None = None
department_id: int | None = None
manager_id: int | None = None
title: str | None = None
status: str | None = None

View File

@@ -0,0 +1,13 @@
from __future__ import annotations
from sqlmodel import SQLModel
class ProjectCreate(SQLModel):
name: str
status: str = "active"
class ProjectUpdate(SQLModel):
name: str | None = None
status: str | None = None

View File

@@ -1,35 +0,0 @@
from __future__ import annotations
from datetime import datetime
from typing import Literal
from pydantic import BaseModel, Field
TaskStatus = Literal["todo", "doing", "done"]
class TaskCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
description: str | None = None
status: TaskStatus = "todo"
assignee: str | None = None
class TaskUpdate(BaseModel):
title: str | None = Field(default=None, min_length=1, max_length=200)
description: str | None = None
status: TaskStatus | None = None
assignee: str | None = None
class TaskOut(BaseModel):
id: int
title: str
description: str | None
status: TaskStatus
assignee: str | None
created_at: datetime
updated_at: datetime | None
model_config = {"from_attributes": True}

View File

@@ -0,0 +1,27 @@
from __future__ import annotations
from sqlmodel import SQLModel
class TaskCreate(SQLModel):
project_id: int
title: str
description: str | None = None
status: str = "backlog"
assignee_employee_id: int | None = None
reviewer_employee_id: int | None = None
created_by_employee_id: int | None = None
class TaskUpdate(SQLModel):
title: str | None = None
description: str | None = None
status: str | None = None
assignee_employee_id: int | None = None
reviewer_employee_id: int | None = None
class TaskCommentCreate(SQLModel):
task_id: int
author_employee_id: int | None = None
body: str

View File

@@ -1,26 +1,7 @@
alembic==1.18.3
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
click==8.3.1
fastapi==0.128.0
greenlet==3.3.1
h11==0.16.0
httptools==0.7.1
idna==3.11
Mako==1.3.10
MarkupSafe==3.0.3
psycopg2-binary==2.9.11
pydantic==2.12.5
pydantic-settings==2.12.0
pydantic_core==2.41.5
python-dotenv==1.2.1
PyYAML==6.0.3
SQLAlchemy==2.0.46
starlette==0.50.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uvicorn==0.40.0
uvloop==0.22.1
watchfiles==1.1.1
websockets==16.0
fastapi
uvicorn[standard]
sqlmodel
alembic
psycopg2-binary
python-dotenv
pydantic-settings