monorepo/cloud/maplefile-backend/internal/usecase/user/quota_helper.go

119 lines
4.6 KiB
Go

// monorepo/cloud/maplefile-backend/internal/usecase/user/quota_helper.go
package user
import (
"context"
"fmt"
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
dom_storagedailyusage "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// Default storage quota limit: 10GB
const DefaultStorageQuotaBytes int64 = 10 * 1024 * 1024 * 1024
// UserStorageQuotaHelperUseCase provides storage quota validation
type UserStorageQuotaHelperUseCase interface {
HasEnoughQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) (bool, error)
CheckAndReserveQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) error
ReleaseQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) error
OnFileDeleted(ctx context.Context, userID gocql.UUID, sizeBytes int64) error
}
type userStorageQuotaHelperUseCaseImpl struct {
logger *zap.Logger
storageDailyUsageRepository dom_storagedailyusage.StorageDailyUsageRepository
}
// NewUserStorageQuotaHelperUseCase creates a new storage quota helper use case
func NewUserStorageQuotaHelperUseCase(
logger *zap.Logger,
storageDailyUsageRepository dom_storagedailyusage.StorageDailyUsageRepository,
) UserStorageQuotaHelperUseCase {
return &userStorageQuotaHelperUseCaseImpl{
logger: logger.Named("UserStorageQuotaHelper"),
storageDailyUsageRepository: storageDailyUsageRepository,
}
}
// HasEnoughQuota checks if user has enough storage quota
func (uc *userStorageQuotaHelperUseCaseImpl) HasEnoughQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) (bool, error) {
// Get current storage usage from most recent day
today := time.Now().UTC().Truncate(24 * time.Hour)
usage, err := uc.storageDailyUsageRepository.GetByUserAndDay(ctx, userID, today)
var currentUsage int64 = 0
if err == nil && usage != nil {
currentUsage = usage.TotalBytes
}
// Check if adding the new size would exceed the quota
newTotal := currentUsage + sizeBytes
hasQuota := newTotal <= DefaultStorageQuotaBytes
uc.logger.Debug("Quota check",
zap.String("user_id", userID.String()),
zap.Int64("current_usage", currentUsage),
zap.Int64("requested_size", sizeBytes),
zap.Int64("new_total", newTotal),
zap.Int64("quota_limit", DefaultStorageQuotaBytes),
zap.Bool("has_quota", hasQuota))
return hasQuota, nil
}
// CheckAndReserveQuota reserves storage quota for a user
func (uc *userStorageQuotaHelperUseCaseImpl) CheckAndReserveQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) error {
hasQuota, err := uc.HasEnoughQuota(ctx, userID, sizeBytes)
if err != nil {
uc.logger.Error("Failed to check quota",
zap.String("user_id", userID.String()),
zap.Int64("size_bytes", sizeBytes),
zap.Error(err))
return httperror.NewForInternalServerErrorWithSingleField("message", "Failed to check storage quota")
}
if !hasQuota {
uc.logger.Warn("User exceeded storage quota",
zap.String("user_id", userID.String()),
zap.Int64("requested_size", sizeBytes))
return httperror.NewForBadRequestWithSingleField(
"storage_quota",
fmt.Sprintf("Storage quota exceeded. You are trying to upload %d bytes, but your quota limit is %d GB. Please delete some files or upgrade your plan.",
sizeBytes,
DefaultStorageQuotaBytes/(1024*1024*1024)))
}
// Note: Actual quota reservation would be tracked in a separate table
// For now, we just validate and rely on the storage events to track actual usage
uc.logger.Info("Quota check passed",
zap.String("user_id", userID.String()),
zap.Int64("size_bytes", sizeBytes))
return nil
}
// ReleaseQuota releases previously reserved storage quota
func (uc *userStorageQuotaHelperUseCaseImpl) ReleaseQuota(ctx context.Context, userID gocql.UUID, sizeBytes int64) error {
// Note: In a full implementation, this would release a reservation
// For now, we just log the release since we're not tracking reservations separately
uc.logger.Debug("Quota release requested",
zap.String("user_id", userID.String()),
zap.Int64("size_bytes", sizeBytes))
return nil
}
// OnFileDeleted handles quota updates when a file is deleted
func (uc *userStorageQuotaHelperUseCaseImpl) OnFileDeleted(ctx context.Context, userID gocql.UUID, sizeBytes int64) error {
// Note: This is a no-op because storage usage tracking is handled by storage events
// The actual storage decrease is recorded via IncrementUsage with negative values
uc.logger.Debug("File deleted notification",
zap.String("user_id", userID.String()),
zap.Int64("size_bytes", sizeBytes))
return nil
}