package auth import ( "context" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/template" "github.com/tech/sendico/pkg/merrors" "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 operations with permission checking type ArchivableDBImp[T model.PermissionBoundStorable] struct { dbImp *template.DBImp[T] logger mlogger.Logger enforcer Enforcer createEmpty func() T getArchivable func(T) model.Archivable } // NewArchivableDB creates a new auth.ArchivableDB instance func newArchivableDBImp[T model.PermissionBoundStorable]( dbImp *template.DBImp[T], logger mlogger.Logger, enforcer Enforcer, createEmpty func() T, getArchivable func(T) model.Archivable, ) ArchivableDB[T] { return &ArchivableDBImp[T]{ dbImp: dbImp, logger: logger.Named("archivable"), enforcer: enforcer, createEmpty: createEmpty, getArchivable: getArchivable, } } // SetArchived sets the archived status of an entity with permission checking func (db *ArchivableDBImp[T]) SetArchived(ctx context.Context, accountRef, objectRef primitive.ObjectID, archived bool) error { // Check permissions using enforceObject helper if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionUpdate, accountRef, objectRef); err != nil { db.logger.Warn("Failed to enforce object permission", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived)) return err } // Get the object to check current archived status obj := db.createEmpty() if err := db.dbImp.Get(ctx, objectRef, obj); err != nil { db.logger.Warn("Failed to get object for setting archived status", zap.Error(err), mzap.ObjRef("account_ref", accountRef), 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("account_ref", accountRef), 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.dbImp.Patch(ctx, objectRef, patch); err != nil { db.logger.Warn("Failed to set archived status on object", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived)) return err } db.logger.Debug("Successfully set archived status on object", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.Bool("archived", archived)) return nil } // IsArchived checks if an entity is archived with permission checking func (db *ArchivableDBImp[T]) IsArchived(ctx context.Context, accountRef, objectRef primitive.ObjectID) (bool, error) { // // Check permissions using single Enforce if err := enforceObjectByRef(ctx, db.dbImp, db.enforcer, model.ActionRead, accountRef, objectRef); err != nil { db.logger.Debug("Permission denied for checking archived status", mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef), zap.String("action", string(model.ActionRead))) return false, merrors.AccessDenied("read", "object", objectRef) } obj := db.createEmpty() if err := db.dbImp.Get(ctx, objectRef, obj); err != nil { db.logger.Warn("Failed to get object for checking archived status", zap.Error(err), mzap.ObjRef("account_ref", accountRef), mzap.ObjRef("object_ref", objectRef)) return false, err } archivable := db.getArchivable(obj) return archivable.IsArchived(), nil } // Archive archives an entity with permission checking (sets archived to true) func (db *ArchivableDBImp[T]) Archive(ctx context.Context, accountRef, objectRef primitive.ObjectID) error { return db.SetArchived(ctx, accountRef, objectRef, true) } // Unarchive unarchives an entity with permission checking (sets archived to false) func (db *ArchivableDBImp[T]) Unarchive(ctx context.Context, accountRef, objectRef primitive.ObjectID) error { return db.SetArchived(ctx, accountRef, objectRef, false) }