532 lines
17 KiB
Go
532 lines
17 KiB
Go
package kanban
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
)
|
|
|
|
// Handler handles kanban HTTP requests
|
|
type Handler struct {
|
|
service Service
|
|
}
|
|
|
|
// NewHandler creates a new kanban handler
|
|
func NewHandler(service Service) *Handler {
|
|
return &Handler{
|
|
service: service,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers kanban routes on the Echo group passed by the caller.
|
|
// The caller MUST mount a /kanban subgroup first (e.g. adminV1.Group("/kanban") or v1.Group("/kanban"))
|
|
// so paths resolve to .../kanban/boards, not .../boards.
|
|
func (h *Handler) RegisterRoutes(router *echo.Group) {
|
|
router.POST("/boards", h.CreateBoard)
|
|
router.GET("/boards", h.GetBoardsByUser)
|
|
router.GET("/boards/default", h.GetDefaultBoard)
|
|
router.GET("/boards/:id", h.GetBoardByID)
|
|
router.PUT("/boards/:id", h.UpdateBoard)
|
|
router.DELETE("/boards/:id", h.DeleteBoard)
|
|
|
|
router.POST("/columns", h.CreateColumn)
|
|
router.PUT("/columns/:id", h.UpdateColumn)
|
|
router.DELETE("/columns/:id", h.DeleteColumn)
|
|
router.PUT("/columns/reorder", h.ReorderColumns)
|
|
|
|
router.POST("/cards", h.CreateCard)
|
|
router.GET("/cards/:id", h.GetCardByID)
|
|
router.PUT("/cards/:id", h.UpdateCard)
|
|
router.DELETE("/cards/:id", h.DeleteCard)
|
|
router.PUT("/cards/move", h.MoveCard)
|
|
router.PUT("/cards/reorder", h.ReorderCards)
|
|
}
|
|
|
|
// CreateBoard creates a new kanban board
|
|
// @Summary Create a new kanban board
|
|
// @Description Create a new kanban board with the provided information
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param board body CreateBoardRequest true "Board information"
|
|
// @Success 201 {object} response.Response{data=BoardResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards [post]
|
|
func (h *Handler) CreateBoard(c echo.Context) error {
|
|
var req CreateBoardRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
req.UserID = ownerID
|
|
|
|
board, err := h.service.CreateBoard(c.Request().Context(), req)
|
|
if err := handleServiceError(c, err, "Failed to create board"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Created(c, board, "Board created successfully")
|
|
}
|
|
|
|
// GetBoardByID retrieves a board by ID
|
|
// @Summary Get a kanban board by ID
|
|
// @Description Retrieve a kanban board with all its columns and cards
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Board ID"
|
|
// @Success 200 {object} response.Response{data=BoardDetailResponse}
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards/{id} [get]
|
|
func (h *Handler) GetBoardByID(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
board, err := h.service.GetBoardByID(c.Request().Context(), id, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to get board"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, board, "Board retrieved successfully")
|
|
}
|
|
|
|
// GetBoardsByUser retrieves all boards for the authenticated user
|
|
// @Summary Get all kanban boards for user
|
|
// @Description Retrieve all kanban boards belonging to the authenticated user
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} response.Response{data=BoardsListResponse}
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards [get]
|
|
func (h *Handler) GetBoardsByUser(c echo.Context) error {
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
boards, err := h.service.GetBoardsByUserID(c.Request().Context(), ownerID)
|
|
if err := handleServiceError(c, err, "Failed to get boards"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, boards, "Boards retrieved successfully")
|
|
}
|
|
|
|
// GetDefaultBoard retrieves the default board for the authenticated user
|
|
// @Summary Get default kanban board
|
|
// @Description Retrieve the default kanban board for the authenticated user
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} response.Response{data=BoardDetailResponse}
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards/default [get]
|
|
func (h *Handler) GetDefaultBoard(c echo.Context) error {
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
board, err := h.service.GetDefaultBoard(c.Request().Context(), ownerID)
|
|
if err := handleServiceError(c, err, "Failed to get default board"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, board, "Default board retrieved successfully")
|
|
}
|
|
|
|
// UpdateBoard updates an existing board
|
|
// @Summary Update a kanban board
|
|
// @Description Update an existing kanban board with the provided information
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Board ID"
|
|
// @Param board body UpdateBoardRequest true "Board update information"
|
|
// @Success 200 {object} response.Response{data=BoardResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards/{id} [put]
|
|
func (h *Handler) UpdateBoard(c echo.Context) error {
|
|
var req UpdateBoardRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
req.ID = c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
board, err := h.service.UpdateBoard(c.Request().Context(), req, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to update board"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, board, "Board updated successfully")
|
|
}
|
|
|
|
// DeleteBoard deletes a board
|
|
// @Summary Delete a kanban board
|
|
// @Description Delete a kanban board and all its columns and cards
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Board ID"
|
|
// @Success 204 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/boards/{id} [delete]
|
|
func (h *Handler) DeleteBoard(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.DeleteBoard(c.Request().Context(), id, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to delete board")
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// CreateColumn creates a new column
|
|
// @Summary Create a new column
|
|
// @Description Create a new column in a kanban board
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param column body CreateColumnRequest true "Column information"
|
|
// @Success 201 {object} response.Response{data=ColumnResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/columns [post]
|
|
func (h *Handler) CreateColumn(c echo.Context) error {
|
|
var req CreateColumnRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
column, err := h.service.CreateColumn(c.Request().Context(), req, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to create column"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Created(c, column, "Column created successfully")
|
|
}
|
|
|
|
// UpdateColumn updates an existing column
|
|
// @Summary Update a column
|
|
// @Description Update an existing column in a kanban board
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Column ID"
|
|
// @Param column body UpdateColumnRequest true "Column update information"
|
|
// @Success 200 {object} response.Response{data=ColumnResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/columns/{id} [put]
|
|
func (h *Handler) UpdateColumn(c echo.Context) error {
|
|
var req UpdateColumnRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
req.ID = c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
column, err := h.service.UpdateColumn(c.Request().Context(), req, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to update column"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, column, "Column updated successfully")
|
|
}
|
|
|
|
// DeleteColumn deletes a column
|
|
// @Summary Delete a column
|
|
// @Description Delete a column and move its cards to the next column
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Column ID"
|
|
// @Success 204 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/columns/{id} [delete]
|
|
func (h *Handler) DeleteColumn(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.DeleteColumn(c.Request().Context(), id, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to delete column")
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// ReorderColumns reorders columns within a board
|
|
// @Summary Reorder columns
|
|
// @Description Reorder columns within a kanban board
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param reorder body ReorderColumnsRequest true "Column reorder information"
|
|
// @Success 200 {object} response.Response
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/columns/reorder [put]
|
|
func (h *Handler) ReorderColumns(c echo.Context) error {
|
|
var req ReorderColumnsRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.ReorderColumns(c.Request().Context(), req, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to reorder columns")
|
|
}
|
|
|
|
return response.Success(c, map[string]string{"message": "Columns reordered successfully"}, "Columns reordered successfully")
|
|
}
|
|
|
|
// CreateCard creates a new card
|
|
// @Summary Create a new card
|
|
// @Description Create a new card in a kanban column
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param card body CreateCardRequest true "Card information"
|
|
// @Success 201 {object} response.Response{data=CardResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards [post]
|
|
func (h *Handler) CreateCard(c echo.Context) error {
|
|
var req CreateCardRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
// Set default priority if not provided
|
|
if req.Priority == "" {
|
|
req.Priority = CardPriorityMedium
|
|
}
|
|
|
|
// Parse order from query param if provided
|
|
if orderStr := c.QueryParam("order"); orderStr != "" {
|
|
if order, err := strconv.Atoi(orderStr); err == nil {
|
|
req.Order = order
|
|
}
|
|
}
|
|
|
|
// Parse due_date from query param if provided
|
|
if dueDateStr := c.QueryParam("due_date"); dueDateStr != "" {
|
|
if dueDate, err := strconv.ParseInt(dueDateStr, 10, 64); err == nil {
|
|
req.DueDate = &dueDate
|
|
}
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
card, err := h.service.CreateCard(c.Request().Context(), req, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to create card"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Created(c, card, "Card created successfully")
|
|
}
|
|
|
|
// GetCardByID retrieves a card by ID
|
|
// @Summary Get a card by ID
|
|
// @Description Retrieve a kanban card by its ID
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Card ID"
|
|
// @Success 200 {object} response.Response{data=CardResponse}
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards/{id} [get]
|
|
func (h *Handler) GetCardByID(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
card, err := h.service.GetCardByID(c.Request().Context(), id, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to get card"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, card, "Card retrieved successfully")
|
|
}
|
|
|
|
// UpdateCard updates an existing card
|
|
// @Summary Update a card
|
|
// @Description Update an existing kanban card
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Card ID"
|
|
// @Param card body UpdateCardRequest true "Card update information"
|
|
// @Success 200 {object} response.Response{data=CardResponse}
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards/{id} [put]
|
|
func (h *Handler) UpdateCard(c echo.Context) error {
|
|
var req UpdateCardRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
req.ID = c.Param("id")
|
|
|
|
// Parse due_date from query param if provided
|
|
if dueDateStr := c.QueryParam("due_date"); dueDateStr != "" {
|
|
if dueDate, err := strconv.ParseInt(dueDateStr, 10, 64); err == nil {
|
|
req.DueDate = &dueDate
|
|
}
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
card, err := h.service.UpdateCard(c.Request().Context(), req, ownerID)
|
|
if err := handleServiceError(c, err, "Failed to update card"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return response.Success(c, card, "Card updated successfully")
|
|
}
|
|
|
|
// DeleteCard deletes a card
|
|
// @Summary Delete a card
|
|
// @Description Delete a kanban card
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "Card ID"
|
|
// @Success 204 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards/{id} [delete]
|
|
func (h *Handler) DeleteCard(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.DeleteCard(c.Request().Context(), id, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to delete card")
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
// MoveCard moves a card to a different column
|
|
// @Summary Move a card
|
|
// @Description Move a kanban card to a different column
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param move body MoveCardRequest true "Card move information"
|
|
// @Success 200 {object} response.Response
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 404 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards/move [put]
|
|
func (h *Handler) MoveCard(c echo.Context) error {
|
|
var req MoveCardRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.MoveCard(c.Request().Context(), req, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to move card")
|
|
}
|
|
|
|
return response.Success(c, map[string]string{"message": "Card moved successfully"}, "Card moved successfully")
|
|
}
|
|
|
|
// ReorderCards reorders cards within a column
|
|
// @Summary Reorder cards
|
|
// @Description Reorder cards within a kanban column
|
|
// @Tags kanban
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param reorder body ReorderCardsRequest true "Card reorder information"
|
|
// @Success 200 {object} response.Response
|
|
// @Failure 400 {object} response.Response
|
|
// @Failure 500 {object} response.Response
|
|
// @Router /kanban/cards/reorder [put]
|
|
func (h *Handler) ReorderCards(c echo.Context) error {
|
|
var req ReorderCardsRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
|
}
|
|
|
|
ownerID, exists := ownerIDFromContext(c)
|
|
if !exists {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
if err := h.service.ReorderCards(c.Request().Context(), req, ownerID); err != nil {
|
|
return handleServiceError(c, err, "Failed to reorder cards")
|
|
}
|
|
|
|
return response.Success(c, map[string]string{"message": "Cards reordered successfully"}, "Cards reordered successfully")
|
|
}
|