164 lines
4.7 KiB
Python
164 lines
4.7 KiB
Python
"""Routes for Epic CRUD operations."""
|
|
|
|
from flask import request
|
|
from flask_jwt_extended import jwt_required
|
|
from flask_pydantic import validate
|
|
|
|
from app import db
|
|
from app.models import Board, Card, Epic
|
|
from app.schemas import EpicCreateRequest, EpicResponse, EpicUpdateRequest
|
|
|
|
from . import kanban_bp
|
|
|
|
|
|
@kanban_bp.route("/boards/<int:board_id>/epics", methods=["GET"])
|
|
@jwt_required()
|
|
def get_board_epics(board_id):
|
|
"""Get all epics for a board."""
|
|
board = db.session.get(Board, board_id)
|
|
if not board:
|
|
return {"error": "Board not found"}, 404
|
|
|
|
epics = Epic.query.filter_by(board_id=board_id).all()
|
|
return [EpicResponse.model_validate(epic).model_dump() for epic in epics], 200
|
|
|
|
|
|
@kanban_bp.route("/boards/<int:board_id>/epics", methods=["POST"])
|
|
@jwt_required()
|
|
@validate(body=EpicCreateRequest)
|
|
def create_epic(board_id, body: EpicCreateRequest):
|
|
"""Create a new epic for a board."""
|
|
board = db.session.get(Board, board_id)
|
|
if not board:
|
|
return {"error": "Board not found"}, 404
|
|
|
|
epic = Epic(
|
|
board_id=board_id,
|
|
name=body.name,
|
|
description=body.description,
|
|
content=body.content,
|
|
color=body.color,
|
|
closed=False,
|
|
pos=body.pos,
|
|
depth_limit=body.depth_limit,
|
|
parent_epic_id=body.parent_epic_id,
|
|
completed_list_id=body.completed_list_id,
|
|
metrics={"card_count": 0, "completed_cards_count": 0},
|
|
)
|
|
|
|
db.session.add(epic)
|
|
db.session.commit()
|
|
|
|
return EpicResponse.model_validate(epic).model_dump(), 201
|
|
|
|
|
|
@kanban_bp.route("/epics/<int:epic_id>", methods=["GET"])
|
|
@jwt_required()
|
|
def get_epic(epic_id):
|
|
"""Get a specific epic with details."""
|
|
epic = db.session.get(Epic, epic_id)
|
|
if not epic:
|
|
return {"error": "Epic not found"}, 404
|
|
|
|
# Get cards for this epic
|
|
cards = Card.query.filter_by(epic_id=epic_id).all()
|
|
epic_dict = EpicResponse.model_validate(epic).model_dump()
|
|
epic_dict["cards"] = [card.to_dict() for card in cards]
|
|
|
|
return epic_dict, 200
|
|
|
|
|
|
def _update_epic_fields(epic: Epic, body: EpicUpdateRequest) -> None:
|
|
"""Update epic fields from request body."""
|
|
if body.name is not None:
|
|
epic.name = body.name
|
|
if body.description is not None:
|
|
epic.description = body.description
|
|
if body.content is not None:
|
|
epic.content = body.content
|
|
if body.color is not None:
|
|
epic.color = body.color
|
|
if body.closed is not None:
|
|
epic.closed = body.closed
|
|
if body.pos is not None:
|
|
epic.pos = body.pos
|
|
if body.depth_limit is not None:
|
|
epic.depth_limit = body.depth_limit
|
|
if body.parent_epic_id is not None:
|
|
epic.parent_epic_id = body.parent_epic_id
|
|
if body.completed_list_id is not None:
|
|
epic.completed_list_id = body.completed_list_id
|
|
|
|
|
|
@kanban_bp.route("/epics/<int:epic_id>", methods=["PUT"])
|
|
@jwt_required()
|
|
@validate(body=EpicUpdateRequest)
|
|
def update_epic(epic_id, body: EpicUpdateRequest):
|
|
"""Update an epic."""
|
|
epic = db.session.get(Epic, epic_id)
|
|
if not epic:
|
|
return {"error": "Epic not found"}, 404
|
|
|
|
_update_epic_fields(epic, body)
|
|
db.session.commit()
|
|
|
|
return EpicResponse.model_validate(epic).model_dump(), 200
|
|
|
|
|
|
@kanban_bp.route("/epics/<int:epic_id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def delete_epic(epic_id):
|
|
"""Delete an epic."""
|
|
epic = db.session.get(Epic, epic_id)
|
|
if not epic:
|
|
return {"error": "Epic not found"}, 404
|
|
|
|
# Unlink all cards from this epic
|
|
Card.query.filter_by(epic_id=epic_id).update({"epic_id": None})
|
|
|
|
# Delete epic
|
|
db.session.delete(epic)
|
|
db.session.commit()
|
|
|
|
return {"message": "Epic deleted successfully"}, 200
|
|
|
|
|
|
@kanban_bp.route("/cards/<int:card_id>/epics", methods=["POST"])
|
|
@jwt_required()
|
|
def add_epic_to_card(card_id):
|
|
"""Link an epic to a card."""
|
|
card = db.session.get(Card, card_id)
|
|
if not card:
|
|
return {"error": "Card not found"}, 404
|
|
|
|
data = request.get_json()
|
|
epic_id = data.get("epic_id")
|
|
|
|
if not epic_id:
|
|
return {"error": "epic_id is required"}, 400
|
|
|
|
epic = db.session.get(Epic, epic_id)
|
|
if not epic:
|
|
return {"error": "Epic not found"}, 404
|
|
|
|
card.epic_id = epic_id
|
|
db.session.commit()
|
|
|
|
return card.to_dict(), 200
|
|
|
|
|
|
@kanban_bp.route("/cards/<int:card_id>/epics/<int:epic_id>", methods=["DELETE"])
|
|
@jwt_required()
|
|
def remove_epic_from_card(card_id, epic_id):
|
|
"""Unlink an epic from a card."""
|
|
card = db.session.get(Card, card_id)
|
|
if not card:
|
|
return {"error": "Card not found"}, 404
|
|
|
|
if card.epic_id != epic_id:
|
|
return {"error": "Card is not linked to this epic"}, 400
|
|
|
|
card.epic_id = None
|
|
db.session.commit()
|
|
|
|
return {"message": "Epic unlinked from card successfully"}, 200
|