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
155
cloud/maplepress-backend/internal/usecase/site/create.go
Normal file
155
cloud/maplepress-backend/internal/usecase/site/create.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package site
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"go.uber.org/zap"
|
||||
|
||||
domainsite "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/site"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/apikey"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcrypt"
|
||||
)
|
||||
|
||||
// CreateSiteUseCase handles site creation business logic
|
||||
type CreateSiteUseCase struct {
|
||||
repo domainsite.Repository
|
||||
apiKeyGen apikey.Generator
|
||||
apiKeyHasher apikey.Hasher
|
||||
ipEncryptor *ipcrypt.IPEncryptor
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// ProvideCreateSiteUseCase creates a new CreateSiteUseCase
|
||||
func ProvideCreateSiteUseCase(
|
||||
repo domainsite.Repository,
|
||||
apiKeyGen apikey.Generator,
|
||||
apiKeyHasher apikey.Hasher,
|
||||
ipEncryptor *ipcrypt.IPEncryptor,
|
||||
logger *zap.Logger,
|
||||
) *CreateSiteUseCase {
|
||||
return &CreateSiteUseCase{
|
||||
repo: repo,
|
||||
apiKeyGen: apiKeyGen,
|
||||
apiKeyHasher: apiKeyHasher,
|
||||
ipEncryptor: ipEncryptor,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSiteInput is the input for creating a site
|
||||
type CreateSiteInput struct {
|
||||
Domain string
|
||||
SiteURL string
|
||||
TestMode bool // true = generate test_sk_ key (skips verification)
|
||||
IPAddress string // Plain IP address (will be encrypted before storage)
|
||||
}
|
||||
|
||||
// CreateSiteOutput is the output after creating a site
|
||||
type CreateSiteOutput struct {
|
||||
ID string `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
SiteURL string `json:"site_url"`
|
||||
APIKey string `json:"api_key"` // ONLY shown once!
|
||||
VerificationToken string `json:"verification_token"`
|
||||
Status string `json:"status"`
|
||||
SearchIndexName string `json:"search_index_name"`
|
||||
}
|
||||
|
||||
// Execute creates a new site
|
||||
func (uc *CreateSiteUseCase) Execute(ctx context.Context, tenantID gocql.UUID, input *CreateSiteInput) (*CreateSiteOutput, error) {
|
||||
uc.logger.Info("executing create site use case",
|
||||
zap.String("tenant_id", tenantID.String()),
|
||||
zap.String("domain", input.Domain))
|
||||
|
||||
// Generate API key (test or live based on test_mode)
|
||||
var apiKey string
|
||||
var err error
|
||||
if input.TestMode {
|
||||
apiKey, err = uc.apiKeyGen.GenerateTest() // test_sk_...
|
||||
uc.logger.Info("generating test API key for development")
|
||||
} else {
|
||||
apiKey, err = uc.apiKeyGen.Generate() // live_sk_...
|
||||
}
|
||||
if err != nil {
|
||||
uc.logger.Error("failed to generate API key", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to generate API key: %w", err)
|
||||
}
|
||||
|
||||
// Hash API key
|
||||
apiKeyHash := uc.apiKeyHasher.Hash(apiKey)
|
||||
apiKeyPrefix := apikey.ExtractPrefix(apiKey)
|
||||
apiKeyLastFour := apikey.ExtractLastFour(apiKey)
|
||||
|
||||
// Generate verification token
|
||||
verificationToken, err := generateVerificationToken()
|
||||
if err != nil {
|
||||
uc.logger.Error("failed to generate verification token", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to generate verification token: %w", err)
|
||||
}
|
||||
|
||||
// Encrypt IP address (CWE-359: GDPR compliance)
|
||||
encryptedIP, err := uc.ipEncryptor.Encrypt(input.IPAddress)
|
||||
if err != nil {
|
||||
uc.logger.Error("failed to encrypt IP address",
|
||||
zap.String("domain", input.Domain),
|
||||
zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to encrypt IP address: %w", err)
|
||||
}
|
||||
|
||||
// Create site entity (no plan tier - usage-based billing)
|
||||
site := domainsite.NewSite(
|
||||
tenantID,
|
||||
input.Domain,
|
||||
input.SiteURL,
|
||||
apiKeyHash,
|
||||
apiKeyPrefix,
|
||||
apiKeyLastFour,
|
||||
encryptedIP,
|
||||
)
|
||||
site.VerificationToken = verificationToken
|
||||
|
||||
// Check if domain already exists
|
||||
exists, err := uc.repo.DomainExists(ctx, input.Domain)
|
||||
if err != nil {
|
||||
uc.logger.Error("failed to check domain existence", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to check domain: %w", err)
|
||||
}
|
||||
if exists {
|
||||
uc.logger.Warn("domain already exists", zap.String("domain", input.Domain))
|
||||
return nil, domainsite.ErrDomainAlreadyExists
|
||||
}
|
||||
|
||||
// Create in repository (writes to all 4 Cassandra tables)
|
||||
if err := uc.repo.Create(ctx, site); err != nil {
|
||||
uc.logger.Error("failed to create site", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uc.logger.Info("site created successfully",
|
||||
zap.String("site_id", site.ID.String()),
|
||||
zap.String("domain", site.Domain))
|
||||
|
||||
return &CreateSiteOutput{
|
||||
ID: site.ID.String(),
|
||||
Domain: site.Domain,
|
||||
SiteURL: site.SiteURL,
|
||||
APIKey: apiKey, // PLAINTEXT - only shown once!
|
||||
VerificationToken: verificationToken,
|
||||
Status: site.Status,
|
||||
SearchIndexName: site.SearchIndexName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateVerificationToken generates a cryptographically secure verification token
|
||||
func generateVerificationToken() (string, error) {
|
||||
b := make([]byte, 16) // 16 bytes = 128 bits
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
token := base64.RawURLEncoding.EncodeToString(b)
|
||||
return "mvp_" + token, nil // mvp = maplepress verify
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue