monorepo/cloud/maplepress-backend/internal/domain/site/site.go

187 lines
5.9 KiB
Go

// File Path: monorepo/cloud/maplepress-backend/internal/domain/site/site.go
package site
import (
"time"
"github.com/gocql/gocql"
)
// Site represents a WordPress site registered in the system
type Site struct {
// Core Identity
ID gocql.UUID `json:"id"`
TenantID gocql.UUID `json:"tenant_id"`
// Site Information
SiteURL string `json:"site_url"` // Full URL: https://example.com
Domain string `json:"domain"` // Extracted: example.com
// Authentication
APIKeyHash string `json:"-"` // SHA-256 hash, never exposed in JSON
APIKeyPrefix string `json:"api_key_prefix"` // "live_sk_a1b2" for display
APIKeyLastFour string `json:"api_key_last_four"` // Last 4 chars for display
// Status & Verification
Status string `json:"status"` // active, inactive, pending, suspended, archived
IsVerified bool `json:"is_verified"`
VerificationToken string `json:"-"` // Never exposed
// Search & Indexing
SearchIndexName string `json:"search_index_name"`
TotalPagesIndexed int64 `json:"total_pages_indexed"` // All-time total for stats
LastIndexedAt time.Time `json:"last_indexed_at,omitempty"`
// Plugin Info
PluginVersion string `json:"plugin_version,omitempty"`
// Usage Tracking (for billing) - no quotas/limits
StorageUsedBytes int64 `json:"storage_used_bytes"` // Current storage usage
SearchRequestsCount int64 `json:"search_requests_count"` // Monthly search count
MonthlyPagesIndexed int64 `json:"monthly_pages_indexed"` // Monthly indexing count
LastResetAt time.Time `json:"last_reset_at"` // Last monthly reset
// Metadata (optional fields)
Language string `json:"language,omitempty"` // ISO 639-1
Timezone string `json:"timezone,omitempty"` // IANA timezone
Notes string `json:"notes,omitempty"`
// Audit
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// CWE-359: IP address tracking for GDPR compliance (90-day expiration)
CreatedFromIPAddress string `json:"-"` // Encrypted IP address, never exposed in JSON
CreatedFromIPTimestamp time.Time `json:"-"` // For 90-day expiration tracking
ModifiedFromIPAddress string `json:"-"` // Encrypted IP address, never exposed in JSON
ModifiedFromIPTimestamp time.Time `json:"-"` // For 90-day expiration tracking
}
// Status constants
const (
StatusPending = "pending" // Site created, awaiting verification
StatusActive = "active" // Site verified and operational
StatusInactive = "inactive" // User temporarily disabled
StatusSuspended = "suspended" // Suspended due to violation or non-payment
StatusArchived = "archived" // Soft deleted
)
// NewSite creates a new Site entity with defaults
func NewSite(tenantID gocql.UUID, domain, siteURL string, apiKeyHash, apiKeyPrefix, apiKeyLastFour string, encryptedIP string) *Site {
now := time.Now()
siteID := gocql.TimeUUID()
return &Site{
ID: siteID,
TenantID: tenantID,
Domain: domain,
SiteURL: siteURL,
APIKeyHash: apiKeyHash,
APIKeyPrefix: apiKeyPrefix,
APIKeyLastFour: apiKeyLastFour,
Status: StatusPending,
IsVerified: false,
VerificationToken: "", // Set by caller
SearchIndexName: "site_" + siteID.String(),
TotalPagesIndexed: 0,
PluginVersion: "",
// Usage tracking (no quotas/limits)
StorageUsedBytes: 0,
SearchRequestsCount: 0,
MonthlyPagesIndexed: 0,
LastResetAt: now,
Language: "",
Timezone: "",
Notes: "",
CreatedAt: now,
UpdatedAt: now,
// CWE-359: Encrypted IP address tracking for GDPR compliance
CreatedFromIPAddress: encryptedIP,
CreatedFromIPTimestamp: now,
ModifiedFromIPAddress: encryptedIP,
ModifiedFromIPTimestamp: now,
}
}
// IsActive checks if the site is active and verified
func (s *Site) IsActive() bool {
return s.Status == StatusActive && s.IsVerified
}
// IsTestMode checks if the site is using a test API key
func (s *Site) IsTestMode() bool {
// Check if API key prefix starts with "test_sk_"
return len(s.APIKeyPrefix) >= 7 && s.APIKeyPrefix[:7] == "test_sk"
}
// RequiresVerification checks if the site requires verification
// Test mode sites skip verification for development
func (s *Site) RequiresVerification() bool {
return !s.IsTestMode()
}
// CanAccessAPI checks if the site can access the API
// More lenient than IsActive - allows pending sites for initial setup
func (s *Site) CanAccessAPI() bool {
// Allow active sites (fully verified)
if s.Status == StatusActive {
return true
}
// Allow pending sites (waiting for verification) for initial setup
if s.Status == StatusPending {
return true
}
// Block inactive, suspended, or archived sites
return false
}
// IncrementSearchCount increments the search request counter
func (s *Site) IncrementSearchCount() {
s.SearchRequestsCount++
s.UpdatedAt = time.Now()
}
// IncrementPageCount increments the indexed page counter (lifetime total)
func (s *Site) IncrementPageCount() {
s.TotalPagesIndexed++
s.UpdatedAt = time.Now()
}
// IncrementMonthlyPageCount increments both lifetime and monthly page counters
func (s *Site) IncrementMonthlyPageCount(count int64) {
s.TotalPagesIndexed += count
s.MonthlyPagesIndexed += count
s.LastIndexedAt = time.Now()
s.UpdatedAt = time.Now()
}
// UpdateStorageUsed updates the storage usage
func (s *Site) UpdateStorageUsed(bytes int64) {
s.StorageUsedBytes = bytes
s.UpdatedAt = time.Now()
}
// Verify marks the site as verified
func (s *Site) Verify() {
s.IsVerified = true
s.Status = StatusActive
s.VerificationToken = "" // Clear token after verification
s.UpdatedAt = time.Now()
}
// ResetMonthlyUsage resets monthly usage counters for billing cycles
func (s *Site) ResetMonthlyUsage() {
now := time.Now()
// Reset usage counters (no quotas)
s.SearchRequestsCount = 0
s.MonthlyPagesIndexed = 0
s.LastResetAt = now
s.UpdatedAt = now
}