service backend
This commit is contained in:
99
api/pkg/db/internal/mongo/archivable/archivable.go
Normal file
99
api/pkg/db/internal/mongo/archivable/archivable.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package archivable
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/repository"
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ArchivableDB implements archive management for entities with model.Archivable embedded
|
||||
type ArchivableDB[T storable.Storable] struct {
|
||||
repo repository.Repository
|
||||
logger mlogger.Logger
|
||||
createEmpty func() T
|
||||
getArchivable func(T) model.Archivable
|
||||
}
|
||||
|
||||
// NewArchivableDB creates a new ArchivableDB instance
|
||||
func NewArchivableDB[T storable.Storable](
|
||||
repo repository.Repository,
|
||||
logger mlogger.Logger,
|
||||
createEmpty func() T,
|
||||
getArchivable func(T) model.Archivable,
|
||||
) *ArchivableDB[T] {
|
||||
return &ArchivableDB[T]{
|
||||
repo: repo,
|
||||
logger: logger,
|
||||
createEmpty: createEmpty,
|
||||
getArchivable: getArchivable,
|
||||
}
|
||||
}
|
||||
|
||||
// SetArchived sets the archived status of an entity
|
||||
func (db *ArchivableDB[T]) SetArchived(ctx context.Context, objectRef primitive.ObjectID, archived bool) error {
|
||||
// Get current object to check current archived status
|
||||
obj := db.createEmpty()
|
||||
if err := db.repo.Get(ctx, objectRef, obj); err != nil {
|
||||
db.logger.Warn("Failed to get object for setting archived status",
|
||||
zap.Error(err),
|
||||
mzap.ObjRef("object_ref", objectRef),
|
||||
zap.Bool("archived", archived))
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract archivable from the object
|
||||
archivable := db.getArchivable(obj)
|
||||
currentArchived := archivable.IsArchived()
|
||||
if currentArchived == archived {
|
||||
db.logger.Debug("No change needed - same archived status",
|
||||
mzap.ObjRef("object_ref", objectRef),
|
||||
zap.Bool("archived", archived))
|
||||
return nil // No change needed
|
||||
}
|
||||
|
||||
// Set the archived status
|
||||
patch := repository.Patch().Set(repository.IsArchivedField(), archived)
|
||||
if err := db.repo.Patch(ctx, objectRef, patch); err != nil {
|
||||
db.logger.Warn("Failed to set archived status on object",
|
||||
zap.Error(err),
|
||||
mzap.ObjRef("object_ref", objectRef),
|
||||
zap.Bool("archived", archived))
|
||||
return err
|
||||
}
|
||||
|
||||
db.logger.Debug("Successfully set archived status on object",
|
||||
mzap.ObjRef("object_ref", objectRef),
|
||||
zap.Bool("archived", archived))
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsArchived checks if an entity is archived
|
||||
func (db *ArchivableDB[T]) IsArchived(ctx context.Context, objectRef primitive.ObjectID) (bool, error) {
|
||||
obj := db.createEmpty()
|
||||
|
||||
if err := db.repo.Get(ctx, objectRef, obj); err != nil {
|
||||
db.logger.Warn("Failed to get object for checking archived status",
|
||||
zap.Error(err),
|
||||
mzap.ObjRef("object_ref", objectRef))
|
||||
return false, err
|
||||
}
|
||||
|
||||
archivable := db.getArchivable(obj)
|
||||
return archivable.IsArchived(), nil
|
||||
}
|
||||
|
||||
// Archive archives an entity (sets archived to true)
|
||||
func (db *ArchivableDB[T]) Archive(ctx context.Context, objectRef primitive.ObjectID) error {
|
||||
return db.SetArchived(ctx, objectRef, true)
|
||||
}
|
||||
|
||||
// Unarchive unarchives an entity (sets archived to false)
|
||||
func (db *ArchivableDB[T]) Unarchive(ctx context.Context, objectRef primitive.ObjectID) error {
|
||||
return db.SetArchived(ctx, objectRef, false)
|
||||
}
|
||||
175
api/pkg/db/internal/mongo/archivable/archivable_test.go
Normal file
175
api/pkg/db/internal/mongo/archivable/archivable_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package archivable
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/internal/mongo/repositoryimp"
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/mongodb"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TestArchivableObject represents a test object with archivable functionality
|
||||
type TestArchivableObject struct {
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
model.ArchivableBase `bson:",inline" json:",inline"`
|
||||
Name string `bson:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (t *TestArchivableObject) Collection() string {
|
||||
return "testArchivableObject"
|
||||
}
|
||||
|
||||
func (t *TestArchivableObject) GetArchivable() model.Archivable {
|
||||
return &t.ArchivableBase
|
||||
}
|
||||
|
||||
func TestArchivableDB(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start MongoDB container (stable)
|
||||
mongoContainer, err := mongodb.Run(ctx,
|
||||
"mongo:latest",
|
||||
mongodb.WithUsername("test"),
|
||||
mongodb.WithPassword("test"),
|
||||
testcontainers.WithWaitStrategy(wait.ForListeningPort("27017/tcp").WithStartupTimeout(2*time.Minute)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
termCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
if err := mongoContainer.Terminate(termCtx); err != nil {
|
||||
t.Logf("Failed to terminate container: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Get MongoDB connection string
|
||||
mongoURI, err := mongoContainer.ConnectionString(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Connect to MongoDB
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := client.Disconnect(context.Background()); err != nil {
|
||||
t.Logf("Failed to disconnect from MongoDB: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Ping the database
|
||||
err = client.Ping(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create repository
|
||||
repo := repositoryimp.NewMongoRepository(client.Database("test_"+t.Name()), "testArchivableCollection")
|
||||
|
||||
// Create archivable DB
|
||||
archivableDB := NewArchivableDB(
|
||||
repo,
|
||||
zap.NewNop(),
|
||||
func() *TestArchivableObject { return &TestArchivableObject{} },
|
||||
func(obj *TestArchivableObject) model.Archivable { return obj.GetArchivable() },
|
||||
)
|
||||
|
||||
t.Run("SetArchived_Success", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = archivableDB.SetArchived(ctx, obj.ID, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result TestArchivableObject
|
||||
err = repo.Get(ctx, obj.ID, &result)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.IsArchived())
|
||||
})
|
||||
|
||||
t.Run("SetArchived_NoChange", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = archivableDB.SetArchived(ctx, obj.ID, true)
|
||||
require.NoError(t, err) // Should not error, just not change anything
|
||||
|
||||
var result TestArchivableObject
|
||||
err = repo.Get(ctx, obj.ID, &result)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.IsArchived())
|
||||
})
|
||||
|
||||
t.Run("SetArchived_Unarchive", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = archivableDB.SetArchived(ctx, obj.ID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result TestArchivableObject
|
||||
err = repo.Get(ctx, obj.ID, &result)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, result.IsArchived())
|
||||
})
|
||||
|
||||
t.Run("IsArchived_True", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
isArchived, err := archivableDB.IsArchived(ctx, obj.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isArchived)
|
||||
})
|
||||
|
||||
t.Run("IsArchived_False", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
isArchived, err := archivableDB.IsArchived(ctx, obj.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isArchived)
|
||||
})
|
||||
|
||||
t.Run("Archive_Success", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: false}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = archivableDB.Archive(ctx, obj.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result TestArchivableObject
|
||||
err = repo.Get(ctx, obj.ID, &result)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result.IsArchived())
|
||||
})
|
||||
|
||||
t.Run("Unarchive_Success", func(t *testing.T) {
|
||||
obj := &TestArchivableObject{Name: "test", ArchivableBase: model.ArchivableBase{Archived: true}}
|
||||
err := repo.Insert(ctx, obj, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = archivableDB.Unarchive(ctx, obj.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result TestArchivableObject
|
||||
err = repo.Get(ctx, obj.ID, &result)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, result.IsArchived())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user