Agent turn complete

This commit is contained in:
VibeCoder
2026-09-07 19:32:02 +05:30 Unverified
commit 0a87bf68b5
39 changed files with 4301 additions and 0 deletions
View File
+37
View File
@@ -0,0 +1,37 @@
"""Database configuration and session management."""
import os
from pathlib import Path
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
# Ensure data directory exists
data_dir = Path("./data")
data_dir.mkdir(exist_ok=True)
# Use SQLite database
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./data/app.db")
# Create engine with SQLite-specific settings
engine = create_engine(
DATABASE_URL,
connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {},
echo=False,
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
"""Dependency for getting database session."""
db = SessionLocal()
try:
yield db
finally:
db.close()
def init_db():
"""Initialize database tables."""
Base.metadata.create_all(bind=engine)
+28
View File
@@ -0,0 +1,28 @@
"""FastAPI entry point. Deploy imports ``app.main:app`` — keep both names."""
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routes import api_router
from app.database import init_db
app = FastAPI(title=os.getenv("APP_NAME", "app"))
# Initialize database tables on startup
init_db()
# Allowed origins come from the environment so the deployed app and local dev
# use the same code path. "*" only when nothing is configured (dev default).
_origins = [o.strip() for o in os.getenv("CORS_ORIGINS", "*").split(",") if o.strip()]
app.add_middleware(
CORSMiddleware,
allow_origins=_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# The one place the /api prefix is applied. Route modules must not repeat it.
app.include_router(api_router, prefix="/api")
+23
View File
@@ -0,0 +1,23 @@
"""SQLAlchemy models for the kanban board."""
from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, Float
from app.database import Base
class Task(Base):
"""Task model for kanban board."""
__tablename__ = "tasks"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(255), nullable=False, index=True)
description = Column(Text, nullable=True)
priority = Column(String(50), nullable=False, default="medium") # low, medium, high, urgent
status = Column(String(50), nullable=False, default="TODO", index=True) # TODO, IN PROGRESS, QA, UAT, PROD
assignee = Column(String(255), nullable=True)
tags = Column(String(500), nullable=True) # Comma-separated tags
time_estimate = Column(Float, nullable=True) # Hours
due_date = Column(DateTime, nullable=True)
created_at = Column(DateTime, nullable=False, default=datetime.utcnow)
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
+16
View File
@@ -0,0 +1,16 @@
"""Route barrel — every router the app serves is mounted here.
Adding a resource is two steps: create ``app/routes/<name>.py`` with a
module-level ``router``, then append one ``include_router`` line below. A route
file that is never mounted 404s silently.
"""
from fastapi import APIRouter
from app.routes import health, items, tasks
api_router = APIRouter()
api_router.include_router(health.router, tags=["health"])
api_router.include_router(items.router, tags=["items"])
api_router.include_router(tasks.router)
+15
View File
@@ -0,0 +1,15 @@
"""Liveness endpoint. The shape every other route module copies."""
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter()
class Health(BaseModel):
status: str
@router.get("/health", response_model=Health)
def health() -> Health:
return Health(status="ok")
+21
View File
@@ -0,0 +1,21 @@
"""One demo resource. Its rows mirror the frontend seed, so Preview and the
deployed app render the same screen. Replace both together."""
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter()
class Item(BaseModel):
id: int
name: str
status: str
@router.get("/items", response_model=list[Item])
def list_items() -> list[Item]:
return [
Item(id=1, name="Draft the launch checklist", status="in progress"),
Item(id=2, name="Review onboarding copy", status="todo"),
]
+92
View File
@@ -0,0 +1,92 @@
"""Task management routes for the kanban board."""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import Task
from app.schemas import TaskCreate, TaskUpdate, TaskResponse, TaskListResponse
router = APIRouter(prefix="/tasks", tags=["tasks"])
@router.post("", response_model=TaskResponse)
def create_task(task: TaskCreate, db: Session = Depends(get_db)):
"""Create a new task."""
db_task = Task(**task.model_dump())
db.add(db_task)
db.commit()
db.refresh(db_task)
return db_task
@router.get("", response_model=TaskListResponse)
def list_tasks(
status: Optional[str] = Query(None),
priority: Optional[str] = Query(None),
tags: Optional[str] = Query(None),
search: Optional[str] = Query(None),
db: Session = Depends(get_db),
):
"""List tasks with optional filtering."""
query = db.query(Task)
# Filter by status
if status:
query = query.filter(Task.status == status)
# Filter by priority
if priority:
query = query.filter(Task.priority == priority)
# Filter by tags (partial match)
if tags:
query = query.filter(Task.tags.ilike(f"%{tags}%"))
# Search in title and description
if search:
query = query.filter(
(Task.title.ilike(f"%{search}%")) | (Task.description.ilike(f"%{search}%"))
)
tasks = query.order_by(Task.created_at.desc()).all()
return TaskListResponse(tasks=tasks, total=len(tasks))
@router.get("/{task_id}", response_model=TaskResponse)
def get_task(task_id: int, db: Session = Depends(get_db)):
"""Get a specific task by ID."""
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
return task
@router.put("/{task_id}", response_model=TaskResponse)
def update_task(task_id: int, task_update: TaskUpdate, db: Session = Depends(get_db)):
"""Update a task."""
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Update only provided fields
update_data = task_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(task, field, value)
db.commit()
db.refresh(task)
return task
@router.delete("/{task_id}")
def delete_task(task_id: int, db: Session = Depends(get_db)):
"""Delete a task."""
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(status_code=404, detail="Task not found")
db.delete(task)
db.commit()
return {"message": "Task deleted successfully"}
+55
View File
@@ -0,0 +1,55 @@
"""Pydantic schemas for request/response validation."""
from datetime import datetime
from typing import Optional, List
from pydantic import BaseModel
class TaskBase(BaseModel):
"""Base task schema with common fields."""
title: str
description: Optional[str] = None
priority: str = "medium"
status: str = "TODO"
assignee: Optional[str] = None
tags: Optional[str] = None
time_estimate: Optional[float] = None
due_date: Optional[datetime] = None
class TaskCreate(TaskBase):
"""Schema for creating a task."""
pass
class TaskUpdate(BaseModel):
"""Schema for updating a task."""
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[str] = None
status: Optional[str] = None
assignee: Optional[str] = None
tags: Optional[str] = None
time_estimate: Optional[float] = None
due_date: Optional[datetime] = None
class TaskResponse(TaskBase):
"""Schema for task response."""
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class TaskListResponse(BaseModel):
"""Schema for task list response."""
tasks: List[TaskResponse]
total: int