kanban-app/backend/app/routes/kanban/cards.py

149 lines
4.3 KiB
Python
Raw Normal View History

from datetime import UTC, datetime
from flask import request
from flask_jwt_extended import jwt_required
from flask_pydantic import validate
from app import db
from app.decorators import load_card_owned, load_list_owned
from app.models import Board, Card, CardLabel, Label, List
from app.schemas import (CardCreateRequest, CardResponse,
CardWithDetailsResponse)
2026-02-27 10:54:34 +00:00
from app.services.card_position_service import CardPositionService
from . import kanban_bp
@kanban_bp.route("/lists/<int:list_id>/cards", methods=["POST"])
@jwt_required()
@load_list_owned
@validate(body=CardCreateRequest)
def create_card(list_id, lst, body: CardCreateRequest):
"""Create a new card in a list"""
card = Card(
name=body.name,
description=body.description,
board_id=lst.board_id,
list_id=list_id,
pos=body.pos,
due=body.due,
due_complete=body.due_complete,
badges=body.badges,
cover=body.cover,
desc_data=body.desc_data,
)
db.session.add(card)
db.session.commit()
return CardResponse.model_validate(card).model_dump(), 201
@kanban_bp.route("/cards/<int:card_id>", methods=["GET"])
@jwt_required()
@load_card_owned
def get_card(card_id, card):
"""Get a single card with full details"""
from app.models import User
card_dict = card.to_dict()
# Add labels
card_dict["labels"] = [
label.to_dict()
for label in (
db.session.query(Label)
.join(CardLabel)
.filter(CardLabel.card_id == card.id)
.all()
)
]
# Add checklists
card_dict["checklists"] = [
{
**checklist.to_dict(),
"items": [item.to_dict() for item in checklist.check_items.all()],
}
for checklist in card.checklists.all()
]
# Add comments
card_dict["comments"] = []
for comment in card.comments.all():
comment_dict = comment.to_dict()
user = db.session.get(User, comment.user_id)
comment_dict["user"] = user.to_dict() if user else None
card_dict["comments"].append(comment_dict)
response = CardWithDetailsResponse(**card_dict)
return response.model_dump(), 200
@kanban_bp.route("/cards/<int:card_id>", methods=["PUT"])
@jwt_required()
@load_card_owned
@validate(body=CardCreateRequest)
def update_card(card_id, card, body: CardCreateRequest):
"""Update a card"""
2026-02-27 10:54:34 +00:00
# Track if position or list is changing
old_position = card.pos
old_list_id = card.list_id
new_position = body.pos
new_list_id = card.list_id
# Update basic card fields
card.name = body.name
if body.description is not None:
card.description = body.description
if request.json.get("closed") is not None:
card.closed = request.json.get("closed")
card.due = body.due
card.due_complete = body.due_complete
if body.badges is not None:
card.badges = body.badges
if body.cover is not None:
card.cover = body.cover
if body.desc_data is not None:
card.desc_data = body.desc_data
# Handle moving card to different list
if "list_id" in request.json:
new_list_id = request.json["list_id"]
new_list = db.session.get(List, new_list_id)
if new_list and new_list.board_id == card.board_id:
card.list_id = new_list_id
2026-02-27 10:54:34 +00:00
# Handle position reordering
if old_list_id != new_list_id or old_position != new_position:
if old_list_id != new_list_id:
# Card moved to different list
CardPositionService.reorder_cards_between_lists(
old_list_id, new_list_id, card_id, new_position
)
else:
# Card moved within same list
CardPositionService.reorder_cards_in_list(
new_list_id, card_id, new_position
)
card.date_last_activity = datetime.now(UTC)
board = db.session.get(Board, card.board_id)
board.date_last_activity = datetime.now(UTC)
db.session.commit()
return CardResponse.model_validate(card).model_dump(), 200
@kanban_bp.route("/cards/<int:card_id>", methods=["DELETE"])
@jwt_required()
@load_card_owned
def delete_card(card_id, card):
"""Delete a card"""
db.session.delete(card)
db.session.commit()
return {"message": "Card deleted"}, 200