209 lines
7.0 KiB
Go
209 lines
7.0 KiB
Go
package fedwiki
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.temporal.io/sdk/temporal"
|
|
"go.temporal.io/sdk/workflow"
|
|
)
|
|
|
|
// FedWikiActivityOptions returns activity options for FedWiki API calls.
|
|
// Uses exponential backoff: 1s -> 2s -> 4s -> 8s -> 16s -> 32s -> 64s -> 128s -> 256s
|
|
// Total retry time approximately 8 minutes before giving up.
|
|
func FedWikiActivityOptions() workflow.ActivityOptions {
|
|
return workflow.ActivityOptions{
|
|
StartToCloseTimeout: 5 * time.Minute, // Allow time for retries within activity
|
|
RetryPolicy: &temporal.RetryPolicy{
|
|
InitialInterval: time.Second,
|
|
BackoffCoefficient: 2.0,
|
|
MaximumInterval: 5 * time.Minute,
|
|
MaximumAttempts: 8, // 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = ~255 seconds (~4 min), but with 8 attempts we get up to 256s
|
|
NonRetryableErrorTypes: []string{
|
|
"QuotaExceeded",
|
|
"InvalidDomain",
|
|
"Unauthorized",
|
|
"DomainAlreadyExists",
|
|
"FarmManagerAPIError", // Non-retryable API errors (409, 403, 401)
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// LocalActivityOptions returns activity options for local database operations.
|
|
func LocalActivityOptions() workflow.ActivityOptions {
|
|
return workflow.ActivityOptions{
|
|
StartToCloseTimeout: 30 * time.Second,
|
|
RetryPolicy: &temporal.RetryPolicy{
|
|
InitialInterval: time.Second,
|
|
BackoffCoefficient: 2.0,
|
|
MaximumInterval: 30 * time.Second,
|
|
MaximumAttempts: 3,
|
|
},
|
|
}
|
|
}
|
|
|
|
// CreateFedWikiSiteWorkflowInput is the input for the CreateFedWikiSiteWorkflow.
|
|
type CreateFedWikiSiteWorkflowInput struct {
|
|
UserID int64
|
|
Domain string // The subdomain part (e.g., "mysite")
|
|
SiteDomain string // The parent domain (e.g., "localtest.me") - ignored for custom domains
|
|
OwnerName string // Display name for the owner
|
|
OwnerID string // OIDC subject (OAuth2 ID) for owner verification
|
|
IsCustomDomain bool
|
|
SupportURL string
|
|
}
|
|
|
|
// CreateFedWikiSiteWorkflowOutput is the output for the CreateFedWikiSiteWorkflow.
|
|
type CreateFedWikiSiteWorkflowOutput struct {
|
|
Success bool
|
|
SiteID int64
|
|
Domain string
|
|
ErrorMessage string
|
|
}
|
|
|
|
// CreateFedWikiSiteWorkflow orchestrates the creation of a new FedWiki site.
|
|
func CreateFedWikiSiteWorkflow(ctx workflow.Context, input CreateFedWikiSiteWorkflowInput) (*CreateFedWikiSiteWorkflowOutput, error) {
|
|
logger := workflow.GetLogger(ctx)
|
|
logger.Info("CreateFedWikiSiteWorkflow started",
|
|
"userID", input.UserID,
|
|
"domain", input.Domain)
|
|
|
|
var activities *Activities
|
|
|
|
// Step 1: Check quota using local activity options (fast, local DB call)
|
|
localCtx := workflow.WithActivityOptions(ctx, LocalActivityOptions())
|
|
|
|
var quotaResult *CheckQuotaOutput
|
|
err := workflow.ExecuteActivity(localCtx, activities.CheckQuotaActivity, CheckQuotaInput{
|
|
UserID: input.UserID,
|
|
}).Get(ctx, "aResult)
|
|
if err != nil {
|
|
logger.Error("Failed to check quota", "error", err)
|
|
return &CreateFedWikiSiteWorkflowOutput{
|
|
Success: false,
|
|
ErrorMessage: "Failed to verify your site quota. Please try again later.",
|
|
}, nil
|
|
}
|
|
|
|
if !quotaResult.CanCreate {
|
|
logger.Info("User quota exceeded",
|
|
"userID", input.UserID,
|
|
"currentCount", quotaResult.CurrentCount,
|
|
"quota", quotaResult.Quota)
|
|
return &CreateFedWikiSiteWorkflowOutput{
|
|
Success: false,
|
|
ErrorMessage: fmt.Sprintf("You have reached your site limit (%d of %d sites). Join us and become a member for more sites.", quotaResult.CurrentCount, quotaResult.Quota),
|
|
}, nil
|
|
}
|
|
|
|
// Step 2: Create the site using FedWiki activity options (external API with retries)
|
|
fedwikiCtx := workflow.WithActivityOptions(ctx, FedWikiActivityOptions())
|
|
|
|
var createResult *CreateFedWikiSiteOutput
|
|
err = workflow.ExecuteActivity(fedwikiCtx, activities.CreateFedWikiSiteActivity, CreateFedWikiSiteInput{
|
|
UserID: input.UserID,
|
|
Domain: input.Domain,
|
|
SiteDomain: input.SiteDomain,
|
|
OwnerName: input.OwnerName,
|
|
OwnerID: input.OwnerID,
|
|
IsCustomDomain: input.IsCustomDomain,
|
|
}).Get(ctx, &createResult)
|
|
if err != nil {
|
|
logger.Error("Failed to create site after retries", "error", err)
|
|
|
|
// Try to extract user-friendly message from the error
|
|
// Activity errors wrap ApplicationErrors, so we need to use errors.As
|
|
var appErr *temporal.ApplicationError
|
|
var errorMsg string
|
|
if errors.As(err, &appErr) {
|
|
errorMsg = appErr.Message()
|
|
} else {
|
|
// Generic error message for unexpected failures
|
|
errorMsg = "We encountered an issue creating your site. Our system tried multiple times but was unable to complete the request. "
|
|
errorMsg += "Please try again later. "
|
|
if input.SupportURL != "" {
|
|
errorMsg += fmt.Sprintf("If the problem persists, please contact support: %s", input.SupportURL)
|
|
}
|
|
}
|
|
|
|
return &CreateFedWikiSiteWorkflowOutput{
|
|
Success: false,
|
|
ErrorMessage: errorMsg,
|
|
}, nil
|
|
}
|
|
|
|
logger.Info("CreateFedWikiSiteWorkflow completed successfully",
|
|
"siteID", createResult.SiteID,
|
|
"domain", createResult.Domain)
|
|
|
|
return &CreateFedWikiSiteWorkflowOutput{
|
|
Success: true,
|
|
SiteID: createResult.SiteID,
|
|
Domain: createResult.Domain,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteFedWikiSiteWorkflowInput is the input for the DeleteFedWikiSiteWorkflow.
|
|
type DeleteFedWikiSiteWorkflowInput struct {
|
|
UserID int64
|
|
Domain string // Full domain as stored in database
|
|
SupportURL string
|
|
}
|
|
|
|
// DeleteFedWikiSiteWorkflowOutput is the output for the DeleteFedWikiSiteWorkflow.
|
|
type DeleteFedWikiSiteWorkflowOutput struct {
|
|
Success bool
|
|
ErrorMessage string
|
|
}
|
|
|
|
// DeleteFedWikiSiteWorkflow orchestrates the deletion of a FedWiki site.
|
|
func DeleteFedWikiSiteWorkflow(ctx workflow.Context, input DeleteFedWikiSiteWorkflowInput) (*DeleteFedWikiSiteWorkflowOutput, error) {
|
|
logger := workflow.GetLogger(ctx)
|
|
logger.Info("DeleteFedWikiSiteWorkflow started",
|
|
"userID", input.UserID,
|
|
"domain", input.Domain)
|
|
|
|
var activities *Activities
|
|
|
|
// Delete the site using FedWiki activity options (external API with retries)
|
|
fedwikiCtx := workflow.WithActivityOptions(ctx, FedWikiActivityOptions())
|
|
|
|
var deleteResult *DeleteFedWikiSiteOutput
|
|
err := workflow.ExecuteActivity(fedwikiCtx, activities.DeleteFedWikiSiteActivity, DeleteFedWikiSiteInput{
|
|
UserID: input.UserID,
|
|
Domain: input.Domain,
|
|
}).Get(ctx, &deleteResult)
|
|
if err != nil {
|
|
logger.Error("Failed to delete site after retries", "error", err)
|
|
|
|
// Try to extract user-friendly message from the error
|
|
// Activity errors wrap ApplicationErrors, so we need to use errors.As
|
|
var appErr *temporal.ApplicationError
|
|
var errorMsg string
|
|
if errors.As(err, &appErr) {
|
|
errorMsg = appErr.Message()
|
|
} else {
|
|
// Generic error message for unexpected failures
|
|
errorMsg = "We encountered an issue deleting your site. Our system tried multiple times but was unable to complete the request. "
|
|
errorMsg += "Please try again later. "
|
|
if input.SupportURL != "" {
|
|
errorMsg += fmt.Sprintf("If the problem persists, please contact support: %s", input.SupportURL)
|
|
}
|
|
}
|
|
|
|
return &DeleteFedWikiSiteWorkflowOutput{
|
|
Success: false,
|
|
ErrorMessage: errorMsg,
|
|
}, nil
|
|
}
|
|
|
|
logger.Info("DeleteFedWikiSiteWorkflow completed successfully",
|
|
"domain", input.Domain)
|
|
|
|
return &DeleteFedWikiSiteWorkflowOutput{
|
|
Success: true,
|
|
}, nil
|
|
}
|