Files
tm_back/internal/kanban/repository.go
T
2026-01-05 15:36:20 +03:30

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
}