Files

FedWiki Workflows

This package implements Temporal workflows for FedWiki site provisioning and synchronization operations.

Overview

The FedWiki workflows handle asynchronous site management operations:

  • CreateFedWikiSiteWorkflow: Creates a new FedWiki site on the farm and records it in the database
  • DeleteFedWikiSiteWorkflow: Deletes a site from the farm and removes it from the database
  • SyncFedWikiSitesWorkflow: Synchronizes sites from the FedWiki farm to the local database (scheduled)

Architecture

The workflow execution flow:

  1. HTTP Handler receives user request and starts a Temporal workflow
  2. Temporal Client submits the workflow to the Temporal server
  3. Workflow orchestrates one or more activities
  4. Activities perform the actual work (API calls, database operations)
  5. HTTP Handler waits for workflow completion and returns result to user

Available activities:

  • CheckQuotaActivity - Local database query to verify user quota
  • CreateFedWikiSiteActivity - Calls FarmManager API and records site in database
  • DeleteFedWikiSiteActivity - Calls FarmManager API and removes site from database
  • ListFedWikiSitesActivity - Lists all sites from the FarmManager API
  • SyncSitesToDBActivity - Synchronizes farm sites to the local database

Files

  • workflow.go - Workflow definitions for create/delete operations
  • sync.go - Sync workflow and activities
  • schedule.go - Temporal schedule management for periodic sync
  • activities.go - Activity implementations
  • farmmanager_client.go - HTTP client for FarmManager API

Workflows

CreateFedWikiSiteWorkflow

Input:

type CreateFedWikiSiteWorkflowInput struct {
    UserID         int64   // Database user ID
    Domain         string  // Subdomain or custom domain
    OwnerName      string  // Display name for the owner
    OwnerID        string  // OIDC subject (OAuth2 ID) for owner verification
    IsCustomDomain bool    // Whether this is a custom domain
    SupportURL     string  // Support URL for error messages
}

Output:

type CreateFedWikiSiteWorkflowOutput struct {
    Success      bool   // Whether creation succeeded
    SiteID       int64  // Database site ID (on success)
    Domain       string // Full domain (on success)
    ErrorMessage string // User-friendly error (on failure)
}

Steps:

  1. CheckQuotaActivity - Verify user hasn't exceeded site limit
  2. CreateFedWikiSiteActivity - Call FarmManager API and record in database

Site Ownership

When a site is created, the FarmManager API creates an owner.json file in the site's status/ directory. This file must contain the OAuth2 identity information that matches what FedWiki's security plugin (wiki-security-passportjs) extracts from the user's login token.

owner.json format:

{
  "name": "username",
  "oauth2": {
    "id": "oidc-subject-uuid",
    "username": "username"
  }
}

Important: The oauth2.id field must match the value extracted by FedWiki's oauth2_IdField configuration. By default, member-console uses the OIDC sub claim (a UUID), so FedWiki must be configured with:

{
  "wikiDomains": {
    "example.com": {
      "oauth2_IdField": "token.sub",
      ...
    }
  }
}

If oauth2_IdField is set to something else (e.g., token.preferred_username), site owners will not be recognized even if the username matches.

DeleteFedWikiSiteWorkflow

Input:

type DeleteFedWikiSiteWorkflowInput struct {
    UserID     int64  // Database user ID
    Domain     string // Full domain to delete
    SupportURL string // Support URL for error messages
}

Output:

type DeleteFedWikiSiteWorkflowOutput struct {
    Success      bool   // Whether deletion succeeded
    ErrorMessage string // User-friendly error (on failure)
}

Steps:

  1. DeleteFedWikiSiteActivity - Call FarmManager API and remove from database

SyncFedWikiSitesWorkflow

This workflow synchronizes sites from the FedWiki farm to the local database. It runs on a configurable schedule (default: hourly) to ensure the local database reflects the current state of the farm.

Use Cases:

  • Importing existing sites that were created outside the member console
  • Removing sites from the database that were deleted directly on the farm
  • Keeping the local database in sync with external changes

Input:

type SyncFedWikiSitesWorkflowInput struct {
    DefaultUserID int64 // User ID to assign to orphaned sites (typically admin)
}

Output:

type SyncFedWikiSitesWorkflowOutput struct {
    Success       bool      // Whether sync succeeded
    SitesFound    int       // Number of sites found on farm
    SitesAdded    int       // Number of sites added to database
    SitesRemoved  int       // Number of sites removed from database
    ErrorMessage  string    // Error message (on failure)
    SyncTimestamp time.Time // When the sync completed
}

Steps:

  1. ListFedWikiSitesActivity - Fetch all sites from the FarmManager API
  2. SyncSitesToDBActivity - Compare with local database and sync changes

Sync Logic:

  • Sites on farm but not in local DB → Add to local DB with DefaultUserID
  • Sites in local DB but not on farm (or inactive) → Remove from local DB
  • Sites in both → Update updated_at timestamp

Scheduling

The sync workflow can be scheduled to run periodically using Temporal Schedules.

Configuration

Enable the sync schedule in your configuration:

fedwiki-sync-enabled: true
fedwiki-sync-interval: 1h           # Sync interval (default: 1 hour)
fedwiki-sync-default-user-id: 1     # User ID for orphaned sites
fedwiki-sync-trigger-immediately: true  # Run sync on startup

Or via command line flags:

member-console start \
  --fedwiki-sync-enabled \
  --fedwiki-sync-interval=30m \
  --fedwiki-sync-default-user-id=1

ScheduleManager

The ScheduleManager provides programmatic control over the sync schedule:

manager := fedwiki.NewScheduleManager(temporalClient, logger)

// Create or update the schedule
err := manager.EnsureSyncSchedule(ctx, fedwiki.ScheduleConfig{
    Interval:           time.Hour,
    DefaultUserID:      1,
    TriggerImmediately: true,
})

// Pause the schedule
err := manager.PauseSyncSchedule(ctx, "Maintenance window")

// Resume the schedule
err := manager.UnpauseSyncSchedule(ctx, "Maintenance complete")

// Trigger an immediate sync
err := manager.TriggerSyncNow(ctx)

// Get schedule info
info, err := manager.GetSyncScheduleInfo(ctx)

// Delete the schedule
err := manager.DeleteSyncSchedule(ctx)

Activity Options

FedWiki API Activities

Used for external API calls with aggressive retry:

ActivityOptions{
    StartToCloseTimeout: 5 * time.Minute,
    RetryPolicy: &temporal.RetryPolicy{
        InitialInterval:    time.Second,
        BackoffCoefficient: 2.0,
        MaximumInterval:    5 * time.Minute,
        MaximumAttempts:    8,
        NonRetryableErrorTypes: []string{
            "FarmManagerAPIError", // 409, 403, 401 errors
        },
    },
}

Local Database Activities

Used for fast local operations:

ActivityOptions{
    StartToCloseTimeout: 30 * time.Second,
    RetryPolicy: &temporal.RetryPolicy{
        InitialInterval:    time.Second,
        BackoffCoefficient: 2.0,
        MaximumInterval:    30 * time.Second,
        MaximumAttempts:    3,
    },
}

Error Handling

Non-Retryable Errors

Certain errors should not be retried because they represent permanent failures:

  • 409 Conflict (Site already exists) - "A site with this name already exists. Please choose a different name."
  • 403 Forbidden (Permission denied) - "You don't have permission to perform this action."
  • 401 Unauthorized (Bad credentials) - "Authentication failed. Please contact support."

These are wrapped using temporal.NewNonRetryableApplicationError() with the type FarmManagerAPIError.

Retryable Errors

Network errors, 5xx server errors, and other transient failures are retried with exponential backoff.

Delete Idempotency

Delete operations treat HTTP 404 (Not Found) as success - if the site doesn't exist, the desired state has been achieved.

Synchronous Execution Pattern

Unlike typical async workflows, these workflows are executed synchronously from HTTP handlers:

// Start workflow
we, err := temporalClient.ExecuteWorkflow(ctx, options, workflow, input)

// Wait for completion (blocks until workflow finishes)
var result WorkflowOutput
err = we.Get(ctx, &result)

// Handle result
if !result.Success {
    // Show error to user
}

This provides immediate feedback to users while still benefiting from Temporal's:

  • Automatic retries with backoff
  • Workflow history and debugging
  • Graceful failure handling