Files
tm_back/pkg/mongo
Mazyar 7a9de273bb
continuous-integration/drone/push Build is passing
Enhance dashboard statistics with TED notice tracking
- Added a new field, ScrapedTEDNotices, to the StatisticsLifetimeTotals struct to track the total number of TED notices scraped.
- Updated the Statistics method in the statistics repository to include a background process for retrieving total scraped TED notices, improving the accuracy of dashboard statistics.
- Introduced new methods in the Counter to increment and retrieve daily counts for scraped TED notices, ensuring reliable metrics for reporting.
- Modified the TEDScraper to increment the TED notice scraped counter upon successful import, enhancing the tracking of scraping activity.

This update improves the dashboard's statistics by providing detailed insights into TED notice scraping activities, contributing to better data visibility and reporting.
2026-06-30 23:28:12 +03:30
..
2026-05-17 17:21:36 +03:30

MongoDB ORM for Go

A production-grade, high-performance generic ORM for MongoDB in Go, using the official MongoDB driver. This ORM provides a clean, type-safe interface for MongoDB operations with support for pagination, transactions, and advanced querying.

Features

Generic Repository Pattern - Type-safe operations for any struct
Dual Pagination Support - Both offset-based and cursor-based pagination
Comprehensive CRUD Operations - Create, Read, Update, Delete with bulk operations
Advanced Querying - Fluent query builder with complex conditions
Aggregation Support - Full MongoDB aggregation pipeline support
Connection Management - Connection pooling and lifecycle management
Index Management - Easy index creation and management
Transaction Support - ACID transactions for multi-document operations
Structured Logging - Comprehensive logging with structured fields
Performance Optimized - Efficient cursor-based pagination and connection pooling
Type Safety - Full Go generics support for compile-time type safety

Quick Start

1. Define Your Model

type User struct {
    Model
    Email     string `bson:"email" json:"email"`
    FirstName string `bson:"first_name" json:"first_name"`
    LastName  string `bson:"last_name" json:"last_name"`
    Age       int    `bson:"age" json:"age"`
    IsActive  bool   `bson:"is_active" json:"is_active"`
}

2. Set Up Connection

// Create logger
logger := &YourLogger{}

// Configure connection
config := mongo.ConnectionConfig{
    URI:      "mongodb://localhost:27017",
    Database: "your_database",
}

// Create connection manager
connManager, err := mongo.NewConnectionManager(config, logger)
if err != nil {
    log.Fatal(err)
}
defer connManager.Close(context.Background())

// Create repository
userRepo := mongo.NewRepository[User](connManager.GetCollection("users"), logger)

3. Basic Operations

// Create
user := &User{
    Email:     "john@example.com",
    FirstName: "John",
    LastName:  "Doe",
    Age:       30,
    IsActive:  true,
}
err := userRepo.Create(context.Background(), user)

// Find by ID
foundUser, err := userRepo.FindByID(context.Background(), user.ID)

// Update
user.FirstName = "Jane"
err = userRepo.Update(context.Background(), user)

// Delete
err = userRepo.Delete(context.Background(), user.ID)

Pagination

Offset-based Pagination

pagination := mongo.NewPaginationBuilder().
    Limit(10).
    Skip(20).
    SortDesc("created_at").
    Build()

results, err := userRepo.FindAll(context.Background(), bson.M{}, pagination)
// First page
pagination := mongo.NewPaginationBuilder().
    Limit(10).
    SortDesc("created_at").
    Build()

firstPage, err := userRepo.FindAll(context.Background(), bson.M{}, pagination)

// Next page using cursor
if firstPage.NextCursor != "" {
    nextPagination := mongo.NewPaginationBuilder().
        Limit(10).
        SortDesc("created_at").
        Cursor(firstPage.NextCursor).
        Build()
    
    nextPage, err := userRepo.FindAll(context.Background(), bson.M{}, nextPagination)
}

Advanced Querying

Using QueryBuilder

query := mongo.NewQueryBuilder().
    Where("is_active", true).
    WhereGreaterThan("age", 18).
    WhereIn("category", []interface{}{"admin", "user"}).
    WhereRegex("email", ".*@example\\.com", "i").
    Build()

results, err := userRepo.FindAll(context.Background(), query, pagination)

Complex Queries

// AND/OR conditions
query := mongo.NewQueryBuilder().
    WhereAnd(
        bson.M{"is_active": true},
        bson.M{"age": bson.M{"$gte": 18}},
    ).
    WhereOr(
        bson.M{"role": "admin"},
        bson.M{"role": "moderator"},
    ).
    Build()

Aggregation

pipeline := mongo.Pipeline{
    {{Key: "$match", Value: bson.M{"is_active": true}}},
    {{Key: "$group", Value: bson.M{
        "_id": "$role",
        "count": bson.M{"$sum": 1},
        "avgAge": bson.M{"$avg": "$age"},
    }}},
    {{Key: "$sort", Value: bson.M{"count": -1}}},
}

results, err := userRepo.Aggregate(context.Background(), pipeline)

Bulk Operations

// Bulk create
users := []User{
    {Email: "user1@example.com", FirstName: "User1"},
    {Email: "user2@example.com", FirstName: "User2"},
}
err := userRepo.BulkCreate(context.Background(), users)

// Bulk update
filter := bson.M{"is_active": false}
update := bson.M{"$set": bson.M{"status": "inactive"}}
err = userRepo.BulkUpdate(context.Background(), filter, update)

// Bulk delete
filter := bson.M{"created_at": bson.M{"$lt": time.Now().AddDate(0, -1, 0).Unix()}}
err = userRepo.BulkDelete(context.Background(), filter)

Index Management

Create Indexes

// Create common indexes
indexes := mongo.UserIndexes()
err := connManager.CreateIndexes("users", indexes)

// Create custom indexes
customIndexes := []mongo.Index{
    *mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
    *mongo.CreateTTLIndex("expires_at_idx", "expires_at", 3600),
    *mongo.CreateTextIndex("search_idx", "title", "description"),
}

err = connManager.CreateIndexes("documents", customIndexes)

Index Types

// Unique index
uniqueIdx := mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}})

// TTL index
ttlIdx := mongo.CreateTTLIndex("expires_at_idx", "expires_at", 3600)

// Text index
textIdx := mongo.CreateTextIndex("search_idx", "title", "description")

// Compound index
compoundIdx := mongo.CreateCompoundIndex("user_status_idx", map[string]int{
    "user_id": 1,
    "status":  1,
})

// Geospatial index
geoIdx := mongo.CreateGeospatialIndex("location_idx", "location")

// Hashed index
hashIdx := mongo.CreateHashedIndex("user_id_hash_idx", "user_id")

Transactions

// Start session
session, err := connManager.client.StartSession()
if err != nil {
    log.Fatal(err)
}
defer session.EndSession(context.Background())

// Execute transaction
_, err = session.WithTransaction(context.Background(), func(sessCtx mongo.SessionContext) (interface{}, error) {
    // Create user
    user := &User{Email: "transaction@example.com"}
    if err := userRepo.Create(sessCtx, user); err != nil {
        return nil, err
    }
    
    // Create related document
    profile := &Profile{UserID: user.ID}
    if err := profileRepo.Create(sessCtx, profile); err != nil {
        return nil, err
    }
    
    return nil, nil
})

Connection Configuration

config := mongo.ConnectionConfig{
    URI:                    "mongodb://localhost:27017",
    Database:               "your_database",
    MaxPoolSize:            100,
    MinPoolSize:            5,
    MaxConnIdleTime:        30 * time.Minute,
    ConnectTimeout:         10 * time.Second,
    ServerSelectionTimeout: 5 * time.Second,
}

Logger Interface

Implement the Logger interface for structured logging:

type YourLogger struct{}

func (l *YourLogger) Info(message string, fields map[string]interface{}) {
    log.Printf("[INFO] %s: %+v", message, fields)
}

func (l *YourLogger) Error(message string, fields map[string]interface{}) {
    log.Printf("[ERROR] %s: %+v", message, fields)
}

func (l *YourLogger) Debug(message string, fields map[string]interface{}) {
    log.Printf("[DEBUG] %s: %+v", message, fields)
}

func (l *YourLogger) Warn(message string, fields map[string]interface{}) {
    log.Printf("[WARN] %s: %+v", message, fields)
}

Model Interfaces

Timestampable Interface

Implement for automatic timestamp management:

type YourModel struct {
    Model
    // Your fields...
}

// Model already implements Timestampable
// Timestamps are automatically set on Create/Update operations

Custom ID Management

type CustomModel struct {
    Model
    // Your fields...
}

// Model already implements IDGetter and IDSetter
// ID is automatically set after Create operations

Performance Best Practices

1. Use Cursor-based Pagination

Cursor-based pagination is more efficient than offset-based pagination for large datasets:

// Good - Cursor-based
pagination := mongo.NewPaginationBuilder().
    Limit(10).
    SortDesc("created_at").
    Build()

// Avoid - Offset-based for large datasets
pagination := mongo.NewPaginationBuilder().
    Limit(10).
    Skip(10000). // Expensive for large datasets
    Build()

2. Create Appropriate Indexes

// Create indexes for frequently queried fields
indexes := []mongo.Index{
    *mongo.CreateUniqueIndex("email_idx", bson.D{{Key: "email", Value: 1}}),
    *mongo.NewIndex("status_created_idx", bson.D{
        {Key: "status", Value: 1},
        {Key: "created_at", Value: -1},
    }),
}

3. Use Projection for Large Documents

// Use FindMany with limit for large result sets
users, err := userRepo.FindMany(context.Background(), filter, 1000)

4. Connection Pooling

Configure appropriate connection pool settings:

config := mongo.ConnectionConfig{
    MaxPoolSize:    100,
    MinPoolSize:    5,
    MaxConnIdleTime: 30 * time.Minute,
}

Error Handling

The ORM provides specific error types:

var (
    ErrDocumentNotFound = errors.New("document not found")
    ErrInvalidCursor    = errors.New("invalid cursor")
    ErrInvalidPagination = errors.New("invalid pagination parameters")
)

// Handle specific errors
user, err := userRepo.FindByID(ctx, id)
if err != nil {
    if errors.Is(err, mongo.ErrDocumentNotFound) {
        // Handle not found
        return
    }
    // Handle other errors
}

Testing

Unit Testing with Mocks

// Create mock repository for testing
type MockUserRepository struct {
    users map[string]*User
}

func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    if user, exists := m.users[id]; exists {
        return user, nil
    }
    return nil, mongo.ErrDocumentNotFound
}

// Use in tests
mockRepo := &MockUserRepository{users: make(map[string]*User)}

Integration Testing

func TestUserRepository(t *testing.T) {
    // Set up test database
    config := mongo.ConnectionConfig{
        URI:      "mongodb://localhost:27017",
        Database: "test_database",
    }
    
    connManager, err := mongo.NewConnectionManager(config, logger)
    require.NoError(t, err)
    defer connManager.Close(context.Background())
    
    userRepo := mongo.NewRepository[User](connManager.GetCollection("users"), logger)
    
    // Run tests...
}

Migration from Existing Code

From Manual MongoDB Operations

// Before
collection := client.Database("db").Collection("users")
filter := bson.M{"email": email}
var user User
err := collection.FindOne(ctx, filter).Decode(&user)

// After
userRepo := mongo.NewRepository[User](collection, logger)
user, err := userRepo.FindOne(ctx, bson.M{"email": email})

From Other ORMs

The generic repository pattern makes migration straightforward:

// Replace existing repository interface
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
    Create(ctx context.Context, user *User) error
    // ... other methods
}

// Implementation remains the same, just use mongo.NewRepository

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

This project is licensed under the MIT License - see the LICENSE file for details.