Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,50 @@
package storagedailyusage
import (
"context"
"github.com/gocql/gocql"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
)
// DeleteByUserUseCase deletes all storage daily usage records for a user
// Used for GDPR right-to-be-forgotten implementation
type DeleteByUserUseCase interface {
Execute(ctx context.Context, userID gocql.UUID) error
}
type deleteByUserUseCaseImpl struct {
logger *zap.Logger
repo storagedailyusage.StorageDailyUsageRepository
}
// NewDeleteByUserUseCase creates a new use case for deleting all storage daily usage by user ID
func NewDeleteByUserUseCase(
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) DeleteByUserUseCase {
return &deleteByUserUseCaseImpl{
logger: logger.Named("DeleteStorageDailyUsageByUserUseCase"),
repo: repo,
}
}
func (uc *deleteByUserUseCaseImpl) Execute(ctx context.Context, userID gocql.UUID) error {
uc.logger.Info("Deleting all storage daily usage for user",
zap.String("user_id", userID.String()))
err := uc.repo.DeleteByUserID(ctx, userID)
if err != nil {
uc.logger.Error("Failed to delete storage daily usage",
zap.String("user_id", userID.String()),
zap.Error(err))
return err
}
uc.logger.Info("✅ Successfully deleted all storage daily usage for user",
zap.String("user_id", userID.String()))
return nil
}

View file

@ -0,0 +1,22 @@
package storagedailyusage
import (
"testing"
"go.uber.org/zap"
)
// NOTE: Unit tests for DeleteByUserUseCase would require mocks.
// For now, this use case will be tested via integration tests.
// See Task 1.10 in RIGHT_TO_BE_FORGOTTEN_IMPLEMENTATION.md
func TestDeleteByUserUseCase_Constructor(t *testing.T) {
// Test that constructor creates use case successfully
logger := zap.NewNop()
useCase := NewDeleteByUserUseCase(logger, nil)
if useCase == nil {
t.Error("Expected use case to be created, got nil")
}
}

View file

@ -0,0 +1,120 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/storagedailyusage/get_trend.go
package storagedailyusage
import (
"context"
"time"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// GetStorageDailyUsageTrendRequest contains the trend parameters
type GetStorageDailyUsageTrendRequest struct {
UserID gocql.UUID `json:"user_id"`
TrendPeriod string `json:"trend_period"` // "7days", "monthly", "yearly"
Year *int `json:"year,omitempty"`
Month *time.Month `json:"month,omitempty"`
}
type GetStorageDailyUsageTrendUseCase interface {
Execute(ctx context.Context, req *GetStorageDailyUsageTrendRequest) (*storagedailyusage.StorageUsageTrend, error)
}
type getStorageDailyUsageTrendUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo storagedailyusage.StorageDailyUsageRepository
}
func NewGetStorageDailyUsageTrendUseCase(
config *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageDailyUsageTrendUseCase {
logger = logger.Named("GetStorageDailyUsageTrendUseCase")
return &getStorageDailyUsageTrendUseCaseImpl{config, logger, repo}
}
func (uc *getStorageDailyUsageTrendUseCaseImpl) Execute(ctx context.Context, req *GetStorageDailyUsageTrendRequest) (*storagedailyusage.StorageUsageTrend, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if req == nil {
e["request"] = "Request is required"
} else {
if req.UserID.String() == "" {
e["user_id"] = "User ID is required"
}
if req.TrendPeriod == "" {
e["trend_period"] = "Trend period is required"
} else if req.TrendPeriod != "7days" && req.TrendPeriod != "monthly" && req.TrendPeriod != "yearly" {
e["trend_period"] = "Trend period must be one of: 7days, monthly, yearly"
}
// Validate period-specific parameters
switch req.TrendPeriod {
case "monthly":
if req.Year == nil {
e["year"] = "Year is required for monthly trend"
}
if req.Month == nil {
e["month"] = "Month is required for monthly trend"
}
case "yearly":
if req.Year == nil {
e["year"] = "Year is required for yearly trend"
}
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage daily usage trend",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get trend based on period.
//
var trend *storagedailyusage.StorageUsageTrend
var err error
switch req.TrendPeriod {
case "7days":
trend, err = uc.repo.GetLast7DaysTrend(ctx, req.UserID)
case "monthly":
trend, err = uc.repo.GetMonthlyTrend(ctx, req.UserID, *req.Year, *req.Month)
case "yearly":
trend, err = uc.repo.GetYearlyTrend(ctx, req.UserID, *req.Year)
default:
return nil, httperror.NewForBadRequestWithSingleField("trend_period", "Invalid trend period")
}
if err != nil {
uc.logger.Error("Failed to get storage daily usage trend",
zap.String("user_id", req.UserID.String()),
zap.String("trend_period", req.TrendPeriod),
zap.Error(err))
return nil, err
}
uc.logger.Debug("Successfully retrieved storage daily usage trend",
zap.String("user_id", req.UserID.String()),
zap.String("trend_period", req.TrendPeriod),
zap.Int("daily_usages_count", len(trend.DailyUsages)),
zap.Int64("total_added", trend.TotalAdded),
zap.Int64("total_removed", trend.TotalRemoved),
zap.Int64("net_change", trend.NetChange))
return trend, nil
}

View file

@ -0,0 +1,185 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/storagedailyusage/get_usage_by_date_range.go
package storagedailyusage
import (
"context"
"time"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// GetStorageUsageByDateRangeRequest contains the date range parameters
type GetStorageUsageByDateRangeRequest struct {
UserID gocql.UUID `json:"user_id"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
}
// GetStorageUsageByDateRangeResponse contains the usage data for the date range
type GetStorageUsageByDateRangeResponse struct {
UserID gocql.UUID `json:"user_id"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
DailyUsages []*storagedailyusage.StorageDailyUsage `json:"daily_usages"`
Summary *DateRangeSummary `json:"summary"`
}
// DateRangeSummary contains aggregated statistics for the date range
type DateRangeSummary struct {
TotalDays int `json:"total_days"`
DaysWithData int `json:"days_with_data"`
TotalAdded int64 `json:"total_added"`
TotalRemoved int64 `json:"total_removed"`
NetChange int64 `json:"net_change"`
AverageDailyAdd float64 `json:"average_daily_add"`
PeakUsageDay *time.Time `json:"peak_usage_day,omitempty"`
PeakUsageBytes int64 `json:"peak_usage_bytes"`
LowestUsageDay *time.Time `json:"lowest_usage_day,omitempty"`
LowestUsageBytes int64 `json:"lowest_usage_bytes"`
}
type GetStorageUsageByDateRangeUseCase interface {
Execute(ctx context.Context, req *GetStorageUsageByDateRangeRequest) (*GetStorageUsageByDateRangeResponse, error)
}
type getStorageUsageByDateRangeUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo storagedailyusage.StorageDailyUsageRepository
}
func NewGetStorageUsageByDateRangeUseCase(
config *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageUsageByDateRangeUseCase {
logger = logger.Named("GetStorageUsageByDateRangeUseCase")
return &getStorageUsageByDateRangeUseCaseImpl{config, logger, repo}
}
func (uc *getStorageUsageByDateRangeUseCaseImpl) Execute(ctx context.Context, req *GetStorageUsageByDateRangeRequest) (*GetStorageUsageByDateRangeResponse, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if req == nil {
e["request"] = "Request is required"
} else {
if req.UserID.String() == "" {
e["user_id"] = "User ID is required"
}
if req.StartDate.IsZero() {
e["start_date"] = "Start date is required"
}
if req.EndDate.IsZero() {
e["end_date"] = "End date is required"
}
if !req.StartDate.IsZero() && !req.EndDate.IsZero() && req.StartDate.After(req.EndDate) {
e["date_range"] = "Start date must be before or equal to end date"
}
// Check for reasonable date range (max 1 year)
if !req.StartDate.IsZero() && !req.EndDate.IsZero() {
daysDiff := int(req.EndDate.Sub(req.StartDate).Hours() / 24)
if daysDiff > 365 {
e["date_range"] = "Date range cannot exceed 365 days"
}
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage usage by date range",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get usage data from repository.
//
// Truncate dates to ensure we're working with date-only values
startDate := req.StartDate.Truncate(24 * time.Hour)
endDate := req.EndDate.Truncate(24 * time.Hour)
dailyUsages, err := uc.repo.GetByUserDateRange(ctx, req.UserID, startDate, endDate)
if err != nil {
uc.logger.Error("Failed to get storage usage by date range",
zap.String("user_id", req.UserID.String()),
zap.Time("start_date", startDate),
zap.Time("end_date", endDate),
zap.Error(err))
return nil, err
}
//
// STEP 3: Generate summary statistics.
//
summary := uc.generateDateRangeSummary(startDate, endDate, dailyUsages)
response := &GetStorageUsageByDateRangeResponse{
UserID: req.UserID,
StartDate: startDate,
EndDate: endDate,
DailyUsages: dailyUsages,
Summary: summary,
}
uc.logger.Debug("Successfully retrieved storage usage by date range",
zap.String("user_id", req.UserID.String()),
zap.Time("start_date", startDate),
zap.Time("end_date", endDate),
zap.Int("daily_usages_count", len(dailyUsages)),
zap.Int("days_with_data", summary.DaysWithData),
zap.Int64("net_change", summary.NetChange))
return response, nil
}
// generateDateRangeSummary creates summary statistics for the date range
func (uc *getStorageUsageByDateRangeUseCaseImpl) generateDateRangeSummary(startDate, endDate time.Time, dailyUsages []*storagedailyusage.StorageDailyUsage) *DateRangeSummary {
totalDays := int(endDate.Sub(startDate).Hours()/24) + 1
summary := &DateRangeSummary{
TotalDays: totalDays,
DaysWithData: len(dailyUsages),
LowestUsageBytes: int64(^uint64(0) >> 1), // Max int64 value as initial
}
if len(dailyUsages) == 0 {
summary.LowestUsageBytes = 0
return summary
}
for _, usage := range dailyUsages {
summary.TotalAdded += usage.TotalAddBytes
summary.TotalRemoved += usage.TotalRemoveBytes
// Track peak usage
if usage.TotalBytes > summary.PeakUsageBytes {
summary.PeakUsageBytes = usage.TotalBytes
peakDay := usage.UsageDay
summary.PeakUsageDay = &peakDay
}
// Track lowest usage
if usage.TotalBytes < summary.LowestUsageBytes {
summary.LowestUsageBytes = usage.TotalBytes
lowestDay := usage.UsageDay
summary.LowestUsageDay = &lowestDay
}
}
summary.NetChange = summary.TotalAdded - summary.TotalRemoved
// Calculate average daily add (only for days with data)
if summary.DaysWithData > 0 {
summary.AverageDailyAdd = float64(summary.TotalAdded) / float64(summary.DaysWithData)
}
return summary
}

View file

@ -0,0 +1,100 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/storagedailyusage/get_usage_summary.go
package storagedailyusage
import (
"context"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// GetStorageUsageSummaryRequest contains the summary parameters
type GetStorageUsageSummaryRequest struct {
UserID gocql.UUID `json:"user_id"`
SummaryType string `json:"summary_type"` // "current_month", "current_year"
}
type GetStorageUsageSummaryUseCase interface {
Execute(ctx context.Context, req *GetStorageUsageSummaryRequest) (*storagedailyusage.StorageUsageSummary, error)
}
type getStorageUsageSummaryUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo storagedailyusage.StorageDailyUsageRepository
}
func NewGetStorageUsageSummaryUseCase(
config *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageUsageSummaryUseCase {
logger = logger.Named("GetStorageUsageSummaryUseCase")
return &getStorageUsageSummaryUseCaseImpl{config, logger, repo}
}
func (uc *getStorageUsageSummaryUseCaseImpl) Execute(ctx context.Context, req *GetStorageUsageSummaryRequest) (*storagedailyusage.StorageUsageSummary, error) {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if req == nil {
e["request"] = "Request is required"
} else {
if req.UserID.String() == "" {
e["user_id"] = "User ID is required"
}
if req.SummaryType == "" {
e["summary_type"] = "Summary type is required"
} else if req.SummaryType != "current_month" && req.SummaryType != "current_year" {
e["summary_type"] = "Summary type must be one of: current_month, current_year"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating get storage usage summary",
zap.Any("error", e))
return nil, httperror.NewForBadRequest(&e)
}
//
// STEP 2: Get summary based on type.
//
var summary *storagedailyusage.StorageUsageSummary
var err error
switch req.SummaryType {
case "current_month":
summary, err = uc.repo.GetCurrentMonthUsage(ctx, req.UserID)
case "current_year":
summary, err = uc.repo.GetCurrentYearUsage(ctx, req.UserID)
default:
return nil, httperror.NewForBadRequestWithSingleField("summary_type", "Invalid summary type")
}
if err != nil {
uc.logger.Error("Failed to get storage usage summary",
zap.String("user_id", req.UserID.String()),
zap.String("summary_type", req.SummaryType),
zap.Error(err))
return nil, err
}
uc.logger.Debug("Successfully retrieved storage usage summary",
zap.String("user_id", req.UserID.String()),
zap.String("summary_type", req.SummaryType),
zap.Int64("current_usage", summary.CurrentUsage),
zap.Int64("total_added", summary.TotalAdded),
zap.Int64("total_removed", summary.TotalRemoved),
zap.Int64("net_change", summary.NetChange),
zap.Int("days_with_data", summary.DaysWithData))
return summary, nil
}

View file

@ -0,0 +1,49 @@
package storagedailyusage
import (
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
)
// Wire providers for storage daily usage use cases
func ProvideGetStorageDailyUsageTrendUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageDailyUsageTrendUseCase {
return NewGetStorageDailyUsageTrendUseCase(cfg, logger, repo)
}
func ProvideGetStorageUsageSummaryUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageUsageSummaryUseCase {
return NewGetStorageUsageSummaryUseCase(cfg, logger, repo)
}
func ProvideGetStorageUsageByDateRangeUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) GetStorageUsageByDateRangeUseCase {
return NewGetStorageUsageByDateRangeUseCase(cfg, logger, repo)
}
func ProvideUpdateStorageUsageUseCase(
cfg *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) UpdateStorageUsageUseCase {
return NewUpdateStorageUsageUseCase(cfg, logger, repo)
}
func ProvideDeleteByUserUseCase(
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) DeleteByUserUseCase {
return NewDeleteByUserUseCase(logger, repo)
}

View file

@ -0,0 +1,124 @@
// monorepo/cloud/maplefile-backend/internal/maplefile/usecase/storagedailyusage/update_usage.go
package storagedailyusage
import (
"context"
"time"
"go.uber.org/zap"
"github.com/gocql/gocql"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/storagedailyusage"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
)
// UpdateStorageUsageRequest contains the update parameters
type UpdateStorageUsageRequest struct {
UserID gocql.UUID `json:"user_id"`
UsageDay *time.Time `json:"usage_day,omitempty"` // Optional, defaults to today
TotalBytes int64 `json:"total_bytes"`
AddBytes int64 `json:"add_bytes"`
RemoveBytes int64 `json:"remove_bytes"`
IsIncrement bool `json:"is_increment"` // If true, increment existing values; if false, set absolute values
}
type UpdateStorageUsageUseCase interface {
Execute(ctx context.Context, req *UpdateStorageUsageRequest) error
}
type updateStorageUsageUseCaseImpl struct {
config *config.Configuration
logger *zap.Logger
repo storagedailyusage.StorageDailyUsageRepository
}
func NewUpdateStorageUsageUseCase(
config *config.Configuration,
logger *zap.Logger,
repo storagedailyusage.StorageDailyUsageRepository,
) UpdateStorageUsageUseCase {
logger = logger.Named("UpdateStorageUsageUseCase")
return &updateStorageUsageUseCaseImpl{config, logger, repo}
}
func (uc *updateStorageUsageUseCaseImpl) Execute(ctx context.Context, req *UpdateStorageUsageRequest) error {
//
// STEP 1: Validation.
//
e := make(map[string]string)
if req == nil {
e["request"] = "Request is required"
} else {
if req.UserID.String() == "" {
e["user_id"] = "User ID is required"
}
if req.AddBytes < 0 {
e["add_bytes"] = "Add bytes cannot be negative"
}
if req.RemoveBytes < 0 {
e["remove_bytes"] = "Remove bytes cannot be negative"
}
if !req.IsIncrement && req.TotalBytes < 0 {
e["total_bytes"] = "Total bytes cannot be negative when setting absolute values"
}
}
if len(e) != 0 {
uc.logger.Warn("Failed validating update storage usage",
zap.Any("error", e))
return httperror.NewForBadRequest(&e)
}
//
// STEP 2: Set usage day if not provided.
//
usageDay := time.Now().Truncate(24 * time.Hour)
if req.UsageDay != nil {
usageDay = req.UsageDay.Truncate(24 * time.Hour)
}
//
// STEP 3: Update or increment usage.
//
var err error
if req.IsIncrement {
// Increment existing values
err = uc.repo.IncrementUsage(ctx, req.UserID, usageDay, req.TotalBytes, req.AddBytes, req.RemoveBytes)
} else {
// Set absolute values
usage := &storagedailyusage.StorageDailyUsage{
UserID: req.UserID,
UsageDay: usageDay,
TotalBytes: req.TotalBytes,
TotalAddBytes: req.AddBytes,
TotalRemoveBytes: req.RemoveBytes,
}
err = uc.repo.UpdateOrCreate(ctx, usage)
}
if err != nil {
uc.logger.Error("Failed to update storage usage",
zap.String("user_id", req.UserID.String()),
zap.Time("usage_day", usageDay),
zap.Int64("total_bytes", req.TotalBytes),
zap.Int64("add_bytes", req.AddBytes),
zap.Int64("remove_bytes", req.RemoveBytes),
zap.Bool("is_increment", req.IsIncrement),
zap.Error(err))
return err
}
uc.logger.Debug("Successfully updated storage usage",
zap.String("user_id", req.UserID.String()),
zap.Time("usage_day", usageDay),
zap.Int64("total_bytes", req.TotalBytes),
zap.Int64("add_bytes", req.AddBytes),
zap.Int64("remove_bytes", req.RemoveBytes),
zap.Bool("is_increment", req.IsIncrement))
return nil
}