187 lines
5.9 KiB
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
|
|
}
|