package access import ( "context" "fmt" "strings" "sub2api-cn-relay-manager/internal/host/sub2api" ) const ( ModeSubscription = "subscription" ModeSelfService = "self_service" ) type SubscriptionTarget struct { UserID string DurationDays int } type ClosureRequest struct { Mode string ProbeAPIKey string Subscriptions []SubscriptionTarget GroupID string ExpectedModel string } type Host interface { EnsureSubscriptionAccess(ctx context.Context, req sub2api.EnsureSubscriptionAccessRequest) (sub2api.SubscriptionAccessRef, error) AssignSubscription(ctx context.Context, req sub2api.AssignSubscriptionRequest) (sub2api.SubscriptionRef, error) CheckGatewayAccess(ctx context.Context, req sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error) } type Service struct { host Host } func NewService(host Host) *Service { return &Service{host: host} } func Validate(req ClosureRequest) error { switch strings.TrimSpace(req.Mode) { case ModeSubscription: if len(req.Subscriptions) == 0 { return fmt.Errorf("subscription access requires at least one subscription target") } case ModeSelfService: if strings.TrimSpace(req.ProbeAPIKey) == "" { return fmt.Errorf("self_service access requires probe api key") } default: return fmt.Errorf("unsupported access mode %q", req.Mode) } if strings.TrimSpace(req.Mode) != ModeSubscription && strings.TrimSpace(req.ProbeAPIKey) == "" { return fmt.Errorf("access probe api key is required to verify gateway closure") } return nil } func (s *Service) Close(ctx context.Context, req ClosureRequest) (sub2api.GatewayAccessResult, error) { if s == nil || s.host == nil { return sub2api.GatewayAccessResult{}, fmt.Errorf("access host is required") } if err := Validate(req); err != nil { return sub2api.GatewayAccessResult{}, err } probeAPIKey := strings.TrimSpace(req.ProbeAPIKey) if strings.TrimSpace(req.Mode) == ModeSubscription { for _, target := range req.Subscriptions { resolvedTarget := target.UserID accessRef, err := s.host.EnsureSubscriptionAccess(ctx, sub2api.EnsureSubscriptionAccessRequest{UserSelector: target.UserID, GroupID: req.GroupID}) if err != nil { return sub2api.GatewayAccessResult{}, fmt.Errorf("ensure subscription access for %s: %w", target.UserID, err) } if strings.TrimSpace(accessRef.UserID) != "" { resolvedTarget = accessRef.UserID } if strings.TrimSpace(accessRef.APIKey) != "" { probeAPIKey = strings.TrimSpace(accessRef.APIKey) } if _, err := s.host.AssignSubscription(ctx, sub2api.AssignSubscriptionRequest{UserID: resolvedTarget, GroupID: req.GroupID, DurationDays: target.DurationDays}); err != nil { return sub2api.GatewayAccessResult{}, fmt.Errorf("assign subscription for %s: %w", target.UserID, err) } } } if probeAPIKey == "" { return sub2api.GatewayAccessResult{}, fmt.Errorf("access probe api key is required to verify gateway closure") } result, err := s.host.CheckGatewayAccess(ctx, sub2api.GatewayAccessCheckRequest{APIKey: probeAPIKey, ExpectedModel: req.ExpectedModel}) if err != nil { return sub2api.GatewayAccessResult{}, fmt.Errorf("check gateway access: %w", err) } return result, nil }