119 lines
4.6 KiB
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
|
|
}
|