From 917eee13a28c8ac9b2a80520282adcd3ba423667 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Fri, 1 May 2026 12:36:13 +0800 Subject: [PATCH] feat: add menus, subscriptions, public users, and plugin bridge to SDK New capability interfaces: - menus.Menus: menu/nav access (GetMenuByName, GetMenuItems) - subscriptions.Subscriptions: tier/plan access (GetUserTierLevel, GetTierBySlug, ListActivePlans) - auth.PublicUsers: public user profiles (GetByUsername, GetByID) - plugin.PluginBridge: inter-plugin service registry with typed GetServiceAs[T] helper All added to ServiceDeps for plugin consumption. Co-Authored-By: Claude Opus 4.6 --- auth/public_users.go | 25 +++++++++++++++++++ menus/menus.go | 34 ++++++++++++++++++++++++++ plugin/bridge.go | 22 +++++++++++++++++ plugin/deps.go | 17 +++++++++---- subscriptions/subscriptions.go | 44 ++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 auth/public_users.go create mode 100644 menus/menus.go create mode 100644 plugin/bridge.go create mode 100644 subscriptions/subscriptions.go diff --git a/auth/public_users.go b/auth/public_users.go new file mode 100644 index 0000000..63ae053 --- /dev/null +++ b/auth/public_users.go @@ -0,0 +1,25 @@ +package auth + +import ( + "context" + + "github.com/google/uuid" +) + +// PublicUsers provides public/community user profile access for plugins. +type PublicUsers interface { + GetByUsername(ctx context.Context, username string) (*PublicUserProfile, error) + GetByID(ctx context.Context, id uuid.UUID) (*PublicUserProfile, error) +} + +// PublicUserProfile is a plugin-facing view of a public user. +type PublicUserProfile struct { + ID uuid.UUID + Email string + Username string + DisplayName string + AvatarURL string + Bio string + EmailVerified bool + Role string +} diff --git a/menus/menus.go b/menus/menus.go new file mode 100644 index 0000000..3b0cba1 --- /dev/null +++ b/menus/menus.go @@ -0,0 +1,34 @@ +package menus + +import ( + "context" + + "github.com/google/uuid" +) + +// Menus provides menu and navigation access for plugins. +type Menus interface { + GetMenuByName(ctx context.Context, name string) (*Menu, error) + GetMenuItems(ctx context.Context, menuID uuid.UUID) ([]MenuItem, error) +} + +// Menu represents a named navigation menu. +type Menu struct { + ID uuid.UUID + Name string +} + +// MenuItem represents a single entry in a navigation menu. +type MenuItem struct { + ID uuid.UUID + MenuID uuid.UUID + Label string + URL string + PageSlug string + ParentID *uuid.UUID + SortOrder int32 + OpenInNewTab bool + CssClass string + ItemType string + Icon string +} diff --git a/plugin/bridge.go b/plugin/bridge.go new file mode 100644 index 0000000..ae27b93 --- /dev/null +++ b/plugin/bridge.go @@ -0,0 +1,22 @@ +package plugin + +// PluginBridge allows plugins to share services with each other. +// Plugins register named services during startup; other plugins look them up at runtime. +type PluginBridge interface { + RegisterService(pluginName, serviceName string, service any) + GetService(pluginName, serviceName string) any +} + +// GetServiceAs retrieves a typed service from the bridge. +func GetServiceAs[T any](bridge PluginBridge, pluginName, serviceName string) (T, bool) { + var zero T + if bridge == nil { + return zero, false + } + svc := bridge.GetService(pluginName, serviceName) + if svc == nil { + return zero, false + } + typed, ok := svc.(T) + return typed, ok +} diff --git a/plugin/deps.go b/plugin/deps.go index 07289c0..0380848 100644 --- a/plugin/deps.go +++ b/plugin/deps.go @@ -5,19 +5,25 @@ import ( "connectrpc.com/connect" "git.dev.alexdunmow.com/block/core/ai" + "git.dev.alexdunmow.com/block/core/auth" "git.dev.alexdunmow.com/block/core/content" "git.dev.alexdunmow.com/block/core/crypto" "git.dev.alexdunmow.com/block/core/gating" + "git.dev.alexdunmow.com/block/core/menus" "git.dev.alexdunmow.com/block/core/settings" + "git.dev.alexdunmow.com/block/core/subscriptions" ) // ServiceDeps provides dependencies that plugins need for RPC service handlers. type ServiceDeps struct { // Capability interfaces — typed access to CMS functionality - Content content.Content - Settings settings.Settings - Gating gating.Gating - Crypto crypto.Crypto + Content content.Content + Settings settings.Settings + Gating gating.Gating + Crypto crypto.Crypto + Menus menus.Menus + PublicUsers auth.PublicUsers + Subscriptions subscriptions.Subscriptions // Database — for plugin's own sqlc queries Pool Pool @@ -36,6 +42,9 @@ type ServiceDeps struct { // Email EmailSender EmailSender + // Plugin interop + Bridge PluginBridge + // Extension points — typed as narrow interfaces where possible JobRunner JobRunner EmbeddingService EmbeddingService diff --git a/subscriptions/subscriptions.go b/subscriptions/subscriptions.go new file mode 100644 index 0000000..02aa512 --- /dev/null +++ b/subscriptions/subscriptions.go @@ -0,0 +1,44 @@ +package subscriptions + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +// Subscriptions provides subscription tier and billing plan access for plugins. +type Subscriptions interface { + GetUserTierLevel(ctx context.Context, userID uuid.UUID) (*TierLevel, error) + GetTierBySlug(ctx context.Context, slug string) (*Tier, error) + ListActivePlans(ctx context.Context, tierID uuid.UUID) ([]Plan, error) +} + +// TierLevel is the resolved tier for a user. +type TierLevel struct { + Level int + Features []byte +} + +// Tier is a subscription tier definition. +type Tier struct { + ID uuid.UUID + Name string + Slug string + Level int + Description string + Features []byte + IsDefault bool + Position int +} + +// Plan is an active billing plan within a tier. +type Plan struct { + ID uuid.UUID + TierID uuid.UUID + BillingInterval string + Amount int32 + Currency string + IsActive bool + CreatedAt time.Time +}