270 lines
8.6 KiB
Go
270 lines
8.6 KiB
Go
package fedwiki
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/db"
|
|
"go.temporal.io/sdk/temporal"
|
|
)
|
|
|
|
// Activities holds dependencies for FedWiki domain activities.
|
|
type Activities struct {
|
|
DB db.Querier
|
|
Logger *slog.Logger
|
|
Client *FarmManagerClient
|
|
FedWikiAllowedDomains []string // Domains where users can create sites
|
|
SupportURL string
|
|
}
|
|
|
|
// ActivitiesConfig holds configuration for creating FedWiki activities.
|
|
type ActivitiesConfig struct {
|
|
DB db.Querier
|
|
Logger *slog.Logger
|
|
FedWikiFarmAPIURL string // URL for FarmManager API calls
|
|
FedWikiAllowedDomains []string // Domains where users can create sites
|
|
FedWikiAdminToken string
|
|
SupportURL string
|
|
}
|
|
|
|
// NewActivities creates a new Activities instance with the FarmManager client.
|
|
func NewActivities(cfg ActivitiesConfig) *Activities {
|
|
client := NewFarmManagerClient(cfg.FedWikiFarmAPIURL, cfg.FedWikiAdminToken)
|
|
if cfg.Logger != nil {
|
|
cfg.Logger.Info("FedWiki activities initialized",
|
|
slog.String("farmAPIURL", cfg.FedWikiFarmAPIURL),
|
|
slog.Any("allowedDomains", cfg.FedWikiAllowedDomains),
|
|
slog.Bool("hasToken", cfg.FedWikiAdminToken != ""))
|
|
}
|
|
return &Activities{
|
|
DB: cfg.DB,
|
|
Logger: cfg.Logger,
|
|
Client: client,
|
|
FedWikiAllowedDomains: cfg.FedWikiAllowedDomains,
|
|
SupportURL: cfg.SupportURL,
|
|
}
|
|
}
|
|
|
|
// CreateFedWikiSiteInput is the input for the CreateFedWikiSite activity.
|
|
type CreateFedWikiSiteInput 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
|
|
}
|
|
|
|
// CreateFedWikiSiteOutput is the output for the CreateFedWikiSite activity.
|
|
type CreateFedWikiSiteOutput struct {
|
|
SiteID int64
|
|
Domain string
|
|
}
|
|
|
|
// CreateFedWikiSiteActivity creates a new site on the FedWiki farm and records it in the database.
|
|
func (a *Activities) CreateFedWikiSiteActivity(ctx context.Context, input CreateFedWikiSiteInput) (*CreateFedWikiSiteOutput, error) {
|
|
// Get the site domain - use provided or fall back to first allowed domain
|
|
siteDomain := input.SiteDomain
|
|
if siteDomain == "" && !input.IsCustomDomain {
|
|
if len(a.FedWikiAllowedDomains) == 0 {
|
|
return nil, temporal.NewNonRetryableApplicationError(
|
|
"No allowed domains configured for site creation",
|
|
"ConfigurationError",
|
|
nil,
|
|
)
|
|
}
|
|
siteDomain = a.FedWikiAllowedDomains[0]
|
|
}
|
|
|
|
// Build the full domain for FarmManager API and storage
|
|
fullDomain := BuildFullDomain(input.Domain, siteDomain, input.IsCustomDomain)
|
|
|
|
a.Logger.Info("creating FedWiki site",
|
|
slog.Int64("userID", input.UserID),
|
|
slog.String("domain", fullDomain),
|
|
slog.String("ownerName", input.OwnerName),
|
|
slog.String("ownerID", input.OwnerID))
|
|
|
|
// Call the FarmManager API to create the site
|
|
_, err := a.Client.CreateSite(ctx, fullDomain, input.OwnerName, input.OwnerID)
|
|
if err != nil {
|
|
a.Logger.Error("failed to create site via FarmManager API",
|
|
slog.String("domain", fullDomain),
|
|
slog.Any("error", err))
|
|
|
|
// Check if this is a non-retryable error (e.g., 409 Conflict)
|
|
var apiErr *FarmManagerAPIError
|
|
if errors.As(err, &apiErr) && apiErr.IsNonRetryable() {
|
|
return nil, temporal.NewNonRetryableApplicationError(
|
|
apiErr.UserMessage(),
|
|
"FarmManagerAPIError",
|
|
err,
|
|
)
|
|
}
|
|
return nil, fmt.Errorf("failed to create site on FedWiki farm: %w", err)
|
|
}
|
|
|
|
// Record the site in our database with the full domain
|
|
site, err := a.DB.CreateSite(ctx, db.CreateSiteParams{
|
|
UserID: input.UserID,
|
|
Domain: fullDomain,
|
|
IsCustomDomain: boolToInt64(input.IsCustomDomain),
|
|
})
|
|
if err != nil {
|
|
a.Logger.Error("failed to record site in database",
|
|
slog.String("domain", fullDomain),
|
|
slog.Any("error", err))
|
|
// Note: Site was created on FedWiki but not recorded locally
|
|
// This is a partial failure state that may need manual resolution
|
|
return nil, fmt.Errorf("site created on FedWiki but failed to record locally: %w", err)
|
|
}
|
|
|
|
a.Logger.Info("FedWiki site created successfully",
|
|
slog.Int64("siteID", site.ID),
|
|
slog.String("domain", fullDomain))
|
|
|
|
return &CreateFedWikiSiteOutput{
|
|
SiteID: site.ID,
|
|
Domain: site.Domain,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteFedWikiSiteInput is the input for the DeleteFedWikiSite activity.
|
|
type DeleteFedWikiSiteInput struct {
|
|
UserID int64
|
|
Domain string // Full domain as stored in database
|
|
}
|
|
|
|
// DeleteFedWikiSiteOutput is the output for the DeleteFedWikiSite activity.
|
|
type DeleteFedWikiSiteOutput struct {
|
|
Success bool
|
|
}
|
|
|
|
// DeleteFedWikiSiteActivity deletes a site from the FedWiki farm and removes it from the database.
|
|
func (a *Activities) DeleteFedWikiSiteActivity(ctx context.Context, input DeleteFedWikiSiteInput) (*DeleteFedWikiSiteOutput, error) {
|
|
a.Logger.Info("deleting FedWiki site",
|
|
slog.Int64("userID", input.UserID),
|
|
slog.String("domain", input.Domain))
|
|
|
|
// Call the FarmManager API to delete the site (hard delete)
|
|
_, err := a.Client.HardDeleteSite(ctx, input.Domain)
|
|
if err != nil {
|
|
a.Logger.Error("failed to delete site via FarmManager API",
|
|
slog.String("domain", input.Domain),
|
|
slog.Any("error", err))
|
|
|
|
// Check if this is a non-retryable error (e.g., 403 Forbidden, 401 Unauthorized)
|
|
var apiErr *FarmManagerAPIError
|
|
if errors.As(err, &apiErr) && apiErr.IsNonRetryable() {
|
|
return nil, temporal.NewNonRetryableApplicationError(
|
|
apiErr.UserMessage(),
|
|
"FarmManagerAPIError",
|
|
err,
|
|
)
|
|
}
|
|
return nil, fmt.Errorf("failed to delete site on FedWiki farm: %w", err)
|
|
}
|
|
|
|
// Remove the site from our database
|
|
err = a.DB.DeleteSiteByUserIDAndDomain(ctx, db.DeleteSiteByUserIDAndDomainParams{
|
|
UserID: input.UserID,
|
|
Domain: input.Domain,
|
|
})
|
|
if err != nil {
|
|
a.Logger.Error("failed to remove site from database",
|
|
slog.String("domain", input.Domain),
|
|
slog.Any("error", err))
|
|
// Note: Site was deleted on FedWiki but not removed locally
|
|
return nil, fmt.Errorf("site deleted on FedWiki but failed to remove locally: %w", err)
|
|
}
|
|
|
|
a.Logger.Info("FedWiki site deleted successfully",
|
|
slog.String("domain", input.Domain))
|
|
|
|
return &DeleteFedWikiSiteOutput{Success: true}, nil
|
|
}
|
|
|
|
// ListFedWikiSitesInput is the input for the ListFedWikiSites activity.
|
|
type ListFedWikiSitesInput struct{}
|
|
|
|
// ListFedWikiSitesOutput is the output for the ListFedWikiSites activity.
|
|
type ListFedWikiSitesOutput struct {
|
|
Sites []SiteInfo
|
|
}
|
|
|
|
// ListFedWikiSitesActivity lists all sites on the FedWiki farm.
|
|
func (a *Activities) ListFedWikiSitesActivity(ctx context.Context, input ListFedWikiSitesInput) (*ListFedWikiSitesOutput, error) {
|
|
a.Logger.Info("listing FedWiki sites")
|
|
|
|
sites, err := a.Client.ListSites(ctx)
|
|
if err != nil {
|
|
a.Logger.Error("failed to list sites via FarmManager API",
|
|
slog.Any("error", err))
|
|
return nil, fmt.Errorf("failed to list sites on FedWiki farm: %w", err)
|
|
}
|
|
|
|
a.Logger.Info("FedWiki sites listed successfully",
|
|
slog.Int("count", len(sites)))
|
|
|
|
return &ListFedWikiSitesOutput{Sites: sites}, nil
|
|
}
|
|
|
|
// CheckQuotaInput is the input for the CheckQuota activity.
|
|
type CheckQuotaInput struct {
|
|
UserID int64
|
|
}
|
|
|
|
// CheckQuotaOutput is the output for the CheckQuota activity.
|
|
type CheckQuotaOutput struct {
|
|
CurrentCount int64
|
|
Quota int64
|
|
CanCreate bool
|
|
}
|
|
|
|
// CheckQuotaActivity checks if a user has quota available to create a new site.
|
|
func (a *Activities) CheckQuotaActivity(ctx context.Context, input CheckQuotaInput) (*CheckQuotaOutput, error) {
|
|
a.Logger.Info("checking user quota", slog.Int64("userID", input.UserID))
|
|
|
|
// Get current site count
|
|
count, err := a.DB.GetSiteCountByUserID(ctx, input.UserID)
|
|
if err != nil {
|
|
a.Logger.Error("failed to get site count",
|
|
slog.Int64("userID", input.UserID),
|
|
slog.Any("error", err))
|
|
return nil, fmt.Errorf("failed to get site count: %w", err)
|
|
}
|
|
|
|
// Get user's quota
|
|
quota, err := a.DB.GetUserSitesQuota(ctx, input.UserID)
|
|
if err != nil {
|
|
a.Logger.Error("failed to get user quota",
|
|
slog.Int64("userID", input.UserID),
|
|
slog.Any("error", err))
|
|
return nil, fmt.Errorf("failed to get user quota: %w", err)
|
|
}
|
|
|
|
canCreate := count < quota
|
|
|
|
a.Logger.Info("quota check completed",
|
|
slog.Int64("userID", input.UserID),
|
|
slog.Int64("currentCount", count),
|
|
slog.Int64("quota", quota),
|
|
slog.Bool("canCreate", canCreate))
|
|
|
|
return &CheckQuotaOutput{
|
|
CurrentCount: count,
|
|
Quota: quota,
|
|
CanCreate: canCreate,
|
|
}, nil
|
|
}
|
|
|
|
// boolToInt64 converts a bool to int64 for SQLite compatibility.
|
|
func boolToInt64(b bool) int64 {
|
|
if b {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|