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 }