tenant

package module
v0.0.0-...-77e2b90 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2026 License: MIT Imports: 11 Imported by: 0

README

Tenant Plugin

Multi-tenant domain management plugin for Leeforge CMS. Provides tenant CRUD, membership management, domain resolution, and event-driven lifecycle hooks.

Quick Start

import (
    tenant "github.com/leeforge/plugins/tenant"
    tenantfactory "github.com/leeforge/plugins/tenant/factory"
)

// 1. Register factory (before plugin Enable)
services.Register(tenant.ServiceKeyTenantFactory, tenantfactory.NewEntFactory(entClient))

// 2. Register plugin
runtime.Register(&tenant.TenantPlugin{})

Plugin Metadata

Field Value
Name tenant
Version 1.0.0
Dependencies None
Module github.com/leeforge/plugins/tenant

Architecture

tenant/                        # https://github.com/leeforge/plugins/tree/main/tenant
├── plugin.go                  # Plugin lifecycle (Enable/Disable/Install)
├── ports.go                   # ServiceFactory interface
├── shared/
│   ├── errors.go              # Exported error sentinels
│   ├── events.go              # Event constants and payloads
│   ├── exported.go            # Re-exported public types
│   └── ports.go               # RoleSeeder / UserLookup interfaces
├── tenant/
│   ├── handler.go             # HTTP handlers
│   ├── service.go             # Business logic
│   └── dto.go                 # Request/Response DTOs
└── factory/
    └── ent_factory.go         # Default Ent-backed factory

ServiceFactory

Host application must provide a ServiceFactory implementation registered under ServiceKeyTenantFactory:

const ServiceKeyTenantFactory = "adapter.tenant.factory"

type ServiceFactory interface {
    NewTenantService(domainSvc core.DomainWriter, events plugin.EventBus, logger logging.Logger) *tenantmod.Service
    RoleSeeder() RoleSeeder
    UserLookup() UserLookup
    Models() []any
}

The built-in factory.EntFactory provides a default implementation backed by core/server/ent.Client.

HTTP Routes

All routes are registered under /tenants:

Method Path Handler Description
GET /tenants/me ListMyTenants List tenants for current user
GET /tenants/ ListTenants List all tenants (platform domain only)
POST /tenants/ CreateTenant Create new tenant
GET /tenants/{id} GetTenant Get tenant by ID
PUT /tenants/{id} UpdateTenant Update tenant
DELETE /tenants/{id} DeleteTenant Soft-delete tenant
POST /tenants/{id}/members AddMember Add member to tenant
GET /tenants/{id}/members ListMembers List tenant members (paginated)
DELETE /tenants/{id}/members/{userId} RemoveMember Remove member

Events

Published
Event Constant Payload
tenant.created EventTenantCreated TenantEventData
tenant.updated EventTenantUpdated TenantEventData
tenant.deleted EventTenantDeleted TenantEventData
tenant.member.added EventTenantMemberAdded MemberEventData
tenant.member.removed EventTenantMemberRemoved MemberEventData
Subscribed
Event Handler
user.deleted Cleans up all memberships for deleted user
Event Payloads
type TenantEventData struct {
    TenantID   uuid.UUID `json:"tenantId"`
    TenantCode string    `json:"tenantCode"`
    DomainID   uuid.UUID `json:"domainId"`
    ActorID    uuid.UUID `json:"actorId"`
}

type MemberEventData struct {
    TenantID uuid.UUID `json:"tenantId"`
    UserID   uuid.UUID `json:"userId"`
    Role     string    `json:"role"`
    ActorID  uuid.UUID `json:"actorId"`
}

Service Keys

Key Type Description
adapter.tenant.factory ServiceFactory Resolved during Enable
tenant.service TenantServiceAPI Public tenant query API
domain.plugin.tenant TenantPlugin Domain resolution provider

Public API (Cross-Plugin)

Other plugins can resolve TenantServiceAPI from the service registry:

type TenantServiceAPI interface {
    GetTenant(ctx context.Context, id uuid.UUID) (*TenantInfo, error)
    GetTenantByCode(ctx context.Context, code string) (*TenantInfo, error)
    IsMember(ctx context.Context, tenantID, userID uuid.UUID) (bool, error)
    GetDomainID(ctx context.Context, tenantCode string) (uuid.UUID, error)
}

Domain Resolution

The tenant plugin implements the domain plugin pattern:

  • ResolveDomain() — Resolves tenant domain from X-Tenant-ID header
  • ValidateMembership() — Checks if subject is member of domain
  • TypeCode() — Returns "tenant"

Error Sentinels

import "github.com/leeforge/plugins/tenant/shared"

shared.ErrTenantNotFound       // Tenant not found
shared.ErrTenantCodeExists     // Tenant code already exists
shared.ErrInvalidTenant        // Invalid tenant data
shared.ErrMemberExists         // User is already a member
shared.ErrMemberNotFound       // Membership not found
shared.ErrPlatformDomainOnly   // Operation requires platform domain
shared.ErrParentTenantInvalid  // Invalid parent tenant

Framework Interfaces

TenantPlugin implements:

Interface Purpose
plugin.Plugin Core lifecycle (Enable)
plugin.Installable First-run setup
plugin.Disableable Shutdown cleanup
plugin.RouteProvider HTTP route registration
plugin.EventSubscriber Domain event subscriptions
plugin.HealthReporter Health check via Ping()
plugin.Configurable Plugin options metadata
plugin.ModelProvider Ent model export

Ent Models

The plugin requires these Ent schemas (provided by core/server/ent):

  • Tenant — Tenant entity with code, name, domain reference
  • TenantUser — Tenant membership (user-tenant-role mapping)

Documentation

Index

Constants

View Source
const (
	EventTenantCreated       = shared.EventTenantCreated
	EventTenantUpdated       = shared.EventTenantUpdated
	EventTenantDeleted       = shared.EventTenantDeleted
	EventTenantMemberAdded   = shared.EventTenantMemberAdded
	EventTenantMemberRemoved = shared.EventTenantMemberRemoved
)

Re-export event constants.

View Source
const ServiceKeyTenantFactory = "adapter.tenant.factory"

Variables

View Source
var (
	ErrTenantNotFound      = shared.ErrTenantNotFound
	ErrTenantCodeExists    = shared.ErrTenantCodeExists
	ErrInvalidTenant       = shared.ErrInvalidTenant
	ErrMemberExists        = shared.ErrMemberExists
	ErrMemberNotFound      = shared.ErrMemberNotFound
	ErrPlatformDomainOnly  = shared.ErrPlatformDomainOnly
	ErrParentTenantInvalid = shared.ErrParentTenantInvalid
)

Re-export sentinel errors.

Functions

This section is empty.

Types

type MemberEventData

type MemberEventData = shared.MemberEventData

Re-export shared types so external consumers can import from this package.

type RoleSeeder

type RoleSeeder = shared.RoleSeeder

Re-export interface types from shared so factory implementations import from this package.

type ServiceFactory

type ServiceFactory interface {
	NewTenantService(
		domainSvc core.DomainWriter,
		events plugin.EventBus,
		logger logging.Logger,
	) *tenantmod.Service
	RoleSeeder() RoleSeeder
	UserLookup() UserLookup
	Models() []any
}

ServiceFactory creates tenant plugin services using host-provided adapters.

type TenantEventData

type TenantEventData = shared.TenantEventData

Re-export shared types so external consumers can import from this package.

type TenantInfo

type TenantInfo = shared.TenantInfo

Re-export shared types so external consumers can import from this package.

type TenantPlugin

type TenantPlugin struct {
	// contains filtered or unexported fields
}

TenantPlugin implements the framework plugin contracts.

func (*TenantPlugin) Dependencies

func (p *TenantPlugin) Dependencies() []string

func (*TenantPlugin) Disable

func (p *TenantPlugin) Disable(ctx context.Context, app *plugin.AppContext) error

Disable performs cleanup on plugin shutdown.

func (*TenantPlugin) Enable

func (p *TenantPlugin) Enable(ctx context.Context, app *plugin.AppContext) error

func (*TenantPlugin) HealthCheck

func (p *TenantPlugin) HealthCheck(ctx context.Context) error

func (*TenantPlugin) Install

func (p *TenantPlugin) Install(ctx context.Context, app *plugin.AppContext) error

Install seeds domain types and default tenants on first run.

func (*TenantPlugin) Name

func (p *TenantPlugin) Name() string

func (*TenantPlugin) PluginOptions

func (p *TenantPlugin) PluginOptions() plugin.PluginOptions

func (*TenantPlugin) RegisterModels

func (p *TenantPlugin) RegisterModels() []any

func (*TenantPlugin) RegisterRoutes

func (p *TenantPlugin) RegisterRoutes(router chi.Router)

func (*TenantPlugin) ResolveDomain

func (p *TenantPlugin) ResolveDomain(ctx context.Context, r *http.Request) (*core.ResolvedDomain, bool, error)

func (*TenantPlugin) SubscribeEvents

func (p *TenantPlugin) SubscribeEvents(bus plugin.EventBus)

SubscribeEvents registers event handlers.

func (*TenantPlugin) TypeCode

func (p *TenantPlugin) TypeCode() string

func (*TenantPlugin) ValidateMembership

func (p *TenantPlugin) ValidateMembership(ctx context.Context, domainID, subjectID uuid.UUID) (bool, error)

func (*TenantPlugin) Version

func (p *TenantPlugin) Version() string

type TenantServiceAPI

type TenantServiceAPI = shared.TenantServiceAPI

Re-export shared types so external consumers can import from this package.

type UserInfo

type UserInfo = shared.UserInfo

Re-export interface types from shared so factory implementations import from this package.

type UserLookup

type UserLookup = shared.UserLookup

Re-export interface types from shared so factory implementations import from this package.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL