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,104 @@
package user
import (
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcrypt"
)
// CreateUserEntityUseCase creates and validates a user domain entity
type CreateUserEntityUseCase struct {
ipEncryptor *ipcrypt.IPEncryptor
logger *zap.Logger
}
// ProvideCreateUserEntityUseCase creates a new CreateUserEntityUseCase
func ProvideCreateUserEntityUseCase(ipEncryptor *ipcrypt.IPEncryptor, logger *zap.Logger) *CreateUserEntityUseCase {
return &CreateUserEntityUseCase{
ipEncryptor: ipEncryptor,
logger: logger.Named("create-user-entity-usecase"),
}
}
// Execute creates a new user domain entity with validation
func (uc *CreateUserEntityUseCase) Execute(tenantID string, input *CreateUserInput) (*domainuser.User, error) {
// Set default role if not provided
role := int(input.Role)
if role == 0 {
role = 1 // Default role
}
now := time.Now()
// CWE-359: Encrypt IP address for GDPR compliance
encryptedIP := ""
if input.CreatedFromIPAddress != "" {
encrypted, err := uc.ipEncryptor.Encrypt(input.CreatedFromIPAddress)
if err != nil {
uc.logger.Error("failed to encrypt IP address",
zap.Error(err),
zap.String("ip_plain", input.CreatedFromIPAddress))
// Don't fail user creation if encryption fails, just log it
encryptedIP = ""
} else {
encryptedIP = encrypted
uc.logger.Debug("IP address encrypted for user creation")
}
}
// Create domain entity
user := &domainuser.User{
ID: gocql.TimeUUID().String(),
TenantID: tenantID,
Email: input.Email,
FirstName: input.FirstName,
LastName: input.LastName,
Name: input.FirstName + " " + input.LastName, // Computed from FirstName + LastName
Role: role,
Status: 1, // Default active status
ProfileData: &domainuser.UserProfileData{
AgreeTermsOfService: true, // Default to true for entity creation
},
SecurityData: &domainuser.UserSecurityData{
PasswordHash: input.PasswordHash,
PasswordHashAlgorithm: "argon2id",
WasEmailVerified: false,
},
Metadata: &domainuser.UserMetadata{
CreatedFromIPAddress: encryptedIP, // CWE-359: Encrypted IP
CreatedFromIPTimestamp: now, // CWE-359: For 90-day GDPR expiration
ModifiedFromIPAddress: encryptedIP, // CWE-359: Encrypted IP
ModifiedFromIPTimestamp: now, // CWE-359: For 90-day GDPR expiration
CreatedAt: now,
ModifiedAt: now,
},
CreatedAt: now,
UpdatedAt: now,
}
// Validate domain entity
if err := user.Validate(); err != nil {
// CWE-532: Use hashed email to prevent PII in logs
uc.logger.Warn("user validation failed",
logger.EmailHash(input.Email),
zap.Error(err))
return nil, err
}
// CWE-532: Use hashed email to prevent PII in logs
uc.logger.Debug("user entity created and validated",
zap.String("user_id", user.ID),
logger.EmailHash(user.Email),
zap.Int("role", user.Role))
return user, nil
}

View file

@ -0,0 +1,61 @@
package user
import (
"context"
"fmt"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
)
// DeleteUserUseCase handles user deletion operations
// Used as compensating transaction in SAGA pattern
type DeleteUserUseCase struct {
repo user.Repository
logger *zap.Logger
}
// ProvideDeleteUserUseCase creates a new DeleteUserUseCase for dependency injection
func ProvideDeleteUserUseCase(
repo user.Repository,
logger *zap.Logger,
) *DeleteUserUseCase {
return &DeleteUserUseCase{
repo: repo,
logger: logger.Named("delete-user-usecase"),
}
}
// Execute deletes a user by ID within a tenant
// This is used as a compensating transaction
//
// IMPORTANT: This operation must be idempotent!
func (uc *DeleteUserUseCase) Execute(ctx context.Context, tenantID, userID string) error {
uc.logger.Info("deleting user",
zap.String("tenant_id", tenantID),
zap.String("user_id", userID))
// Validate inputs
if tenantID == "" {
return fmt.Errorf("tenant ID cannot be empty")
}
if userID == "" {
return fmt.Errorf("user ID cannot be empty")
}
// Execute deletion using repository
if err := uc.repo.Delete(ctx, tenantID, userID); err != nil {
uc.logger.Error("failed to delete user",
zap.String("tenant_id", tenantID),
zap.String("user_id", userID),
zap.Error(err))
return fmt.Errorf("failed to delete user: %w", err)
}
uc.logger.Info("user deleted successfully",
zap.String("tenant_id", tenantID),
zap.String("user_id", userID))
return nil
}

View file

@ -0,0 +1,59 @@
package user
import (
"context"
"time"
"go.uber.org/zap"
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
)
// GetUserUseCase handles retrieving a user by ID
type GetUserUseCase struct {
repo domainuser.Repository
logger *zap.Logger
}
// ProvideGetUserUseCase creates a new GetUserUseCase
func ProvideGetUserUseCase(repo domainuser.Repository, logger *zap.Logger) *GetUserUseCase {
return &GetUserUseCase{
repo: repo,
logger: logger,
}
}
// GetUserInput is the input for getting a user
type GetUserInput struct {
ID string
}
// GetUserOutput is the output after getting a user
type GetUserOutput struct {
ID string
Email string
Name string
CreatedAt time.Time
UpdatedAt time.Time
}
// Execute retrieves a user by ID
func (uc *GetUserUseCase) Execute(ctx context.Context, tenantID string, input *GetUserInput) (*GetUserOutput, error) {
uc.logger.Debug("executing get user use case",
zap.String("tenant_id", tenantID),
zap.String("id", input.ID))
user, err := uc.repo.GetByID(ctx, tenantID, input.ID)
if err != nil {
uc.logger.Error("failed to get user", zap.Error(err))
return nil, err
}
return &GetUserOutput{
ID: user.ID,
Email: user.Email,
Name: user.Name,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}, nil
}

View file

@ -0,0 +1,46 @@
package user
import (
"context"
"fmt"
"go.uber.org/zap"
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
)
// SaveUserToRepoUseCase saves a user to the repository
type SaveUserToRepoUseCase struct {
repo domainuser.Repository
logger *zap.Logger
}
// ProvideSaveUserToRepoUseCase creates a new SaveUserToRepoUseCase
func ProvideSaveUserToRepoUseCase(
repo domainuser.Repository,
logger *zap.Logger,
) *SaveUserToRepoUseCase {
return &SaveUserToRepoUseCase{
repo: repo,
logger: logger.Named("save-user-to-repo-usecase"),
}
}
// Execute saves a user to the repository
func (uc *SaveUserToRepoUseCase) Execute(ctx context.Context, tenantID string, user *domainuser.User) error {
if err := uc.repo.Create(ctx, tenantID, user); err != nil {
uc.logger.Error("failed to create user in repository",
zap.String("user_id", user.ID),
zap.String("email", user.Email),
zap.String("tenant_id", tenantID),
zap.Error(err))
return fmt.Errorf("failed to create user: %w", err)
}
uc.logger.Info("user saved to repository",
zap.String("user_id", user.ID),
zap.String("email", user.Email),
zap.String("tenant_id", tenantID))
return nil
}

View file

@ -0,0 +1,30 @@
package user
import "time"
// CreateUserInput is the input for creating a user (IDO - Internal Data Object)
type CreateUserInput struct {
Email string
FirstName string
LastName string
PasswordHash string // Optional: Hashed password (if creating user with password)
PasswordHashAlgorithm string // Algorithm used for password hashing (e.g., "argon2id")
Role int // User role (numeric value)
Timezone string
// Consent fields
AgreeTermsOfService bool
AgreePromotions bool
AgreeToTrackingAcrossThirdPartyAppsAndServices bool
// Optional: IP address for audit trail
CreatedFromIPAddress string
}
// CreateUserOutput is the output after creating a user (IDO - Internal Data Object)
type CreateUserOutput struct {
ID string
Email string
Name string
CreatedAt time.Time
}

View file

@ -0,0 +1,53 @@
package user
import (
"context"
"go.uber.org/zap"
domainuser "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/user"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
)
// ValidateUserEmailUniqueUseCase validates that a user email is unique within a tenant
type ValidateUserEmailUniqueUseCase struct {
repo domainuser.Repository
logger *zap.Logger
}
// ProvideValidateUserEmailUniqueUseCase creates a new ValidateUserEmailUniqueUseCase
func ProvideValidateUserEmailUniqueUseCase(
repo domainuser.Repository,
logger *zap.Logger,
) *ValidateUserEmailUniqueUseCase {
return &ValidateUserEmailUniqueUseCase{
repo: repo,
logger: logger.Named("validate-user-email-unique-usecase"),
}
}
// Execute validates that a user email is unique within a tenant
func (uc *ValidateUserEmailUniqueUseCase) Execute(ctx context.Context, tenantID, email string) error {
existing, err := uc.repo.GetByEmail(ctx, tenantID, email)
if err == nil && existing != nil {
// CWE-532: Use redacted email for logging
uc.logger.Warn("user email already exists",
logger.EmailHash(email),
logger.SafeEmail("email_redacted", email),
zap.String("tenant_id", tenantID))
return domainuser.ErrUserAlreadyExists
}
// Ignore ErrUserNotFound - it's expected (email is available)
if err != nil && err != domainuser.ErrUserNotFound {
uc.logger.Error("failed to check user email uniqueness", zap.Error(err))
return err
}
// CWE-532: Use redacted email for logging
uc.logger.Debug("user email is unique",
logger.EmailHash(email),
logger.SafeEmail("email_redacted", email),
zap.String("tenant_id", tenantID))
return nil
}