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") }