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,87 @@
package tenant
import (
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcrypt"
)
// CreateTenantInput represents the input for creating a tenant
type CreateTenantInput struct {
Name string
Slug string
CreatedFromIPAddress string // Plain IP address (will be encrypted before storage)
}
// CreateTenantOutput represents the output after creating a tenant
type CreateTenantOutput struct {
ID string
Name string
Slug string
Status string
CreatedAt time.Time
}
// CreateTenantEntityUseCase creates and validates a tenant domain entity
type CreateTenantEntityUseCase struct {
ipEncryptor *ipcrypt.IPEncryptor
logger *zap.Logger
}
// ProvideCreateTenantEntityUseCase creates a new CreateTenantEntityUseCase
func ProvideCreateTenantEntityUseCase(
ipEncryptor *ipcrypt.IPEncryptor,
logger *zap.Logger,
) *CreateTenantEntityUseCase {
return &CreateTenantEntityUseCase{
ipEncryptor: ipEncryptor,
logger: logger.Named("create-tenant-entity-usecase"),
}
}
// Execute creates a new tenant domain entity with validation
func (uc *CreateTenantEntityUseCase) Execute(input *CreateTenantInput) (*domaintenant.Tenant, error) {
now := time.Now()
// Encrypt IP address (CWE-359: GDPR compliance)
encryptedIP, err := uc.ipEncryptor.Encrypt(input.CreatedFromIPAddress)
if err != nil {
uc.logger.Error("failed to encrypt IP address",
zap.String("slug", input.Slug),
zap.Error(err))
return nil, err
}
// Create domain entity
tenant := &domaintenant.Tenant{
ID: gocql.TimeUUID().String(),
Name: input.Name,
Slug: input.Slug,
Status: domaintenant.StatusActive,
CreatedAt: now,
UpdatedAt: now,
// CWE-359: Encrypted IP address tracking for GDPR compliance
CreatedFromIPAddress: encryptedIP,
CreatedFromIPTimestamp: now,
ModifiedFromIPAddress: encryptedIP,
ModifiedFromIPTimestamp: now,
}
// Validate domain entity
if err := tenant.Validate(); err != nil {
uc.logger.Warn("tenant validation failed",
zap.String("slug", input.Slug),
zap.Error(err))
return nil, err
}
uc.logger.Debug("tenant entity created and validated",
zap.String("tenant_id", tenant.ID),
zap.String("slug", tenant.Slug))
return tenant, nil
}

View file

@ -0,0 +1,60 @@
package tenant
import (
"context"
"fmt"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// DeleteTenantUseCase handles tenant deletion operations
// Used as compensating transaction in SAGA pattern
type DeleteTenantUseCase struct {
repo tenant.Repository
logger *zap.Logger
}
// ProvideDeleteTenantUseCase creates a new DeleteTenantUseCase for dependency injection
func ProvideDeleteTenantUseCase(
repo tenant.Repository,
logger *zap.Logger,
) *DeleteTenantUseCase {
return &DeleteTenantUseCase{
repo: repo,
logger: logger.Named("delete-tenant-usecase"),
}
}
// Execute deletes a tenant by ID
// This is used as a compensating transaction when registration fails
//
// IMPORTANT: This operation must be idempotent!
// If called multiple times with the same ID, it should not error
func (uc *DeleteTenantUseCase) Execute(ctx context.Context, tenantID string) error {
uc.logger.Info("deleting tenant",
zap.String("tenant_id", tenantID))
// Validate input
if tenantID == "" {
return fmt.Errorf("tenant ID cannot be empty")
}
// Execute deletion using existing repository method
// The repository handles deletion from all denormalized tables:
// - tenants_by_id
// - tenants_by_slug
// - tenants_by_status
if err := uc.repo.Delete(ctx, tenantID); err != nil {
uc.logger.Error("failed to delete tenant",
zap.String("tenant_id", tenantID),
zap.Error(err))
return fmt.Errorf("failed to delete tenant: %w", err)
}
uc.logger.Info("tenant deleted successfully",
zap.String("tenant_id", tenantID))
return nil
}

View file

@ -0,0 +1,72 @@
package tenant
import (
"context"
"time"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// GetTenantInput represents the input for getting a tenant
type GetTenantInput struct {
ID string
}
// GetTenantBySlugInput represents the input for getting a tenant by slug
type GetTenantBySlugInput struct {
Slug string
}
// GetTenantOutput represents the output after getting a tenant
type GetTenantOutput struct {
ID string
Name string
Slug string
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
// GetTenantUseCase handles tenant retrieval business logic
type GetTenantUseCase struct {
repo domaintenant.Repository
}
// ProvideGetTenantUseCase creates a new GetTenantUseCase
func ProvideGetTenantUseCase(repo domaintenant.Repository) *GetTenantUseCase {
return &GetTenantUseCase{repo: repo}
}
// Execute retrieves a tenant by ID
func (uc *GetTenantUseCase) Execute(ctx context.Context, input *GetTenantInput) (*GetTenantOutput, error) {
tenant, err := uc.repo.GetByID(ctx, input.ID)
if err != nil {
return nil, err
}
return &GetTenantOutput{
ID: tenant.ID,
Name: tenant.Name,
Slug: tenant.Slug,
Status: string(tenant.Status),
CreatedAt: tenant.CreatedAt,
UpdatedAt: tenant.UpdatedAt,
}, nil
}
// ExecuteBySlug retrieves a tenant by slug
func (uc *GetTenantUseCase) ExecuteBySlug(ctx context.Context, input *GetTenantBySlugInput) (*GetTenantOutput, error) {
tenant, err := uc.repo.GetBySlug(ctx, input.Slug)
if err != nil {
return nil, err
}
return &GetTenantOutput{
ID: tenant.ID,
Name: tenant.Name,
Slug: tenant.Slug,
Status: string(tenant.Status),
CreatedAt: tenant.CreatedAt,
UpdatedAt: tenant.UpdatedAt,
}, nil
}

View file

@ -0,0 +1,44 @@
package tenant
import (
"context"
"fmt"
"go.uber.org/zap"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
)
// SaveTenantToRepoUseCase saves a tenant to the repository
type SaveTenantToRepoUseCase struct {
repo domaintenant.Repository
logger *zap.Logger
}
// ProvideSaveTenantToRepoUseCase creates a new SaveTenantToRepoUseCase
func ProvideSaveTenantToRepoUseCase(
repo domaintenant.Repository,
logger *zap.Logger,
) *SaveTenantToRepoUseCase {
return &SaveTenantToRepoUseCase{
repo: repo,
logger: logger.Named("save-tenant-to-repo-usecase"),
}
}
// Execute saves a tenant to the repository
func (uc *SaveTenantToRepoUseCase) Execute(ctx context.Context, tenant *domaintenant.Tenant) error {
if err := uc.repo.Create(ctx, tenant); err != nil {
uc.logger.Error("failed to create tenant in repository",
zap.String("tenant_id", tenant.ID),
zap.String("slug", tenant.Slug),
zap.Error(err))
return fmt.Errorf("failed to create tenant: %w", err)
}
uc.logger.Info("tenant saved to repository",
zap.String("tenant_id", tenant.ID),
zap.String("slug", tenant.Slug))
return nil
}

View file

@ -0,0 +1,51 @@
package tenant
import (
"context"
"go.uber.org/zap"
domaintenant "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/tenant"
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
)
// ValidateTenantSlugUniqueUseCase validates that a tenant slug is unique
type ValidateTenantSlugUniqueUseCase struct {
repo domaintenant.Repository
logger *zap.Logger
}
// ProvideValidateTenantSlugUniqueUseCase creates a new ValidateTenantSlugUniqueUseCase
func ProvideValidateTenantSlugUniqueUseCase(
repo domaintenant.Repository,
logger *zap.Logger,
) *ValidateTenantSlugUniqueUseCase {
return &ValidateTenantSlugUniqueUseCase{
repo: repo,
logger: logger.Named("validate-tenant-slug-unique-usecase"),
}
}
// Execute validates that a tenant slug is unique (not already taken)
func (uc *ValidateTenantSlugUniqueUseCase) Execute(ctx context.Context, slug string) error {
existing, err := uc.repo.GetBySlug(ctx, slug)
if err == nil && existing != nil {
// CWE-532: Use redacted tenant slug for logging
uc.logger.Warn("tenant slug already exists",
logger.TenantSlugHash(slug),
logger.SafeTenantSlug("tenant_slug_redacted", slug))
return domaintenant.ErrTenantExists
}
// Ignore "not found" error (expected case - slug is available)
if err != nil && err != domaintenant.ErrTenantNotFound {
uc.logger.Error("failed to check tenant slug uniqueness", zap.Error(err))
return err
}
// CWE-532: Use redacted tenant slug for logging
uc.logger.Debug("tenant slug is unique",
logger.TenantSlugHash(slug),
logger.SafeTenantSlug("tenant_slug_redacted", slug))
return nil
}