licensing

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 25 Imported by: 0

README

GraphDB Licensing Package

Server-based license validation with caching, background revalidation, and graceful degradation.

Features

  • Server-based validation: Validates licenses against license server
  • Multi-layer caching: 24h primary cache + 7d fallback cache
  • Fail-open design: Defaults to community tier if license server unreachable
  • Background revalidation: Checks license every 24h without blocking startup
  • Feature gating: Tier-based access control for Pro/Enterprise features
  • Zero-downtime: License server outages don't brick customer instances

Quick Start

1. Initialize License Manager

In your main.go:

import "github.com/dd0wney/cluso-graphdb/pkg/licensing"

func main() {
    // Get license configuration from environment
    licenseKey := os.Getenv("GRAPHDB_LICENSE_KEY")
    licenseServerURL := os.Getenv("LICENSE_SERVER_URL")
    if licenseServerURL == "" {
        licenseServerURL = "https://license.graphdb.com"
    }

    // Initialize global license manager
    licensing.InitGlobal(licenseKey, licenseServerURL)

    // Get current license info
    license := licensing.Global().GetLicense()
    log.Printf("License tier: %s, valid: %v", license.Tier, license.IsValid())

    // ... rest of your application

    // Clean shutdown
    defer licensing.Global().Stop()
}
2. Gate Features in API Handlers
Simple feature check
func HandlePageRank(w http.ResponseWriter, r *http.Request) {
    // Check if feature is available
    if !licensing.Global().HasFeature(licensing.FeaturePageRank) {
        http.Error(w, "PageRank requires Pro or Enterprise tier", http.StatusForbidden)
        return
    }

    // Feature is available, proceed
    results := runPageRank()
    json.NewEncoder(w).Encode(results)
}
func HandleFraudDetection(w http.ResponseWriter, r *http.Request) {
    // CheckFeature returns helpful error message
    if err := licensing.Global().CheckFeature(licensing.FeatureFraudDetection); err != nil {
        // Error includes: "Feature 'fraud_detection' requires pro tier (current tier: community).
        // Upgrade at https://graphdb.com/pricing"
        http.Error(w, err.Error(), http.StatusForbidden)
        return
    }

    results := detectFraudRings()
    json.NewEncoder(w).Encode(results)
}
Tier-based feature fallback
func HandleCommunityDetection(w http.ResponseWriter, r *http.Request) {
    var results interface{}

    if licensing.Global().HasFeature(licensing.FeatureCommunityDetection) {
        // Use advanced algorithm (Pro+)
        results = runLouvainCommunityDetection()
    } else {
        // Fall back to basic algorithm (Community)
        results = runSimpleClustering()
    }

    json.NewEncoder(w).Encode(results)
}

License Tiers

Community (Free)
  • Basic graph queries (nodes, edges, traversal)
  • Shortest path algorithm
  • Breadth-first search (BFS)
  • Depth-first search (DFS)
Pro ($249/month)

All Community features plus:

  • PageRank algorithm
  • Community detection algorithms
  • Trust and reputation scoring
  • Fraud ring detection
  • Time-based graph queries
  • Comprehensive audit logging
Enterprise ($999/month)

All Pro features plus:

  • Role-based access control (RBAC)
  • Single sign-on (SAML/OAuth)
  • Priority email support (24h SLA)
  • Multi-region replication

Available Features

// Community features
licensing.FeatureBasicQueries
licensing.FeatureShortestPath
licensing.FeatureBFS
licensing.FeatureDFS

// Pro features
licensing.FeaturePageRank
licensing.FeatureCommunityDetection
licensing.FeatureTrustScoring
licensing.FeatureFraudDetection
licensing.FeatureTemporalGraphs
licensing.FeatureAuditLogging

// Enterprise features
licensing.FeatureRBAC
licensing.FeatureSSO
licensing.FeaturePrioritySupport
licensing.FeatureMultiRegion

Caching Strategy

The license client implements a resilient caching strategy:

  1. Primary cache (24h TTL): Valid license responses cached for 24 hours
  2. Fallback cache (7d TTL): Extended cache used when license server is down
  3. Fail-open: If all caches expire and server unreachable, default to community tier
  4. Background revalidation: License checked every 24h in background goroutine
Cache Behavior
Startup → License Server Available?
   ├─ YES → Validate → Cache (24h) + Fallback (7d) → Success
   └─ NO  → Primary cache valid? (24h)
       ├─ YES → Use cached license
       └─ NO  → Fallback cache valid? (7d)
           ├─ YES → Use fallback license
           └─ NO  → Fail open to community tier

This ensures:

  • Zero downtime: License server outages don't break customer instances
  • Grace period: 7 days to fix license server before degrading to community
  • Automatic recovery: When license server returns, next validation updates cache

Environment Variables

  • GRAPHDB_LICENSE_KEY: License key (format: CGDB-XXXX-XXXX-XXXX-XXXX)
  • LICENSE_SERVER_URL: License server URL (default: https://license.graphdb.com)

API Reference

Manager Methods
// Get current license info
license := licensing.Global().GetLicense()

// Check license tier
tier := licensing.Global().GetTier()
isValid := licensing.Global().IsValid()
isPro := licensing.Global().IsPro()
isEnterprise := licensing.Global().IsEnterprise()

// Feature checks
hasFeature := licensing.Global().HasFeature(licensing.FeaturePageRank)
err := licensing.Global().CheckFeature(licensing.FeaturePageRank)

// Require feature (panics if not available - use for critical startup checks)
licensing.Global().RequireFeature(licensing.FeatureRBAC)
LicenseInfo Fields
type LicenseInfo struct {
    Valid       bool          // Is license currently valid?
    Tier        LicenseTier   // community, pro, or enterprise
    Status      LicenseStatus // active, suspended, cancelled, expired
    ExpiresAt   *time.Time    // Expiration time (nil = never expires)
    MaxNodes    *int          // Maximum nodes allowed (nil = unlimited)
    ValidatedAt time.Time     // When license was last validated
    CachedUntil time.Time     // When cache expires
}

// Helper methods
license.IsValid()      // Checks Valid flag, expiration, and status
license.IsPro()        // True for Pro or Enterprise
license.IsEnterprise() // True for Enterprise only
license.HasFeature(feature) // True if tier supports feature

Testing

See example_test.go for complete examples of:

  • Basic feature checks
  • Graceful error handling
  • Tier-based fallback behavior
  • License info endpoints
  • Feature-based route registration

License Server Protocol

Validate Request
POST /validate
{
  "licenseKey": "CGDB-XXXX-XXXX-XXXX-XXXX",
  "instanceId": "uuid-v4",
  "version": "1.0.0"
}
Validate Response
{
  "valid": true,
  "tier": "pro",
  "status": "active",
  "expiresAt": "2026-01-01T00:00:00Z",
  "maxNodes": null,
  "timestamp": "2025-11-19T02:00:00Z"
}
Error Response
{
  "valid": false,
  "error": "License is expired",
  "timestamp": "2025-11-19T02:00:00Z"
}

Architecture Notes

  • Singleton pattern: Global license manager initialized once at startup
  • Thread-safe: All methods protected by RWMutex for concurrent access
  • Graceful shutdown: Call licensing.Global().Stop() to stop background validation
  • No blocking: Initial validation runs in background, doesn't block startup
  • Observability: All validation events logged with structured logging

Best Practices

  1. Initialize early: Call InitGlobal() in main() before starting services
  2. Use CheckFeature: Prefer CheckFeature() over HasFeature() for better error messages
  3. Fail gracefully: Always provide fallback behavior for Pro/Enterprise features
  4. Log license info: Log tier and validation status on startup for debugging
  5. Clean shutdown: Call Stop() on shutdown to stop background goroutine
  6. Monitor cache hits: Watch logs for validation failures and cache usage

Troubleshooting

License server unreachable
[License] Validation failed: http request: dial tcp: connection refused
[License] No valid cache, failing open to community tier

Solution: Check LICENSE_SERVER_URL is correct, or verify license server is running

License validation fails
[License] Validation failed: invalid license: License is expired

Solution: Check license key is valid and not expired. Contact support to renew.

Background validation stopped
[License] Background validation stopped

Expected behavior: Logged on clean shutdown when Stop() is called.

Documentation

Index

Examples

Constants

View Source
const (
	// Cache TTL (24 hours)
	CacheTTL = 24 * time.Hour

	// Fallback cache TTL (7 days - used when license server is down)
	FallbackCacheTTL = 7 * 24 * time.Hour

	// Revalidation interval (check license server every 24 hours in background)
	RevalidationInterval = 24 * time.Hour

	// Default license server URL
	DefaultLicenseServerURL = "https://license.graphdb.com"

	// DefaultVersion is used when build info is not available
	DefaultVersion = "dev"
)

Variables

View Source
var (
	// Community features (always available)
	FeatureBasicQueries = Feature{
		Name:         "basic_queries",
		Description:  "Basic graph queries (nodes, edges, traversal)",
		RequiredTier: TierCommunity,
	}

	FeatureShortestPath = Feature{
		Name:         "shortest_path",
		Description:  "Shortest path algorithm",
		RequiredTier: TierCommunity,
	}

	FeatureBFS = Feature{
		Name:         "bfs",
		Description:  "Breadth-first search",
		RequiredTier: TierCommunity,
	}

	FeatureDFS = Feature{
		Name:         "dfs",
		Description:  "Depth-first search",
		RequiredTier: TierCommunity,
	}

	// Pro features
	FeaturePageRank = Feature{
		Name:         "pagerank",
		Description:  "PageRank algorithm",
		RequiredTier: TierPro,
	}

	FeatureCommunityDetection = Feature{
		Name:         "community_detection",
		Description:  "Community detection algorithms",
		RequiredTier: TierPro,
	}

	FeatureTrustScoring = Feature{
		Name:         "trust_scoring",
		Description:  "Trust and reputation scoring",
		RequiredTier: TierPro,
	}

	FeatureFraudDetection = Feature{
		Name:         "fraud_detection",
		Description:  "Fraud ring detection",
		RequiredTier: TierPro,
	}

	FeatureTemporalGraphs = Feature{
		Name:         "temporal_graphs",
		Description:  "Time-based graph queries",
		RequiredTier: TierPro,
	}

	FeatureAuditLogging = Feature{
		Name:         "audit_logging",
		Description:  "Comprehensive audit logging",
		RequiredTier: TierPro,
	}

	// Enterprise features
	FeatureRBAC = Feature{
		Name:         "rbac",
		Description:  "Role-based access control",
		RequiredTier: TierEnterprise,
	}

	FeatureSSO = Feature{
		Name:         "sso",
		Description:  "Single sign-on (SAML/OAuth)",
		RequiredTier: TierEnterprise,
	}

	FeaturePrioritySupport = Feature{
		Name:         "priority_support",
		Description:  "Priority email support with 24h SLA",
		RequiredTier: TierEnterprise,
	}

	FeatureMultiRegion = Feature{
		Name:         "multi_region",
		Description:  "Multi-region replication",
		RequiredTier: TierEnterprise,
	}
)

GraphDB feature definitions

View Source
var TelemetryEndpoint = "https://telemetry.graphdb.io/v1/report"

TelemetryEndpoint is the URL where telemetry data is sent Override this in production to point to your telemetry server

Functions

func GenerateLicenseID

func GenerateLicenseID() (string, error)

GenerateLicenseID creates a unique license ID. Returns an error if random generation fails.

func GenerateLicenseKey

func GenerateLicenseKey(licenseType LicenseType, email string) (string, error)

GenerateLicenseKey creates a unique license key with checksum Format: CGDB-XXXX-XXXX-XXXX-XXXX-CC (where CC is checksum)

func InitGlobal

func InitGlobal(licenseKey, serverURL string)

InitGlobal initializes the global license manager

func ReportOnce

func ReportOnce(license *License, version string) error

ReportOnce sends a single telemetry report without starting the periodic reporter Useful for one-time events like startup or shutdown

func SendLicenseEmail

func SendLicenseEmail(config *EmailConfig, license *License) error

SendLicenseEmail sends a license key to a customer

func ValidateLicenseKey

func ValidateLicenseKey(key string) bool

ValidateLicenseKey checks if a license key has valid format and checksum Supports both old format (CGDB-XXXX-XXXX-XXXX-XXXX) and new format with checksum

func VerifyFingerprint

func VerifyFingerprint(expectedHash string) (bool, error)

VerifyFingerprint checks if the current hardware matches the provided fingerprint

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client validates licenses against the license server

func NewClient

func NewClient(serverURL string) *Client

NewClient creates a new license client

func (*Client) GetCurrent

func (c *Client) GetCurrent() *LicenseInfo

GetCurrent returns the current cached license (if any)

func (*Client) StartBackgroundValidation

func (c *Client) StartBackgroundValidation(licenseKey string)

StartBackgroundValidation starts background license re-validation

func (*Client) Stop

func (c *Client) Stop()

Stop stops background validation

func (*Client) Validate

func (c *Client) Validate(licenseKey string) (*LicenseInfo, error)

Validate validates a license key and caches the result

type EmailConfig

type EmailConfig struct {
	SMTPHost     string
	SMTPPort     string
	SMTPUsername string
	SMTPPassword string
	FromEmail    string
	FromName     string
}

EmailConfig holds email configuration

func LoadEmailConfigFromEnv

func LoadEmailConfigFromEnv() *EmailConfig

LoadEmailConfigFromEnv loads email configuration from environment variables

func (*EmailConfig) IsConfigured

func (c *EmailConfig) IsConfigured() bool

IsConfigured checks if email is properly configured

type Feature

type Feature struct {
	Name         string
	Description  string
	RequiredTier LicenseTier
}

Feature represents a GraphDB feature with licensing requirements

func AllFeatures

func AllFeatures() []Feature

AllFeatures returns a list of all GraphDB features

func FeaturesByTier

func FeaturesByTier(tier LicenseTier) []Feature

FeaturesByTier returns features available for a given tier

Example

Example: Feature-based route registration

package main

import (
	"fmt"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	licensing.InitGlobal("", "")

	// Get available features for current tier
	currentTier := licensing.Global().GetTier()
	features := licensing.FeaturesByTier(currentTier)

	fmt.Printf("Available features for %s tier:\n", currentTier)
	for _, feature := range features {
		fmt.Printf("  - %s: %s\n", feature.Name, feature.Description)
	}

	// Example output for Community tier:
	// Available features for community tier:
	//   - basic_queries: Basic graph queries
	//   - shortest_path: Shortest path algorithm
	//   - bfs: Breadth-first search
	//   - dfs: Depth-first search
}

type FeatureNotAvailableError

type FeatureNotAvailableError struct {
	Feature      Feature
	CurrentTier  LicenseTier
	RequiredTier LicenseTier
}

FeatureNotAvailableError is returned when a feature is not available in the current tier

func (*FeatureNotAvailableError) Error

func (e *FeatureNotAvailableError) Error() string

type HardwareFingerprint

type HardwareFingerprint struct {
	Hash       string            `json:"hash"`
	Components map[string]string `json:"components"`
	Generated  string            `json:"generated_at"`
}

HardwareFingerprint represents a unique hardware identifier

func GenerateFingerprint

func GenerateFingerprint() (*HardwareFingerprint, error)

GenerateFingerprint creates a unique hardware fingerprint based on: - CPU architecture and cores - Primary MAC address - Hostname This allows licenses to be tied to specific deployments

type License

type License struct {
	ID             string            `json:"id"`
	Key            string            `json:"key"`
	Type           LicenseType       `json:"type"`
	Email          string            `json:"email"`
	CustomerID     string            `json:"customer_id"`     // Stripe customer ID
	SubscriptionID string            `json:"subscription_id"` // Stripe subscription ID
	Status         string            `json:"status"`          // active, cancelled, expired
	CreatedAt      time.Time         `json:"created_at"`
	ExpiresAt      *time.Time        `json:"expires_at,omitempty"`
	Metadata       map[string]string `json:"metadata,omitempty"`
}

License represents a GraphDB license

func (*License) BindToFingerprint

func (l *License) BindToFingerprint(fingerprint *HardwareFingerprint)

BindLicenseToFingerprint stores the hardware fingerprint in license metadata

func (*License) GetFingerprint

func (l *License) GetFingerprint() (string, bool)

GetFingerprint returns the hardware fingerprint bound to this license, if any

func (*License) IsActive

func (l *License) IsActive() bool

IsActive checks if a license is currently active

func (*License) IsExpired

func (l *License) IsExpired() bool

IsExpired checks if a license has expired

func (*License) Validate

func (l *License) Validate() error

Validate performs comprehensive license validation Returns nil if license is valid, error otherwise

func (*License) ValidateWithOptions

func (l *License) ValidateWithOptions(opts ValidationOptions) error

ValidateWithOptions performs license validation with custom options

func (*License) VerifyHardwareBinding

func (l *License) VerifyHardwareBinding() (bool, error)

VerifyHardwareBinding checks if the license is bound to the current hardware Returns true if: - License has no hardware binding (not enforced) - Current hardware matches the bound fingerprint

type LicenseInfo

type LicenseInfo struct {
	Valid       bool
	Tier        LicenseTier
	Status      LicenseStatus
	ExpiresAt   *time.Time
	MaxNodes    *int
	ValidatedAt time.Time
	CachedUntil time.Time
}

LicenseInfo represents a validated license with caching info

func (*LicenseInfo) HasFeature

func (l *LicenseInfo) HasFeature(feature Feature) bool

HasFeature checks if a license tier supports a feature

func (*LicenseInfo) IsEnterprise

func (l *LicenseInfo) IsEnterprise() bool

IsEnterprise returns whether the license is Enterprise

func (*LicenseInfo) IsPro

func (l *LicenseInfo) IsPro() bool

IsPro returns whether the license is Pro or higher

func (*LicenseInfo) IsValid

func (l *LicenseInfo) IsValid() bool

IsValid returns whether the license is valid

type LicenseStatus

type LicenseStatus string

LicenseStatus represents the status of a license

const (
	StatusActive    LicenseStatus = "active"
	StatusSuspended LicenseStatus = "suspended"
	StatusCancelled LicenseStatus = "cancelled"
	StatusExpired   LicenseStatus = "expired"
)

type LicenseStore

type LicenseStore interface {
	CreateLicense(ctx context.Context, license *License) error
	GetLicense(ctx context.Context, id string) (*License, error)
	GetLicenseByKey(ctx context.Context, key string) (*License, error)
	GetLicenseByCustomer(ctx context.Context, customerID string) (*License, error)
	UpdateLicense(ctx context.Context, license *License) error
	ListLicenses(ctx context.Context) ([]*License, error)
	Ping(ctx context.Context) error
	Close() error
}

LicenseStore defines the interface for license persistence

type LicenseTier

type LicenseTier string

LicenseTier represents the GraphDB license tier

const (
	TierCommunity  LicenseTier = "community"
	TierPro        LicenseTier = "pro"
	TierEnterprise LicenseTier = "enterprise"
)

type LicenseType

type LicenseType string

LicenseType represents the license tier

const (
	LicenseTypeProfessional LicenseType = "professional"
	LicenseTypeEnterprise   LicenseType = "enterprise"
)

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages license validation and feature access

func Global

func Global() *Manager

Global returns the global license manager

func (*Manager) CheckFeature

func (m *Manager) CheckFeature(feature Feature) error

CheckFeature returns an error if the current license doesn't support a feature Use this for graceful feature gating in API handlers

Example

Example: Graceful feature gating with error messages

package main

import (
	"encoding/json"
	"net/http"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	licensing.InitGlobal("", "") // Community tier

	handler := func(w http.ResponseWriter, r *http.Request) {
		// CheckFeature returns a helpful error with upgrade link
		if err := licensing.Global().CheckFeature(licensing.FeatureFraudDetection); err != nil {
			// Error message includes current tier, required tier, and upgrade URL
			http.Error(w, err.Error(), http.StatusForbidden)
			return
		}

		// Feature is available
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]any{
			"fraudRings": []string{"ring-1", "ring-2"},
		})
	}

	_ = handler
}

func (*Manager) GetLicense

func (m *Manager) GetLicense() *LicenseInfo

GetLicense returns the current license

Example

Example: License info endpoint

package main

import (
	"encoding/json"
	"net/http"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	licensing.InitGlobal("", "")

	handler := func(w http.ResponseWriter, r *http.Request) {
		license := licensing.Global().GetLicense()

		response := map[string]any{
			"valid":  license.IsValid(),
			"tier":   license.Tier,
			"status": license.Status,
		}

		if license.ExpiresAt != nil {
			response["expiresAt"] = license.ExpiresAt
		}
		if license.MaxNodes != nil {
			response["maxNodes"] = license.MaxNodes
		}

		json.NewEncoder(w).Encode(response)
	}

	_ = handler
}

func (*Manager) GetTier

func (m *Manager) GetTier() LicenseTier

GetTier returns the current license tier

Example

Example: Check license tier for conditional features

package main

import (
	"encoding/json"
	"net/http"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	licensing.InitGlobal("", "")

	handler := func(w http.ResponseWriter, r *http.Request) {
		tier := licensing.Global().GetTier()

		// Return tier-appropriate response
		response := map[string]any{
			"tier": tier,
		}

		switch tier {
		case licensing.TierEnterprise:
			response["features"] = []string{"all", "rbac", "sso", "multi-region"}
		case licensing.TierPro:
			response["features"] = []string{"pagerank", "fraud-detection", "audit-logging"}
		case licensing.TierCommunity:
			response["features"] = []string{"basic-queries", "shortest-path"}
		}

		json.NewEncoder(w).Encode(response)
	}

	_ = handler
}

func (*Manager) HasFeature

func (m *Manager) HasFeature(feature Feature) bool

HasFeature checks if the current license supports a feature

Example

Example: Basic feature check in an API handler

package main

import (
	"encoding/json"
	"net/http"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	// Initialize the global license manager (typically done in main.go)
	licensing.InitGlobal("", "") // Community tier (no license key)

	// In your API handler, check if a feature is available
	handler := func(w http.ResponseWriter, r *http.Request) {
		// Check if PageRank is available (Pro+ feature)
		if !licensing.Global().HasFeature(licensing.FeaturePageRank) {
			http.Error(w, "PageRank requires Pro or Enterprise tier", http.StatusForbidden)
			return
		}

		// Feature is available, proceed with request
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]any{
			"status": "success",
			"data":   []float64{0.15, 0.23, 0.42}, // PageRank results
		})
	}

	_ = handler // Use the handler in your HTTP router
}
Example (Fallback)

Example: Multiple feature checks with fallback behavior

package main

import (
	"encoding/json"
	"net/http"

	"github.com/dd0wney/graphdb/pkg/licensing"
)

func main() {
	licensing.InitGlobal("", "")

	handler := func(w http.ResponseWriter, r *http.Request) {
		var results any

		// Try enterprise algorithm first
		if licensing.Global().HasFeature(licensing.FeatureCommunityDetection) {
			results = runAdvancedCommunityDetection()
		} else {
			// Fall back to basic algorithm
			results = runBasicClustering()
		}

		json.NewEncoder(w).Encode(map[string]any{
			"results": results,
			"tier":    licensing.Global().GetTier(),
		})
	}

	_ = handler
}

// Mock functions for examples
func runAdvancedCommunityDetection() any {
	return map[string]any{"algorithm": "louvain", "communities": 5}
}

func runBasicClustering() any {
	return map[string]any{"algorithm": "simple", "clusters": 3}
}

func (*Manager) IsEnterprise

func (m *Manager) IsEnterprise() bool

IsEnterprise returns whether the license is Enterprise

func (*Manager) IsPro

func (m *Manager) IsPro() bool

IsPro returns whether the license is Pro or higher

func (*Manager) IsValid

func (m *Manager) IsValid() bool

IsValid returns whether the current license is valid

func (*Manager) RequireFeature

func (m *Manager) RequireFeature(feature Feature)

RequireFeature panics if the current license doesn't support a feature Use this for critical features that should never be accessed without proper licensing

func (*Manager) Stop

func (m *Manager) Stop()

Stop stops the license manager

type PGStore

type PGStore struct {
	// contains filtered or unexported fields
}

PGStore handles license persistence using PostgreSQL

func NewPGStore

func NewPGStore(ctx context.Context, databaseURL string) (*PGStore, error)

NewPGStore creates a new PostgreSQL-backed license store

func (*PGStore) Close

func (s *PGStore) Close() error

Close closes the database connection pool

func (*PGStore) CreateLicense

func (s *PGStore) CreateLicense(ctx context.Context, license *License) error

CreateLicense stores a new license

func (*PGStore) GetLicense

func (s *PGStore) GetLicense(ctx context.Context, id string) (*License, error)

GetLicense retrieves a license by ID

func (*PGStore) GetLicenseByCustomer

func (s *PGStore) GetLicenseByCustomer(ctx context.Context, customerID string) (*License, error)

GetLicenseByCustomer retrieves a license by Stripe customer ID

func (*PGStore) GetLicenseByKey

func (s *PGStore) GetLicenseByKey(ctx context.Context, key string) (*License, error)

GetLicenseByKey retrieves a license by its key

func (*PGStore) ListLicenses

func (s *PGStore) ListLicenses(ctx context.Context) ([]*License, error)

ListLicenses returns all licenses

func (*PGStore) Ping

func (s *PGStore) Ping(ctx context.Context) error

Ping checks database connectivity

func (*PGStore) UpdateLicense

func (s *PGStore) UpdateLicense(ctx context.Context, license *License) error

UpdateLicense updates an existing license

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store handles license persistence using a local file

func NewStore

func NewStore(dataDir string) (*Store, error)

NewStore creates a new license store

func (*Store) Close

func (s *Store) Close() error

Close is a no-op for file-based store

func (*Store) CreateLicense

func (s *Store) CreateLicense(_ context.Context, license *License) error

CreateLicense stores a new license

func (*Store) GetLicense

func (s *Store) GetLicense(_ context.Context, id string) (*License, error)

GetLicense retrieves a license by ID

func (*Store) GetLicenseByCustomer

func (s *Store) GetLicenseByCustomer(_ context.Context, customerID string) (*License, error)

GetLicenseByCustomer retrieves a license by Stripe customer ID

func (*Store) GetLicenseByKey

func (s *Store) GetLicenseByKey(_ context.Context, key string) (*License, error)

GetLicenseByKey retrieves a license by its key

func (*Store) ListLicenses

func (s *Store) ListLicenses(_ context.Context) ([]*License, error)

ListLicenses returns all licenses

func (*Store) Ping

func (s *Store) Ping(_ context.Context) error

Ping checks if store is accessible (always succeeds for file-based store)

func (*Store) UpdateLicense

func (s *Store) UpdateLicense(_ context.Context, license *License) error

UpdateLicense updates an existing license

type TelemetryData

type TelemetryData struct {
	// Anonymous license hash (SHA-256 of license key)
	LicenseHash string `json:"license_hash"`

	// Version information
	Version   string `json:"version"`
	GoVersion string `json:"go_version"`
	OS        string `json:"os"`
	Arch      string `json:"arch"`

	// Usage metrics (anonymous)
	NodeCount int64 `json:"node_count,omitempty"`
	EdgeCount int64 `json:"edge_count,omitempty"`
	Uptime    int64 `json:"uptime_seconds,omitempty"`

	// Deployment type
	DeploymentType string `json:"deployment_type,omitempty"` // docker, binary, kubernetes

	// Timestamp
	Timestamp time.Time `json:"timestamp"`

	// Installation ID (anonymous hash of hardware fingerprint)
	InstallationID string `json:"installation_id,omitempty"`
}

TelemetryData represents anonymous usage metrics All data is anonymized - no personal or identifying information is sent

type TelemetryReporter

type TelemetryReporter struct {
	// contains filtered or unexported fields
}

TelemetryReporter handles periodic telemetry reporting

func NewTelemetryReporter

func NewTelemetryReporter(enabled bool, interval time.Duration) *TelemetryReporter

NewTelemetryReporter creates a new telemetry reporter

func (*TelemetryReporter) Start

func (t *TelemetryReporter) Start(license *License, getMetrics func() (nodeCount, edgeCount int64))

Start begins periodic telemetry reporting

func (*TelemetryReporter) Stop

func (t *TelemetryReporter) Stop()

Stop halts telemetry reporting

type ValidateRequest

type ValidateRequest struct {
	LicenseKey string `json:"licenseKey"`
	InstanceID string `json:"instanceId,omitempty"`
	Version    string `json:"version,omitempty"`
}

ValidateRequest is sent to the license server

type ValidateResponse

type ValidateResponse struct {
	Valid     bool          `json:"valid"`
	Tier      LicenseTier   `json:"tier,omitempty"`
	Status    LicenseStatus `json:"status,omitempty"`
	Error     string        `json:"error,omitempty"`
	ExpiresAt *time.Time    `json:"expiresAt,omitempty"`
	MaxNodes  *int          `json:"maxNodes,omitempty"`
	Timestamp time.Time     `json:"timestamp"`
}

ValidateResponse is received from the license server

type ValidationError

type ValidationError struct {
	Code    string
	Message string
}

ValidationError represents a license validation failure

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ValidationOptions

type ValidationOptions struct {
	CheckExpiration      bool
	CheckStatus          bool
	CheckHardwareBinding bool
}

ValidationOptions controls which validations to perform

func DefaultValidationOptions

func DefaultValidationOptions() ValidationOptions

DefaultValidationOptions returns the standard validation options

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL