kanban-app/backend/app/routes/kanban/epics.py
2026-05-01 18:03:00 +03:00

170 lines
4.8 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.decorators import serialize_list_response, serialize_response
from app.models import Board, Card, Epic
from app.schemas import (CardResponse, EpicCreateRequest, EpicResponse,
EpicUpdateRequest)
from . import kanban_bp
@kanban_bp.route("/boards/<int:board_id>/epics", methods=["GET"])
@jwt_required()
@serialize_list_response(EpicResponse)
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.active().filter_by(board_id=board_id).all()
return epics, 200
@kanban_bp.route("/boards/<int:board_id>/epics", methods=["POST"])
@jwt_required()
@validate(body=EpicCreateRequest)
@serialize_response(EpicResponse)
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 epic, 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.active().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)
@serialize_response(EpicResponse)
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 epic, 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.active().filter_by(epic_id=epic_id).update({"epic_id": None})
# Delete epic
epic.soft_delete()
db.session.commit()
return {"message": "Epic deleted successfully"}, 200
@kanban_bp.route("/cards/<int:card_id>/epics", methods=["POST"])
@jwt_required()
@serialize_response(CardResponse)
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, 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