Documentation
¶
Overview ¶
Package organizations provides multi-tenancy and team management for Aegis.
This plugin enables SaaS applications to manage multiple organizations (workspaces, companies, tenants) with member roles and team hierarchies. It implements a complete RBAC (Role-Based Access Control) system for organizational resources.
Multi-Tenancy Architecture:
- Organization: Top-level tenant (e.g., "Acme Corp", "Tech Startup")
- Members: Users with roles in an organization (owner, admin, member)
- Teams: Groups within an organization (e.g., "Engineering", "Sales")
- Team Members: Users with roles in a team (lead, member)
Role Hierarchy:
Organization Roles: - owner: Full control, can delete organization, manage all members - admin: Can manage members, teams, but cannot delete organization - member: Read access to organization resources Team Roles: - lead: Can manage team members and settings - member: Participate in team activities
Common Use Cases:
- SaaS with company workspaces (Slack, Notion, GitHub)
- Project management tools with teams
- Enterprise apps with department hierarchies
- Multi-tenant platforms with access control
Database Schema:
- organization: Stores organization metadata (id, name, slug)
- members: Links users to organizations with roles
- team: Stores team metadata within organizations
- team_member: Links users to teams with roles
Example Setup:
// Create organization plugin orgPlugin := organizations.New(nil, plugins.DialectPostgres) // User creates organization org, _ := orgPlugin.CreateOrganization(ctx, "Acme Corp", "acme", user.ID) // User is automatically added as owner // Owner adds admin orgPlugin.AddOrganizationMember(ctx, org.ID, adminUserID, "admin") // Admin creates team team, _ := orgPlugin.CreateTeam(ctx, org.ID, "Engineering", "Dev team") // Admin adds team member orgPlugin.AddTeamMember(ctx, team.ID, devUserID, "member")
Security Features:
- All routes require authentication (RequireAuthMiddleware)
- Role-based middleware (RequireOrganizationMember, RequireOrganizationAdmin, RequireOrganizationOwner)
- Foreign key constraints prevent orphaned records
- Cascade deletes when organization is deleted
Index ¶
- Constants
- func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)
- func GetSchema(dialect plugins.Dialect) (*plugins.Schema, error)
- func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
- type AddOrganizationMemberRequest
- type AddTeamMemberRequest
- type CreateOrganizationRequest
- type CreateTeamRequest
- type DefaultOrganizationStore
- func (s *DefaultOrganizationStore) CreateMember(ctx context.Context, id, userID, orgID, role string, ...) error
- func (s *DefaultOrganizationStore) CreateOrganization(ctx context.Context, id, name, slug string, createdAt, updatedAt time.Time) error
- func (s *DefaultOrganizationStore) CreateTeam(ctx context.Context, id, orgID, name, description string, ...) error
- func (s *DefaultOrganizationStore) CreateTeamMember(ctx context.Context, id, teamID, userID, role string, ...) error
- func (s *DefaultOrganizationStore) DeleteOrganization(ctx context.Context, id string, updatedAt time.Time) error
- func (s *DefaultOrganizationStore) DeleteTeam(ctx context.Context, id string) error
- func (s *DefaultOrganizationStore) GetMember(ctx context.Context, userID, orgID string) (Member, error)
- func (s *DefaultOrganizationStore) GetOrganization(ctx context.Context, id string) (Organization, error)
- func (s *DefaultOrganizationStore) GetOrganizationBySlug(ctx context.Context, slug string) (Organization, error)
- func (s *DefaultOrganizationStore) GetTeam(ctx context.Context, id string) (Team, error)
- func (s *DefaultOrganizationStore) GetTeamMember(ctx context.Context, teamID, userID string) (TeamMember, error)
- func (s *DefaultOrganizationStore) IsOrganizationMember(ctx context.Context, userID, orgID string) (bool, error)
- func (s *DefaultOrganizationStore) IsOwner(ctx context.Context, userID, orgID string) (bool, error)
- func (s *DefaultOrganizationStore) IsOwnerOrAdmin(ctx context.Context, userID, orgID string) (bool, error)
- func (s *DefaultOrganizationStore) ListOrganizationMembers(ctx context.Context, orgID string) ([]Member, error)
- func (s *DefaultOrganizationStore) ListTeamMembers(ctx context.Context, teamID string) ([]TeamMember, error)
- func (s *DefaultOrganizationStore) ListTeams(ctx context.Context, orgID string) ([]Team, error)
- func (s *DefaultOrganizationStore) ListUserOrganizations(ctx context.Context, userID string) ([]Organization, error)
- func (s *DefaultOrganizationStore) RemoveMember(ctx context.Context, userID, orgID string) error
- func (s *DefaultOrganizationStore) RemoveTeamMember(ctx context.Context, teamID, userID string) error
- func (s *DefaultOrganizationStore) UpdateMemberRole(ctx context.Context, userID, orgID, role string, updatedAt time.Time) error
- func (s *DefaultOrganizationStore) UpdateOrganization(ctx context.Context, id, name, slug string, updatedAt time.Time) error
- func (s *DefaultOrganizationStore) UpdateTeam(ctx context.Context, id, name, description string, updatedAt time.Time) error
- func (s *DefaultOrganizationStore) UpdateTeamMemberRole(ctx context.Context, teamID, userID, role string, updatedAt time.Time) error
- type Member
- type Organization
- type OrganizationStore
- type Plugin
- func (p *Plugin) AddOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) AddTeamMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CreateOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CreateTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) DeleteOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) DeleteTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) Dependencies() []plugins.Dependency
- func (p *Plugin) Description() string
- func (p *Plugin) GetMigrations() []plugins.Migration
- func (p *Plugin) GetOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) GetSchemas() []plugins.Schema
- func (p *Plugin) GetTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error
- func (p *Plugin) ListOrganizationMembersHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListOrganizationsHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListTeamMembersHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListTeamsHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) MountRoutes(r router.Router, prefix string)
- func (p *Plugin) Name() string
- func (p *Plugin) ProvidesAuthMethods() []string
- func (p *Plugin) RemoveOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) RemoveTeamMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) RequireOrganizationAdminMiddleware() func(http.Handler) http.Handler
- func (p *Plugin) RequireOrganizationMemberMiddleware() func(http.Handler) http.Handler
- func (p *Plugin) RequireOrganizationOwnerMiddleware() func(http.Handler) http.Handler
- func (p *Plugin) RequiresTables() []string
- func (p *Plugin) UpdateMemberRoleHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateTeamMemberRoleHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) Version() string
- type Team
- type TeamMember
- type UpdateMemberRoleRequest
- type UpdateOrganizationRequest
- type UpdateTeamMemberRoleRequest
- type UpdateTeamRequest
Constants ¶
const ( // Request schemas SchemaCreateOrganizationRequest = "CreateOrganizationRequest" SchemaUpdateOrganizationRequest = "UpdateOrganizationRequest" SchemaAddOrganizationMemberRequest = "AddOrganizationMemberRequest" SchemaUpdateMemberRoleRequest = "UpdateMemberRoleRequest" SchemaCreateTeamRequest = "CreateTeamRequest" SchemaUpdateTeamRequest = "UpdateTeamRequest" SchemaAddTeamMemberRequest = "AddTeamMemberRequest" SchemaUpdateTeamMemberRoleRequest = "UpdateTeamMemberRoleRequest" // Response schemas SchemaOrganization = "Organization" SchemaOrganizationList = "OrganizationList" SchemaTeam = "Team" SchemaTeamList = "TeamList" SchemaMember = "Member" SchemaMemberList = "MemberList" SchemaTeamMember = "TeamMember" SchemaTeamMemberList = "TeamMemberList" )
Schema names for OpenAPI specification generation.
These constants define the OpenAPI schema names for organizations request/response types. They are used in route metadata to generate accurate API documentation with typed request/response examples.
Variables ¶
This section is empty.
Functions ¶
func GetMigrations ¶
GetMigrations returns all database migrations for the organizations plugin.
This function loads migrations from embedded SQL files and returns them in version order. The initial schema is always treated as version 001.
Version Numbering:
- Version 001: Initial schema from internal/sql/<dialect>/schema.sql
- Version 002+: Additional migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql
Migration File Format:
- Up migration: 002_add_teams.up.sql
- Down migration: 002_add_teams.down.sql
Parameters:
- dialect: Database dialect (postgres, mysql, sqlite)
Returns:
- []plugins.Migration: Sorted list of migrations (oldest first)
- error: If schema files cannot be read or parsed
func GetSchema ¶
GetSchema returns the database schema for the organizations plugin.
The schema defines tables for multi-tenant organization management:
- organization: Organization metadata (id, name, slug, disabled)
- members: Organization membership with roles (owner, admin, member)
- team: Team metadata within organizations
- team_member: Team membership with roles (lead, member)
All tables include created_at and updated_at timestamps.
Parameters:
- dialect: Database dialect (postgres, mysql)
Returns:
- *plugins.Schema: Schema definition with SQL DDL
- error: If dialect is not supported
func GetSchemaRequirements ¶
func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
GetSchemaRequirements returns schema validation requirements for the organizations plugin.
This function defines structural requirements that must be satisfied for the plugin to function correctly. The Init() method validates these requirements at startup.
Validation Checks:
- Table existence: organization, members, team, team_member
- Column existence: All required columns in each table
- Column properties: Data types, nullability (not implemented yet)
These checks help detect schema drift, incomplete migrations, or manual schema changes.
Parameters:
- dialect: Database dialect (postgres, mysql)
Returns:
- []plugins.SchemaRequirement: List of validation requirements
Types ¶
type AddOrganizationMemberRequest ¶
type AddOrganizationMemberRequest struct {
UserID string `json:"userId"` // User ID to add
Role string `json:"role"` // Member role ("admin" or "member")
}
AddOrganizationMemberRequest represents a request to add a member to an organization.
Validation Rules:
- userId: Required (must be a valid user ID in the system)
- role: Required, must be "admin" or "member" ("owner" cannot be assigned this way)
Example:
{
"userId": "user_xyz789",
"role": "admin"
}
Security Note: The "owner" role cannot be assigned via this endpoint to prevent privilege escalation. Ownership is assigned during organization creation or via explicit transfer (if implemented).
func (AddOrganizationMemberRequest) Validate ¶
func (r AddOrganizationMemberRequest) Validate() error
Validate validates the add organization member request.
type AddTeamMemberRequest ¶
AddTeamMemberRequest represents a request to add a member to a team.
func (AddTeamMemberRequest) Validate ¶
func (r AddTeamMemberRequest) Validate() error
Validate validates the add team member request.
type CreateOrganizationRequest ¶
type CreateOrganizationRequest struct {
Name string `json:"name"` // Organization display name
Slug string `json:"slug"` // URL-friendly identifier (must be unique)
}
CreateOrganizationRequest represents a request to create an organization.
Validation Rules:
- name: Required, 1-100 characters (organization display name)
- slug: Required, 3-50 characters, lowercase alphanumeric + hyphens only
Example:
{
"name": "Acme Corporation",
"slug": "acme-corp"
}
func (CreateOrganizationRequest) Validate ¶
func (r CreateOrganizationRequest) Validate() error
Validate validates the create organization request.
Returns:
- error: Validation error if name or slug is invalid
type CreateTeamRequest ¶
type CreateTeamRequest struct {
Name string `json:"name"` // Team display name
Description string `json:"description"` // Team purpose/description
}
CreateTeamRequest represents a request to create a team within an organization.
Validation Rules:
- name: Required, 1-100 characters (team display name)
- description: Optional, max 500 characters (team purpose)
Example:
{
"name": "Engineering",
"description": "Software development team"
}
func (CreateTeamRequest) Validate ¶
func (r CreateTeamRequest) Validate() error
Validate validates the create team request.
type DefaultOrganizationStore ¶
type DefaultOrganizationStore struct {
// contains filtered or unexported fields
}
DefaultOrganizationStore implements OrganizationStore using a SQL database.
This implementation uses sqlc-generated type-safe queries to manage organizations, members, teams, and team members in PostgreSQL, MySQL, or SQLite.
Database Tables:
- organization: Organization metadata (id, name, slug)
- members: Organization membership with roles
- team: Team metadata within organizations
- team_member: Team membership with roles
Constraints:
- organization.slug: UNIQUE (for URL routing)
- members(user_id, organization_id): UNIQUE (one membership per user per org)
- team_member(team_id, user_id): UNIQUE (one membership per user per team)
- Foreign keys with CASCADE DELETE for referential integrity
Thread Safety: This store is safe for concurrent use through database transactions.
func NewDefaultOrganizationStore ¶
func NewDefaultOrganizationStore(db *sql.DB) *DefaultOrganizationStore
NewDefaultOrganizationStore creates a new DefaultOrganizationStore backed by SQL.
The provided database connection must have the organization schema applied.
Parameters:
- db: Active SQL database connection
Returns:
- *DefaultOrganizationStore: Configured store ready for use
func (*DefaultOrganizationStore) CreateMember ¶
func (s *DefaultOrganizationStore) CreateMember(ctx context.Context, id, userID, orgID, role string, createdAt, updatedAt time.Time) error
CreateMember adds a member to an organization.
func (*DefaultOrganizationStore) CreateOrganization ¶
func (s *DefaultOrganizationStore) CreateOrganization(ctx context.Context, id, name, slug string, createdAt, updatedAt time.Time) error
CreateOrganization creates a new organization.
func (*DefaultOrganizationStore) CreateTeam ¶
func (s *DefaultOrganizationStore) CreateTeam(ctx context.Context, id, orgID, name, description string, createdAt, updatedAt time.Time) error
CreateTeam creates a new team within an organization.
func (*DefaultOrganizationStore) CreateTeamMember ¶
func (s *DefaultOrganizationStore) CreateTeamMember(ctx context.Context, id, teamID, userID, role string, createdAt, updatedAt time.Time) error
CreateTeamMember adds a member to a team.
func (*DefaultOrganizationStore) DeleteOrganization ¶
func (s *DefaultOrganizationStore) DeleteOrganization(ctx context.Context, id string, updatedAt time.Time) error
DeleteOrganization soft deletes an organization.
func (*DefaultOrganizationStore) DeleteTeam ¶
func (s *DefaultOrganizationStore) DeleteTeam(ctx context.Context, id string) error
DeleteTeam deletes a team.
func (*DefaultOrganizationStore) GetMember ¶
func (s *DefaultOrganizationStore) GetMember(ctx context.Context, userID, orgID string) (Member, error)
GetMember retrieves a specific member from an organization.
func (*DefaultOrganizationStore) GetOrganization ¶
func (s *DefaultOrganizationStore) GetOrganization(ctx context.Context, id string) (Organization, error)
GetOrganization retrieves an organization by its ID.
func (*DefaultOrganizationStore) GetOrganizationBySlug ¶
func (s *DefaultOrganizationStore) GetOrganizationBySlug(ctx context.Context, slug string) (Organization, error)
GetOrganizationBySlug retrieves an organization by its slug.
func (*DefaultOrganizationStore) GetTeamMember ¶
func (s *DefaultOrganizationStore) GetTeamMember(ctx context.Context, teamID, userID string) (TeamMember, error)
GetTeamMember retrieves a specific team member.
func (*DefaultOrganizationStore) IsOrganizationMember ¶
func (s *DefaultOrganizationStore) IsOrganizationMember(ctx context.Context, userID, orgID string) (bool, error)
IsOrganizationMember checks if a user is a member of an organization.
func (*DefaultOrganizationStore) IsOwner ¶
IsOwner checks if a user is the owner of an organization.
func (*DefaultOrganizationStore) IsOwnerOrAdmin ¶
func (s *DefaultOrganizationStore) IsOwnerOrAdmin(ctx context.Context, userID, orgID string) (bool, error)
IsOwnerOrAdmin checks if a user is an owner or admin of an organization.
func (*DefaultOrganizationStore) ListOrganizationMembers ¶
func (s *DefaultOrganizationStore) ListOrganizationMembers(ctx context.Context, orgID string) ([]Member, error)
ListOrganizationMembers retrieves all members of an organization.
func (*DefaultOrganizationStore) ListTeamMembers ¶
func (s *DefaultOrganizationStore) ListTeamMembers(ctx context.Context, teamID string) ([]TeamMember, error)
ListTeamMembers retrieves all members of a team.
func (*DefaultOrganizationStore) ListUserOrganizations ¶
func (s *DefaultOrganizationStore) ListUserOrganizations(ctx context.Context, userID string) ([]Organization, error)
ListUserOrganizations retrieves all organizations for a user.
func (*DefaultOrganizationStore) RemoveMember ¶
func (s *DefaultOrganizationStore) RemoveMember(ctx context.Context, userID, orgID string) error
RemoveMember removes a member from an organization.
func (*DefaultOrganizationStore) RemoveTeamMember ¶
func (s *DefaultOrganizationStore) RemoveTeamMember(ctx context.Context, teamID, userID string) error
RemoveTeamMember removes a member from a team.
func (*DefaultOrganizationStore) UpdateMemberRole ¶
func (s *DefaultOrganizationStore) UpdateMemberRole(ctx context.Context, userID, orgID, role string, updatedAt time.Time) error
UpdateMemberRole updates a member's role in an organization.
func (*DefaultOrganizationStore) UpdateOrganization ¶
func (s *DefaultOrganizationStore) UpdateOrganization(ctx context.Context, id, name, slug string, updatedAt time.Time) error
UpdateOrganization updates an organization's details.
func (*DefaultOrganizationStore) UpdateTeam ¶
func (s *DefaultOrganizationStore) UpdateTeam(ctx context.Context, id, name, description string, updatedAt time.Time) error
UpdateTeam updates a team's details.
func (*DefaultOrganizationStore) UpdateTeamMemberRole ¶
func (s *DefaultOrganizationStore) UpdateTeamMemberRole(ctx context.Context, teamID, userID, role string, updatedAt time.Time) error
UpdateTeamMemberRole updates a team member's role.
type Member ¶
type Member struct {
ID string `json:"id"` // Unique membership identifier
UserID string `json:"userId"` // User ID (foreign key)
OrganizationID string `json:"organizationId"` // Organization ID (foreign key)
Role string `json:"role"` // Member role ("owner", "admin", "member")
CreatedAt time.Time `json:"createdAt"` // When the user joined the organization
UpdatedAt time.Time `json:"updatedAt"` // Last role update timestamp
}
Member represents a user's membership in an organization with a role.
Members link users to organizations with role-based permissions. The role determines what actions the user can perform within the organization.
Database Table: members Unique Constraint: (user_id, organization_id) Foreign Keys: user_id → auth.users.id, organization_id → organization.id
Roles:
- "owner": Full control, can delete organization and manage all settings
- "admin": Can invite/remove members, create teams, but cannot delete org
- "member": Read access to organization resources, cannot manage
Example:
{
"id": "mem_xyz789",
"userId": "user_123",
"organizationId": "org_abc123",
"role": "admin",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
type Organization ¶
type Organization struct {
ID string `json:"id"` // Unique organization identifier
Name string `json:"name"` // Display name (e.g., "Acme Corporation")
Slug string `json:"slug"` // URL-friendly identifier (e.g., "acme-corp")
CreatedAt time.Time `json:"createdAt"` // When the organization was created
UpdatedAt time.Time `json:"updatedAt"` // Last update timestamp
}
Organization represents a workspace, company, or tenant in a multi-tenant system.
Organizations are the top-level container for resources in a multi-tenant application. Each organization has members with roles and can contain teams for hierarchical access.
Database Table: organization Unique Constraint: slug (for URL-friendly access like /org/acme-corp)
Example:
{
"id": "org_abc123",
"name": "Acme Corporation",
"slug": "acme-corp",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
type OrganizationStore ¶
type OrganizationStore interface {
// CreateOrganization creates a new organization.
//
// Parameters:
// - ctx: Request context
// - id: Unique organization ID
// - name: Organization display name
// - slug: URL-friendly identifier (must be unique)
// - createdAt, updatedAt: Timestamps
//
// Returns:
// - error: Duplicate slug or database error
CreateOrganization(ctx context.Context, id, name, slug string, createdAt, updatedAt time.Time) error
// GetOrganization retrieves an organization by ID.
GetOrganization(ctx context.Context, id string) (Organization, error)
// GetOrganizationBySlug retrieves an organization by slug.
// Used for URL routing like /org/acme-corp.
GetOrganizationBySlug(ctx context.Context, slug string) (Organization, error)
// UpdateOrganization updates organization name and/or slug.
UpdateOrganization(ctx context.Context, id, name, slug string, updatedAt time.Time) error
// DeleteOrganization deletes an organization.
// Should cascade delete members, teams, and team members.
DeleteOrganization(ctx context.Context, id string, updatedAt time.Time) error
// ListUserOrganizations retrieves all organizations a user is a member of.
ListUserOrganizations(ctx context.Context, userID string) ([]Organization, error)
// CreateMember adds a user to an organization with a role.
//
// Valid roles: "owner", "admin", "member"
CreateMember(ctx context.Context, id, userID, orgID, role string, createdAt, updatedAt time.Time) error
// GetMember retrieves a user's membership in an organization.
GetMember(ctx context.Context, userID, orgID string) (Member, error)
// IsOrganizationMember checks if a user is a member (any role).
// Used by middleware for access control.
IsOrganizationMember(ctx context.Context, userID, orgID string) (bool, error)
// IsOwnerOrAdmin checks if a user has admin privileges.
// Returns true if role is "owner" or "admin".
IsOwnerOrAdmin(ctx context.Context, userID, orgID string) (bool, error)
// IsOwner checks if a user is the organization owner.
// Returns true only if role is "owner".
IsOwner(ctx context.Context, userID, orgID string) (bool, error)
// UpdateMemberRole changes a member's role.
// Caller should verify permissions before calling.
UpdateMemberRole(ctx context.Context, userID, orgID, role string, updatedAt time.Time) error
// RemoveMember removes a user from an organization.
// Should also remove from all teams in the organization.
RemoveMember(ctx context.Context, userID, orgID string) error
// ListOrganizationMembers retrieves all members of an organization.
ListOrganizationMembers(ctx context.Context, orgID string) ([]Member, error)
// CreateTeam creates a new team within an organization.
CreateTeam(ctx context.Context, id, orgID, name, description string, createdAt, updatedAt time.Time) error
// GetTeam retrieves a team by ID.
GetTeam(ctx context.Context, id string) (Team, error)
// ListTeams retrieves all teams in an organization.
ListTeams(ctx context.Context, orgID string) ([]Team, error)
// UpdateTeam updates team name and/or description.
UpdateTeam(ctx context.Context, id, name, description string, updatedAt time.Time) error
// DeleteTeam deletes a team.
// Should cascade delete team members.
DeleteTeam(ctx context.Context, id string) error
// CreateTeamMember adds a user to a team with a role.
//
// Valid roles: "lead", "member"
// User must be an organization member.
CreateTeamMember(ctx context.Context, id, teamID, userID, role string, createdAt, updatedAt time.Time) error
// GetTeamMember retrieves a user's team membership.
GetTeamMember(ctx context.Context, teamID, userID string) (TeamMember, error)
// ListTeamMembers retrieves all members of a team.
ListTeamMembers(ctx context.Context, teamID string) ([]TeamMember, error)
// UpdateTeamMemberRole changes a team member's role.
UpdateTeamMemberRole(ctx context.Context, teamID, userID, role string, updatedAt time.Time) error
// RemoveTeamMember removes a user from a team.
RemoveTeamMember(ctx context.Context, teamID, userID string) error
}
OrganizationStore defines the interface for organization storage operations.
This interface abstracts database operations for multi-tenant organization management, including organizations, members, teams, and team members.
Thread Safety: Implementations must be safe for concurrent use from multiple goroutines.
Transaction Considerations: Several operations should be atomic (e.g., CreateOrganization + CreateMember). Implementations should handle this appropriately.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin implements multi-tenant organization and team management.
This plugin provides complete CRUD operations for organizations, members, teams, and team members with role-based access control.
Components:
- sessionService: User authentication for protected routes
- store: Database persistence for organizations, members, teams
- dialect: SQL dialect (PostgreSQL, MySQL, SQLite)
Endpoints Provided:
Organizations: POST, GET, PUT, DELETE /organizations Members: POST, GET, PATCH, DELETE /organizations/:id/members Teams: POST, GET, PUT, DELETE /teams, /organizations/:id/teams Team Members: POST, GET, PATCH, DELETE /teams/:teamId/members
func New ¶
func New(store OrganizationStore, dialect ...plugins.Dialect) *Plugin
New creates a new organizations plugin for multi-tenancy management.
Parameters:
- store: Organization storage implementation (nil = use DefaultOrganizationStore)
- dialect: Database dialect (defaults to PostgreSQL)
Returns:
- *Plugin: Initialized plugin ready for Init() call
Example:
plugin := organizations.New(nil, plugins.DialectPostgres)
func (*Plugin) AddOrganizationMemberHandler ¶
func (p *Plugin) AddOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
AddOrganizationMemberHandler adds a member to an organization
func (*Plugin) AddTeamMemberHandler ¶
func (p *Plugin) AddTeamMemberHandler(w http.ResponseWriter, r *http.Request)
AddTeamMemberHandler adds a member to a team
func (*Plugin) CreateOrganizationHandler ¶
func (p *Plugin) CreateOrganizationHandler(w http.ResponseWriter, r *http.Request)
CreateOrganizationHandler creates a new organization with the user as owner.
This endpoint allows any authenticated user to create an organization. The creator is automatically assigned the "owner" role with full administrative privileges.
Endpoint:
- Method: POST
- Path: /organizations
- Auth: Required (any authenticated user)
Request Body:
{
"name": "Acme Corporation",
"slug": "acme-corp"
}
Validation:
- name: Required, 1-100 characters
- slug: Required, 3-50 characters, lowercase alphanumeric with hyphens only
Response (201 Created):
{
"success": true,
"organization": {
"id": "org_abc123",
"name": "Acme Corporation",
"slug": "acme-corp",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}
func (*Plugin) CreateTeamHandler ¶
func (p *Plugin) CreateTeamHandler(w http.ResponseWriter, r *http.Request)
CreateTeamHandler creates a new team within an organization
func (*Plugin) DeleteOrganizationHandler ¶
func (p *Plugin) DeleteOrganizationHandler(w http.ResponseWriter, r *http.Request)
DeleteOrganizationHandler deletes an organization
func (*Plugin) DeleteTeamHandler ¶
func (p *Plugin) DeleteTeamHandler(w http.ResponseWriter, r *http.Request)
DeleteTeamHandler deletes a team
func (*Plugin) Dependencies ¶
func (p *Plugin) Dependencies() []plugins.Dependency
Dependencies returns plugin dependencies
func (*Plugin) Description ¶
Description returns the plugin description
func (*Plugin) GetMigrations ¶
GetMigrations returns the plugin migrations
func (*Plugin) GetOrganizationHandler ¶
func (p *Plugin) GetOrganizationHandler(w http.ResponseWriter, r *http.Request)
GetOrganizationHandler retrieves details of a specific organization.
This endpoint returns organization metadata. Requires membership in the organization.
Endpoint:
- Method: GET
- Path: /organizations/:id
- Auth: Required (must be organization member)
Path Parameters:
- id: Organization ID
Response (200 OK):
{
"success": true,
"organization": {
"id": "org_abc123",
"name": "Acme Corporation",
"slug": "acme-corp",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
}
func (*Plugin) GetSchemas ¶
GetSchemas returns all schemas for all supported dialects
func (*Plugin) GetTeamHandler ¶
func (p *Plugin) GetTeamHandler(w http.ResponseWriter, r *http.Request)
GetTeamHandler gets a specific team
func (*Plugin) Init ¶
Init initializes the organizations plugin with Aegis services.
This method validates database schema requirements and stores the session service for authentication middleware.
Initialization Steps:
- Initialize store if not provided (DefaultOrganizationStore)
- Build schema validation requirements (tables, foreign keys)
- Validate schema via Aegis
- Store session service for protected routes
Required Tables:
- organization: Organization metadata
- members: Organization membership with roles
- team: Team metadata within organizations
- team_member: Team membership with roles
Parameters:
- ctx: Initialization context
- aegis: Aegis interface providing services and DB
Returns:
- error: Schema validation error if tables don't exist
func (*Plugin) ListOrganizationMembersHandler ¶
func (p *Plugin) ListOrganizationMembersHandler(w http.ResponseWriter, r *http.Request)
ListOrganizationMembersHandler lists organization members.
func (*Plugin) ListOrganizationsHandler ¶
func (p *Plugin) ListOrganizationsHandler(w http.ResponseWriter, r *http.Request)
ListOrganizationsHandler lists all organizations the user is a member of.
This endpoint returns all organizations where the user has any membership (owner, admin, or member role).
Endpoint:
- Method: GET
- Path: /organizations
- Auth: Required
Response (200 OK):
{
"success": true,
"organizations": [
{"id": "org_1", "name": "Acme Corp", "slug": "acme", ...},
{"id": "org_2", "name": "Tech Inc", "slug": "tech", ...}
]
}
func (*Plugin) ListTeamMembersHandler ¶
func (p *Plugin) ListTeamMembersHandler(w http.ResponseWriter, r *http.Request)
ListTeamMembersHandler lists team members
func (*Plugin) ListTeamsHandler ¶
func (p *Plugin) ListTeamsHandler(w http.ResponseWriter, r *http.Request)
ListTeamsHandler lists teams in an organization.
func (*Plugin) MountRoutes ¶
MountRoutes registers HTTP routes for the organizations plugin
func (*Plugin) ProvidesAuthMethods ¶
ProvidesAuthMethods returns the provided auth methods
func (*Plugin) RemoveOrganizationMemberHandler ¶
func (p *Plugin) RemoveOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
RemoveOrganizationMemberHandler removes a member from an organization
func (*Plugin) RemoveTeamMemberHandler ¶
func (p *Plugin) RemoveTeamMemberHandler(w http.ResponseWriter, r *http.Request)
RemoveTeamMemberHandler removes a member from a team
func (*Plugin) RequireOrganizationAdminMiddleware ¶
RequireOrganizationAdminMiddleware enforces admin or owner privileges.
This middleware ensures the authenticated user has administrative privileges (owner or admin role) in the organization. Regular members are denied access.
Use Cases:
- Adding/removing organization members
- Creating/deleting teams
- Updating organization settings
Permission Requirements:
- Owner role: Allowed ✓
- Admin role: Allowed ✓
- Member role: Denied ✗
Request Flow:
- Get authenticated user from context
- Extract organization ID from path parameter ":id"
- Check if user has owner OR admin role
- If yes → continue, if no → 403 Forbidden
func (*Plugin) RequireOrganizationMemberMiddleware ¶
RequireOrganizationMemberMiddleware enforces organization membership.
This middleware ensures the authenticated user is a member of the organization specified in the URL path parameter ":id". It allows any role (owner, admin, member).
Use Cases:
- Viewing organization details
- Listing organization members
- Viewing teams
Request Flow:
- Get authenticated user from context (set by RequireAuthMiddleware)
- Extract organization ID from path parameter ":id"
- Check if user is a member (any role)
- If yes → continue to handler, if no → 403 Forbidden
Example:
r.GET("/organizations/:id",
requireAuth(
plugin.RequireOrganizationMemberMiddleware()(
http.HandlerFunc(handler),
),
),
)
func (*Plugin) RequireOrganizationOwnerMiddleware ¶
RequireOrganizationOwnerMiddleware enforces owner-only access.
This middleware ensures the authenticated user is the organization owner. This is the highest privilege level and is required for destructive operations.
Use Cases:
- Deleting organization (permanent)
- Transferring ownership
- Changing other members' roles to admin
Permission Requirements:
- Owner role: Allowed ✓
- Admin role: Denied ✗
- Member role: Denied ✗
Request Flow:
- Get authenticated user from context
- Extract organization ID from path parameter ":id"
- Check if user has owner role (exact match)
- If yes → continue, if no → 403 Forbidden
Best Practice: Only one owner per organization is recommended. Multiple owners complicate permission management and deletion workflows.
func (*Plugin) RequiresTables ¶
RequiresTables returns required tables
func (*Plugin) UpdateMemberRoleHandler ¶
func (p *Plugin) UpdateMemberRoleHandler(w http.ResponseWriter, r *http.Request)
UpdateMemberRoleHandler updates a member's role
func (*Plugin) UpdateOrganizationHandler ¶
func (p *Plugin) UpdateOrganizationHandler(w http.ResponseWriter, r *http.Request)
UpdateOrganizationHandler updates an organization
func (*Plugin) UpdateTeamHandler ¶
func (p *Plugin) UpdateTeamHandler(w http.ResponseWriter, r *http.Request)
UpdateTeamHandler updates a team
func (*Plugin) UpdateTeamMemberRoleHandler ¶
func (p *Plugin) UpdateTeamMemberRoleHandler(w http.ResponseWriter, r *http.Request)
UpdateTeamMemberRoleHandler updates a team member's role
type Team ¶
type Team struct {
ID string `json:"id"` // Unique team identifier
OrganizationID string `json:"organizationId"` // Parent organization ID
Name string `json:"name"` // Team display name
Description string `json:"description"` // Team purpose/description
CreatedAt time.Time `json:"createdAt"` // When the team was created
UpdatedAt time.Time `json:"updatedAt"` // Last update timestamp
}
Team represents a group within an organization.
Teams provide hierarchical organization within a tenant, allowing for department-level or project-level access control. Teams belong to a single organization and can have members with team-specific roles.
Database Table: team Foreign Key: organization_id → organization.id
Use Cases:
- Department teams ("Engineering", "Sales", "Marketing")
- Project teams ("Product Launch", "Q4 Initiative")
- Access control groups ("Beta Testers", "Premium Features")
Example:
{
"id": "team_def456",
"organizationId": "org_abc123",
"name": "Engineering",
"description": "Software development team",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
type TeamMember ¶
type TeamMember struct {
ID string `json:"id"` // Unique team membership identifier
TeamID string `json:"teamId"` // Team ID (foreign key)
UserID string `json:"userId"` // User ID (foreign key)
Role string `json:"role"` // Team role ("lead", "member")
CreatedAt time.Time `json:"createdAt"` // When the user joined the team
UpdatedAt time.Time `json:"updatedAt"` // Last role update timestamp
}
TeamMember represents a user's membership in a team with a role.
Team members must also be members of the parent organization. Teams provide an additional layer of access control within an organization.
Database Table: team_member Unique Constraint: (team_id, user_id) Foreign Keys: team_id → team.id, user_id → auth.users.id
Roles:
- "lead": Can manage team members and settings
- "member": Participate in team activities
Example:
{
"id": "tmem_ghi789",
"teamId": "team_def456",
"userId": "user_123",
"role": "lead",
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
type UpdateMemberRoleRequest ¶
type UpdateMemberRoleRequest struct {
Role string `json:"role"` // New role ("admin" or "member")
}
UpdateMemberRoleRequest represents a request to update a member's role.
Validation Rules:
- role: Required, must be "admin" or "member"
Example:
{
"role": "admin"
}
Security Note: Cannot update to "owner" role via this endpoint. Ownership transfer requires a separate flow with additional safeguards.
func (UpdateMemberRoleRequest) Validate ¶
func (r UpdateMemberRoleRequest) Validate() error
Validate validates the update member role request.
type UpdateOrganizationRequest ¶
type UpdateOrganizationRequest struct {
Name string `json:"name"` // Updated organization name
Slug string `json:"slug"` // Updated URL-friendly identifier
}
UpdateOrganizationRequest represents a request to update an organization.
Validation Rules:
- name: Required, 1-100 characters
- slug: Required, 3-50 characters, lowercase alphanumeric + hyphens only
Note: Both fields must be provided even if only updating one. The handler will apply the new values.
func (UpdateOrganizationRequest) Validate ¶
func (r UpdateOrganizationRequest) Validate() error
Validate validates the update organization request.
type UpdateTeamMemberRoleRequest ¶
type UpdateTeamMemberRoleRequest struct {
Role string `json:"role"`
}
UpdateTeamMemberRoleRequest represents a request to update a team member's role.
func (UpdateTeamMemberRoleRequest) Validate ¶
func (r UpdateTeamMemberRoleRequest) Validate() error
Validate validates the update team member role request.
type UpdateTeamRequest ¶
UpdateTeamRequest represents a request to update a team.
func (UpdateTeamRequest) Validate ¶
func (r UpdateTeamRequest) Validate() error
Validate validates the update team request.