// 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 }