feat: implement full arch design dashboard #1

Open
openclaw wants to merge 38 commits from feat/full-implementation into main
3 changed files with 151 additions and 0 deletions
Showing only changes of commit 50db453ec9 - Show all commits

View File

@ -0,0 +1,47 @@
import os
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from app.shared.kernel.exceptions import NotFoundError, ValidationError
from app.shared.infrastructure.config import Settings
from app.modules.project.infrastructure.json_repository import JsonProjectRepository
from app.modules.project.application.services import ProjectService
from app.modules.project.interfaces.http.router import router as project_router, init_router as init_project_router
def create_app() -> FastAPI:
app = FastAPI(title="Arch Design Dashboard API", version="0.1.0")
# Settings
registry_path = Path(os.environ.get("REGISTRY_PATH", str(Settings().registry_path)))
# Wire Project module
project_repo = JsonProjectRepository(registry_path)
project_service = ProjectService(project_repo)
init_project_router(project_service)
# Register routers
app.include_router(project_router, prefix="/api")
# Health check
@app.get("/api/health")
def health():
return {"status": "ok"}
# Exception handlers
@app.exception_handler(NotFoundError)
async def not_found_handler(request: Request, exc: NotFoundError):
return JSONResponse(status_code=404, content={"detail": str(exc)})
@app.exception_handler(ValidationError)
async def validation_handler(request: Request, exc: ValidationError):
return JSONResponse(status_code=400, content={"detail": exc.message})
return app
# For uvicorn: use `uvicorn app.main:create_app --factory`
# or for simple usage:
app = create_app()

View File

@ -0,0 +1,60 @@
from fastapi import APIRouter, Response
from pydantic import BaseModel
from app.modules.project.application.services import ProjectService
router = APIRouter(prefix="/projects", tags=["project"])
_service: ProjectService | None = None
def init_router(service: ProjectService) -> None:
global _service
_service = service
class CreateProjectRequest(BaseModel):
name: str
design_dir: str
code_dir: str | None = None
class ProjectResponse(BaseModel):
id: str
name: str
design_dir: str
code_dir: str | None
created_at: str
def _to_response(p) -> dict:
return {
"id": p.id,
"name": p.name,
"design_dir": p.design_dir,
"code_dir": p.code_dir,
"created_at": p.created_at.isoformat(),
}
@router.get("")
def list_projects():
return [_to_response(p) for p in _service.list_projects()]
@router.post("", status_code=201)
def create_project(req: CreateProjectRequest):
p = _service.create_project(req.name, req.design_dir, req.code_dir)
return _to_response(p)
@router.get("/{project_id}")
def get_project(project_id: str):
p = _service.get_project(project_id)
return _to_response(p)
@router.delete("/{project_id}", status_code=204)
def delete_project(project_id: str):
_service.delete_project(project_id)
return Response(status_code=204)

View File

@ -0,0 +1,44 @@
import pytest
def test_health(client):
r = client.get("/api/health")
assert r.status_code == 200
assert r.json()["status"] == "ok"
def test_list_projects_empty(client):
r = client.get("/api/projects")
assert r.status_code == 200
assert r.json() == []
def test_create_and_get_project(client, design_dir):
r = client.post("/api/projects", json={"name": "test", "design_dir": str(design_dir)})
assert r.status_code == 201
pid = r.json()["id"]
r = client.get(f"/api/projects/{pid}")
assert r.status_code == 200
assert r.json()["name"] == "test"
def test_create_project_invalid_dir(client):
r = client.post("/api/projects", json={"name": "test", "design_dir": "/nonexistent"})
assert r.status_code == 400
def test_delete_project(client, design_dir):
r = client.post("/api/projects", json={"name": "test", "design_dir": str(design_dir)})
pid = r.json()["id"]
r = client.delete(f"/api/projects/{pid}")
assert r.status_code == 204
r = client.get(f"/api/projects/{pid}")
assert r.status_code == 404
def test_get_nonexistent_project(client):
r = client.get("/api/projects/nonexistent")
assert r.status_code == 404