605 lines
16 KiB
Go
605 lines
16 KiB
Go
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
|
|
}
|