kanban dashboard
This commit is contained in:
@@ -0,0 +1,604 @@
|
||||
package kanban
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"tm/pkg/logger"
|
||||
orm "tm/pkg/mongo"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
// BoardRepository interface defines board data access methods
|
||||
type BoardRepository interface {
|
||||
// Board CRUD operations
|
||||
CreateBoard(ctx context.Context, board *Board) error
|
||||
GetBoardByID(ctx context.Context, id string) (*Board, error)
|
||||
GetBoardsByUserID(ctx context.Context, userID string) ([]Board, error)
|
||||
GetDefaultBoard(ctx context.Context, userID string) (*Board, error)
|
||||
UpdateBoard(ctx context.Context, board *Board) error
|
||||
DeleteBoard(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// ColumnRepository interface defines column data access methods
|
||||
type ColumnRepository interface {
|
||||
// Column CRUD operations
|
||||
CreateColumn(ctx context.Context, column *Column) error
|
||||
GetColumnByID(ctx context.Context, id string) (*Column, error)
|
||||
GetColumnsByBoardID(ctx context.Context, boardID string) ([]Column, error)
|
||||
UpdateColumn(ctx context.Context, column *Column) error
|
||||
DeleteColumn(ctx context.Context, id string) error
|
||||
UpdateColumnOrder(ctx context.Context, boardID string, columnOrders map[string]int) error
|
||||
}
|
||||
|
||||
// CardRepository interface defines card data access methods
|
||||
type CardRepository interface {
|
||||
// Card CRUD operations
|
||||
CreateCard(ctx context.Context, card *Card) error
|
||||
GetCardByID(ctx context.Context, id string) (*Card, error)
|
||||
GetCardsByColumnID(ctx context.Context, columnID string) ([]Card, error)
|
||||
GetCardsByTenderID(ctx context.Context, tenderID string) ([]Card, error)
|
||||
UpdateCard(ctx context.Context, card *Card) error
|
||||
DeleteCard(ctx context.Context, id string) error
|
||||
MoveCard(ctx context.Context, cardID, targetColumnID string, newOrder int) error
|
||||
UpdateCardOrder(ctx context.Context, columnID string, cardOrders map[string]int) error
|
||||
}
|
||||
|
||||
func boardCollectionName() string {
|
||||
return "kanban_boards"
|
||||
}
|
||||
|
||||
func columnCollectionName() string {
|
||||
return "kanban_columns"
|
||||
}
|
||||
|
||||
func cardCollectionName() string {
|
||||
return "kanban_cards"
|
||||
}
|
||||
|
||||
// boardRepository implements BoardRepository interface using MongoDB ORM
|
||||
type boardRepository struct {
|
||||
ormRepo orm.Repository[Board]
|
||||
mongoManager *orm.ConnectionManager
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// columnRepository implements ColumnRepository interface using MongoDB ORM
|
||||
type columnRepository struct {
|
||||
ormRepo orm.Repository[Column]
|
||||
mongoManager *orm.ConnectionManager
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// cardRepository implements CardRepository interface using MongoDB ORM
|
||||
type cardRepository struct {
|
||||
ormRepo orm.Repository[Card]
|
||||
mongoManager *orm.ConnectionManager
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewBoardRepository creates a new board repository
|
||||
func NewBoardRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) BoardRepository {
|
||||
// Create indexes for boards collection
|
||||
boardIndexes := []orm.Index{
|
||||
*orm.NewIndex("user_id_idx", bson.D{{Key: "user_id", Value: 1}}),
|
||||
*orm.NewIndex("is_default_idx", bson.D{{Key: "is_default", Value: 1}}),
|
||||
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*orm.NewIndex("user_default_idx", bson.D{
|
||||
{Key: "user_id", Value: 1},
|
||||
{Key: "is_default", Value: 1},
|
||||
}),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
if err := mongoManager.CreateIndexes(boardCollectionName(), boardIndexes); err != nil {
|
||||
logger.Warn("Failed to create board indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
boardOrmRepo := orm.NewRepository[Board](mongoManager.GetCollection(boardCollectionName()), logger)
|
||||
|
||||
return &boardRepository{
|
||||
ormRepo: boardOrmRepo,
|
||||
mongoManager: mongoManager,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewColumnRepository creates a new column repository
|
||||
func NewColumnRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) ColumnRepository {
|
||||
// Create indexes for columns collection
|
||||
columnIndexes := []orm.Index{
|
||||
*orm.NewIndex("board_id_idx", bson.D{{Key: "board_id", Value: 1}}),
|
||||
*orm.NewIndex("order_idx", bson.D{{Key: "order", Value: 1}}),
|
||||
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*orm.NewIndex("board_order_idx", bson.D{
|
||||
{Key: "board_id", Value: 1},
|
||||
{Key: "order", Value: 1},
|
||||
}),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
if err := mongoManager.CreateIndexes(columnCollectionName(), columnIndexes); err != nil {
|
||||
logger.Warn("Failed to create column indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
columnOrmRepo := orm.NewRepository[Column](mongoManager.GetCollection(columnCollectionName()), logger)
|
||||
|
||||
return &columnRepository{
|
||||
ormRepo: columnOrmRepo,
|
||||
mongoManager: mongoManager,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCardRepository creates a new card repository
|
||||
func NewCardRepository(mongoManager *orm.ConnectionManager, logger logger.Logger) CardRepository {
|
||||
// Create indexes for cards collection
|
||||
cardIndexes := []orm.Index{
|
||||
*orm.NewIndex("column_id_idx", bson.D{{Key: "column_id", Value: 1}}),
|
||||
*orm.NewIndex("tender_id_idx", bson.D{{Key: "tender_id", Value: 1}}),
|
||||
*orm.NewIndex("order_idx", bson.D{{Key: "order", Value: 1}}),
|
||||
*orm.NewIndex("priority_idx", bson.D{{Key: "priority", Value: 1}}),
|
||||
*orm.NewIndex("due_date_idx", bson.D{{Key: "due_date", Value: 1}}),
|
||||
*orm.NewIndex("created_at_idx", bson.D{{Key: "created_at", Value: -1}}),
|
||||
*orm.NewIndex("column_order_idx", bson.D{
|
||||
{Key: "column_id", Value: 1},
|
||||
{Key: "order", Value: 1},
|
||||
}),
|
||||
}
|
||||
|
||||
// Create indexes
|
||||
if err := mongoManager.CreateIndexes(cardCollectionName(), cardIndexes); err != nil {
|
||||
logger.Warn("Failed to create card indexes", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
cardOrmRepo := orm.NewRepository[Card](mongoManager.GetCollection(cardCollectionName()), logger)
|
||||
|
||||
return &cardRepository{
|
||||
ormRepo: cardOrmRepo,
|
||||
mongoManager: mongoManager,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// BoardRepository implementation
|
||||
|
||||
func (r *boardRepository) CreateBoard(ctx context.Context, board *Board) error {
|
||||
now := time.Now().Unix()
|
||||
board.SetCreatedAt(now)
|
||||
|
||||
err := r.ormRepo.Create(ctx, board)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create board", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"name": board.Name,
|
||||
"user_id": board.UserID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Board created successfully", map[string]interface{}{
|
||||
"board_id": board.ID,
|
||||
"name": board.Name,
|
||||
"user_id": board.UserID,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *boardRepository) GetBoardByID(ctx context.Context, id string) (*Board, error) {
|
||||
board, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get board by ID", map[string]interface{}{
|
||||
"board_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return board, nil
|
||||
}
|
||||
|
||||
func (r *boardRepository) GetBoardsByUserID(ctx context.Context, userID string) ([]Board, error) {
|
||||
filter := bson.M{"user_id": userID}
|
||||
pagination := orm.Pagination{
|
||||
SortField: "created_at",
|
||||
SortOrder: -1, // Newest first
|
||||
}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get boards by user ID", map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Items, nil
|
||||
}
|
||||
|
||||
func (r *boardRepository) GetDefaultBoard(ctx context.Context, userID string) (*Board, error) {
|
||||
filter := bson.M{
|
||||
"user_id": userID,
|
||||
"is_default": true,
|
||||
}
|
||||
pagination := orm.Pagination{Limit: 1}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get default board", map[string]interface{}{
|
||||
"user_id": userID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.Items) == 0 {
|
||||
return nil, orm.ErrDocumentNotFound
|
||||
}
|
||||
|
||||
return &result.Items[0], nil
|
||||
}
|
||||
|
||||
func (r *boardRepository) UpdateBoard(ctx context.Context, board *Board) error {
|
||||
board.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := r.ormRepo.Update(ctx, board)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update board", map[string]interface{}{
|
||||
"board_id": board.ID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Board updated successfully", map[string]interface{}{
|
||||
"board_id": board.ID,
|
||||
"name": board.Name,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *boardRepository) DeleteBoard(ctx context.Context, id string) error {
|
||||
err := r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete board", map[string]interface{}{
|
||||
"board_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Board deleted successfully", map[string]interface{}{
|
||||
"board_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ColumnRepository implementation
|
||||
|
||||
func (r *columnRepository) CreateColumn(ctx context.Context, column *Column) error {
|
||||
now := time.Now().Unix()
|
||||
column.SetCreatedAt(now)
|
||||
|
||||
err := r.ormRepo.Create(ctx, column)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create column", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"name": column.Name,
|
||||
"board_id": column.BoardID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Column created successfully", map[string]interface{}{
|
||||
"column_id": column.ID,
|
||||
"name": column.Name,
|
||||
"board_id": column.BoardID,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *columnRepository) GetColumnByID(ctx context.Context, id string) (*Column, error) {
|
||||
column, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get column by ID", map[string]interface{}{
|
||||
"column_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return column, nil
|
||||
}
|
||||
|
||||
func (r *columnRepository) GetColumnsByBoardID(ctx context.Context, boardID string) ([]Column, error) {
|
||||
filter := bson.M{"board_id": boardID}
|
||||
pagination := orm.Pagination{
|
||||
SortField: "order",
|
||||
SortOrder: 1, // Ascending order
|
||||
}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get columns by board ID", map[string]interface{}{
|
||||
"board_id": boardID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Items, nil
|
||||
}
|
||||
|
||||
func (r *columnRepository) UpdateColumn(ctx context.Context, column *Column) error {
|
||||
column.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := r.ormRepo.Update(ctx, column)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update column", map[string]interface{}{
|
||||
"column_id": column.ID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Column updated successfully", map[string]interface{}{
|
||||
"column_id": column.ID,
|
||||
"name": column.Name,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *columnRepository) DeleteColumn(ctx context.Context, id string) error {
|
||||
err := r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete column", map[string]interface{}{
|
||||
"column_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Column deleted successfully", map[string]interface{}{
|
||||
"column_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *columnRepository) UpdateColumnOrder(ctx context.Context, boardID string, columnOrders map[string]int) error {
|
||||
// Update each column's order
|
||||
for columnID, order := range columnOrders {
|
||||
column, err := r.ormRepo.FindByID(ctx, columnID)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to find column for order update", map[string]interface{}{
|
||||
"column_id": columnID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
column.Order = order
|
||||
column.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err = r.ormRepo.Update(ctx, column)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update column order", map[string]interface{}{
|
||||
"column_id": columnID,
|
||||
"order": order,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("Column orders updated successfully", map[string]interface{}{
|
||||
"board_id": boardID,
|
||||
"count": len(columnOrders),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CardRepository implementation
|
||||
|
||||
func (r *cardRepository) CreateCard(ctx context.Context, card *Card) error {
|
||||
now := time.Now().Unix()
|
||||
card.SetCreatedAt(now)
|
||||
|
||||
err := r.ormRepo.Create(ctx, card)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to create card", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"title": card.Title,
|
||||
"tender_id": card.TenderID,
|
||||
"column_id": card.ColumnID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Card created successfully", map[string]interface{}{
|
||||
"card_id": card.ID,
|
||||
"title": card.Title,
|
||||
"tender_id": card.TenderID,
|
||||
"column_id": card.ColumnID,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) GetCardByID(ctx context.Context, id string) (*Card, error) {
|
||||
card, err := r.ormRepo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get card by ID", map[string]interface{}{
|
||||
"card_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return card, nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) GetCardsByColumnID(ctx context.Context, columnID string) ([]Card, error) {
|
||||
filter := bson.M{"column_id": columnID}
|
||||
pagination := orm.Pagination{
|
||||
SortField: "order",
|
||||
SortOrder: 1, // Ascending order
|
||||
}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get cards by column ID", map[string]interface{}{
|
||||
"column_id": columnID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Items, nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) GetCardsByTenderID(ctx context.Context, tenderID string) ([]Card, error) {
|
||||
filter := bson.M{"tender_id": tenderID}
|
||||
pagination := orm.Pagination{
|
||||
SortField: "created_at",
|
||||
SortOrder: -1, // Newest first
|
||||
}
|
||||
|
||||
result, err := r.ormRepo.FindAll(ctx, filter, pagination)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to get cards by tender ID", map[string]interface{}{
|
||||
"tender_id": tenderID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.Items, nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) UpdateCard(ctx context.Context, card *Card) error {
|
||||
card.UpdatedAt = time.Now().Unix()
|
||||
|
||||
err := r.ormRepo.Update(ctx, card)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update card", map[string]interface{}{
|
||||
"card_id": card.ID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Card updated successfully", map[string]interface{}{
|
||||
"card_id": card.ID,
|
||||
"title": card.Title,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) DeleteCard(ctx context.Context, id string) error {
|
||||
err := r.ormRepo.Delete(ctx, id)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to delete card", map[string]interface{}{
|
||||
"card_id": id,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Card deleted successfully", map[string]interface{}{
|
||||
"card_id": id,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) MoveCard(ctx context.Context, cardID, targetColumnID string, newOrder int) error {
|
||||
card, err := r.ormRepo.FindByID(ctx, cardID)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to find card for move", map[string]interface{}{
|
||||
"card_id": cardID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
oldColumnID := card.ColumnID
|
||||
oldOrder := card.Order
|
||||
|
||||
// Update card position
|
||||
card.MoveToColumn(targetColumnID, newOrder)
|
||||
|
||||
err = r.ormRepo.Update(ctx, card)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to move card", map[string]interface{}{
|
||||
"card_id": cardID,
|
||||
"target_column_id": targetColumnID,
|
||||
"new_order": newOrder,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Card moved successfully", map[string]interface{}{
|
||||
"card_id": cardID,
|
||||
"old_column_id": oldColumnID,
|
||||
"target_column_id": targetColumnID,
|
||||
"old_order": oldOrder,
|
||||
"new_order": newOrder,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *cardRepository) UpdateCardOrder(ctx context.Context, columnID string, cardOrders map[string]int) error {
|
||||
// Update each card's order
|
||||
for cardID, order := range cardOrders {
|
||||
// Find the card
|
||||
card, err := r.ormRepo.FindByID(ctx, cardID)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to find card for order update", map[string]interface{}{
|
||||
"card_id": cardID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the order
|
||||
card.Order = order
|
||||
card.UpdatedAt = time.Now().Unix()
|
||||
|
||||
// Save the card
|
||||
err = r.ormRepo.Update(ctx, card)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update card order", map[string]interface{}{
|
||||
"card_id": cardID,
|
||||
"order": order,
|
||||
"column_id": columnID,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
r.logger.Info("Card orders updated successfully", map[string]interface{}{
|
||||
"column_id": columnID,
|
||||
"count": len(cardOrders),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user