Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
|
|
@ -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
|
||||
}
|
||||
60
cloud/maplepress-backend/internal/usecase/tenant/delete.go
Normal file
60
cloud/maplepress-backend/internal/usecase/tenant/delete.go
Normal 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
|
||||
}
|
||||
72
cloud/maplepress-backend/internal/usecase/tenant/get.go
Normal file
72
cloud/maplepress-backend/internal/usecase/tenant/get.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue