- Added backend routes for DEV Portal dashboard and workflow editor - Created frontend templates for portal and editor using Jinja2 - Integrated draw.io for workflow diagram editing and saving - Developed API endpoints for features, ideas, and workflows management - Established database schema for features, ideas, and workflows - Documented DEV Portal functionality, API endpoints, and database structure
293 lines
9.3 KiB
Python
293 lines
9.3 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from app.core.database import execute_query
|
|
from typing import List, Optional, Dict, Any
|
|
from pydantic import BaseModel
|
|
from datetime import date, datetime
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter()
|
|
|
|
# Pydantic Models
|
|
class Feature(BaseModel):
|
|
id: Optional[int] = None
|
|
title: str
|
|
description: Optional[str] = None
|
|
version: Optional[str] = None
|
|
status: str = 'planlagt'
|
|
priority: int = 50
|
|
expected_date: Optional[date] = None
|
|
completed_date: Optional[date] = None
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class FeatureCreate(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
version: Optional[str] = None
|
|
status: str = 'planlagt'
|
|
priority: int = 50
|
|
expected_date: Optional[date] = None
|
|
|
|
class Idea(BaseModel):
|
|
id: Optional[int] = None
|
|
title: str
|
|
description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
votes: int = 0
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class IdeaCreate(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
|
|
class Workflow(BaseModel):
|
|
id: Optional[int] = None
|
|
title: str
|
|
description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
diagram_xml: str
|
|
thumbnail_url: Optional[str] = None
|
|
created_at: Optional[datetime] = None
|
|
updated_at: Optional[datetime] = None
|
|
|
|
class WorkflowCreate(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
diagram_xml: str
|
|
|
|
|
|
# Features/Roadmap Endpoints
|
|
@router.get("/features", response_model=List[Feature])
|
|
async def get_features(version: Optional[str] = None, status: Optional[str] = None):
|
|
"""Get all roadmap features with optional filters"""
|
|
query = "SELECT * FROM dev_features WHERE 1=1"
|
|
params = []
|
|
|
|
if version:
|
|
query += " AND version = %s"
|
|
params.append(version)
|
|
if status:
|
|
query += " AND status = %s"
|
|
params.append(status)
|
|
|
|
query += " ORDER BY priority DESC, expected_date ASC"
|
|
result = execute_query(query, tuple(params) if params else None)
|
|
return result or []
|
|
|
|
|
|
@router.get("/features/{feature_id}", response_model=Feature)
|
|
async def get_feature(feature_id: int):
|
|
"""Get a specific feature"""
|
|
result = execute_query("SELECT * FROM dev_features WHERE id = %s", (feature_id,), fetchone=True)
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Feature not found")
|
|
return result
|
|
|
|
|
|
@router.post("/features", response_model=Feature)
|
|
async def create_feature(feature: FeatureCreate):
|
|
"""Create a new roadmap feature"""
|
|
query = """
|
|
INSERT INTO dev_features (title, description, version, status, priority, expected_date)
|
|
VALUES (%s, %s, %s, %s, %s, %s)
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (
|
|
feature.title, feature.description, feature.version,
|
|
feature.status, feature.priority, feature.expected_date
|
|
), fetchone=True)
|
|
|
|
logger.info(f"✅ Created feature: {feature.title}")
|
|
return result
|
|
|
|
|
|
@router.put("/features/{feature_id}", response_model=Feature)
|
|
async def update_feature(feature_id: int, feature: FeatureCreate):
|
|
"""Update a roadmap feature"""
|
|
query = """
|
|
UPDATE dev_features
|
|
SET title = %s, description = %s, version = %s, status = %s,
|
|
priority = %s, expected_date = %s
|
|
WHERE id = %s
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (
|
|
feature.title, feature.description, feature.version,
|
|
feature.status, feature.priority, feature.expected_date, feature_id
|
|
), fetchone=True)
|
|
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Feature not found")
|
|
|
|
logger.info(f"✅ Updated feature: {feature_id}")
|
|
return result
|
|
|
|
|
|
@router.delete("/features/{feature_id}")
|
|
async def delete_feature(feature_id: int):
|
|
"""Delete a roadmap feature"""
|
|
result = execute_query("DELETE FROM dev_features WHERE id = %s RETURNING id", (feature_id,), fetchone=True)
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Feature not found")
|
|
|
|
logger.info(f"✅ Deleted feature: {feature_id}")
|
|
return {"message": "Feature deleted successfully"}
|
|
|
|
|
|
# Ideas Endpoints
|
|
@router.get("/ideas", response_model=List[Idea])
|
|
async def get_ideas(category: Optional[str] = None):
|
|
"""Get all ideas with optional category filter"""
|
|
query = "SELECT * FROM dev_ideas WHERE 1=1"
|
|
params = []
|
|
|
|
if category:
|
|
query += " AND category = %s"
|
|
params.append(category)
|
|
|
|
query += " ORDER BY votes DESC, created_at DESC"
|
|
result = execute_query(query, tuple(params) if params else None)
|
|
return result or []
|
|
|
|
|
|
@router.post("/ideas", response_model=Idea)
|
|
async def create_idea(idea: IdeaCreate):
|
|
"""Create a new idea"""
|
|
query = """
|
|
INSERT INTO dev_ideas (title, description, category)
|
|
VALUES (%s, %s, %s)
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (idea.title, idea.description, idea.category), fetchone=True)
|
|
|
|
logger.info(f"✅ Created idea: {idea.title}")
|
|
return result
|
|
|
|
|
|
@router.post("/ideas/{idea_id}/vote")
|
|
async def vote_idea(idea_id: int):
|
|
"""Increment vote count for an idea"""
|
|
query = """
|
|
UPDATE dev_ideas
|
|
SET votes = votes + 1
|
|
WHERE id = %s
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (idea_id,), fetchone=True)
|
|
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Idea not found")
|
|
|
|
return result
|
|
|
|
|
|
@router.delete("/ideas/{idea_id}")
|
|
async def delete_idea(idea_id: int):
|
|
"""Delete an idea"""
|
|
result = execute_query("DELETE FROM dev_ideas WHERE id = %s RETURNING id", (idea_id,), fetchone=True)
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Idea not found")
|
|
|
|
logger.info(f"✅ Deleted idea: {idea_id}")
|
|
return {"message": "Idea deleted successfully"}
|
|
|
|
|
|
# Workflows Endpoints
|
|
@router.get("/workflows", response_model=List[Workflow])
|
|
async def get_workflows(category: Optional[str] = None):
|
|
"""Get all workflows with optional category filter"""
|
|
query = "SELECT * FROM dev_workflows WHERE 1=1"
|
|
params = []
|
|
|
|
if category:
|
|
query += " AND category = %s"
|
|
params.append(category)
|
|
|
|
query += " ORDER BY created_at DESC"
|
|
result = execute_query(query, tuple(params) if params else None)
|
|
return result or []
|
|
|
|
|
|
@router.get("/workflows/{workflow_id}", response_model=Workflow)
|
|
async def get_workflow(workflow_id: int):
|
|
"""Get a specific workflow"""
|
|
result = execute_query("SELECT * FROM dev_workflows WHERE id = %s", (workflow_id,), fetchone=True)
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
return result
|
|
|
|
|
|
@router.post("/workflows", response_model=Workflow)
|
|
async def create_workflow(workflow: WorkflowCreate):
|
|
"""Create a new workflow diagram"""
|
|
query = """
|
|
INSERT INTO dev_workflows (title, description, category, diagram_xml)
|
|
VALUES (%s, %s, %s, %s)
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (
|
|
workflow.title, workflow.description, workflow.category, workflow.diagram_xml
|
|
), fetchone=True)
|
|
|
|
logger.info(f"✅ Created workflow: {workflow.title}")
|
|
return result
|
|
|
|
|
|
@router.put("/workflows/{workflow_id}", response_model=Workflow)
|
|
async def update_workflow(workflow_id: int, workflow: WorkflowCreate):
|
|
"""Update a workflow diagram"""
|
|
query = """
|
|
UPDATE dev_workflows
|
|
SET title = %s, description = %s, category = %s, diagram_xml = %s
|
|
WHERE id = %s
|
|
RETURNING *
|
|
"""
|
|
result = execute_query(query, (
|
|
workflow.title, workflow.description, workflow.category,
|
|
workflow.diagram_xml, workflow_id
|
|
), fetchone=True)
|
|
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
|
|
logger.info(f"✅ Updated workflow: {workflow_id}")
|
|
return result
|
|
|
|
|
|
@router.delete("/workflows/{workflow_id}")
|
|
async def delete_workflow(workflow_id: int):
|
|
"""Delete a workflow"""
|
|
result = execute_query("DELETE FROM dev_workflows WHERE id = %s RETURNING id", (workflow_id,), fetchone=True)
|
|
if not result:
|
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
|
|
logger.info(f"✅ Deleted workflow: {workflow_id}")
|
|
return {"message": "Workflow deleted successfully"}
|
|
|
|
|
|
# Stats endpoint
|
|
@router.get("/stats")
|
|
async def get_devportal_stats():
|
|
"""Get DEV Portal statistics"""
|
|
features_count = execute_query("SELECT COUNT(*) as count FROM dev_features", fetchone=True)
|
|
ideas_count = execute_query("SELECT COUNT(*) as count FROM dev_ideas", fetchone=True)
|
|
workflows_count = execute_query("SELECT COUNT(*) as count FROM dev_workflows", fetchone=True)
|
|
|
|
features_by_status = execute_query("""
|
|
SELECT status, COUNT(*) as count
|
|
FROM dev_features
|
|
GROUP BY status
|
|
""")
|
|
|
|
return {
|
|
"features_count": features_count['count'] if features_count else 0,
|
|
"ideas_count": ideas_count['count'] if ideas_count else 0,
|
|
"workflows_count": workflows_count['count'] if workflows_count else 0,
|
|
"features_by_status": features_by_status or []
|
|
}
|