app

package
v0.10.3 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DefaultPageSize = 50
	MaxPageSize     = 200
)

Variables

View Source
var ErrDuplicate = errors.New("duplicate")
View Source
var ErrLastOwner = errors.New("last active owner protected")
View Source
var ErrNotFound = errors.New("not found")

Functions

This section is empty.

Types

type Auditor

type Auditor interface {
	Record(ctx context.Context, workspaceID *string, p *webx.Principal, action, resourceType, resourceID string, meta map[string]any)
}

type Clock

type Clock func() time.Time

type EmailSender

type EmailSender interface {
	SendInvite(ctx context.Context, to, workspaceName, invitationID string) error
}

type Invitation

type Invitation struct {
	ID          string    `json:"id"`
	WorkspaceID string    `json:"workspace_id"`
	Email       string    `json:"email"`
	Role        string    `json:"role"`
	Status      string    `json:"status"`
	ExpiresAt   time.Time `json:"expires_at"`
	CreatedAt   time.Time `json:"created_at"`
}

type InvitationWithWorkspace

type InvitationWithWorkspace struct {
	Invitation
	WorkspaceName string `json:"workspace_name"`
	WorkspaceSlug string `json:"workspace_slug"`
}

type Member

type Member struct {
	UserID      string    `json:"user_id"`
	Email       string    `json:"email"`
	DisplayName string    `json:"display_name"`
	Role        string    `json:"role"`
	CreatedAt   time.Time `json:"created_at"`
}

type Page

type Page[T any] struct {
	Items []T `json:"items"`
	Total int `json:"total"`
}

type PoolDeps

type PoolDeps struct {
	Pool *pgxpool.Pool
}

type Repo

type Repo interface {
	CreateWorkspaceWithOwner(ctx context.Context, slug, name, ownerUserID string, now time.Time) (Workspace, error)
	ListWorkspacesByUser(ctx context.Context, userID string) ([]WorkspaceMembership, error)
	GetWorkspace(ctx context.Context, id string) (Workspace, bool, error)
	CountWorkspacesByUser(ctx context.Context, userID string) (int, error)
	GetMember(ctx context.Context, workspaceID, userID string) (Member, bool, error)
	IsMemberByEmail(ctx context.Context, workspaceID, email string) (bool, error)
	ListMembers(ctx context.Context, workspaceID string, page, pageSize int) (Page[Member], error)
	CountActiveOwners(ctx context.Context, workspaceID string) (int, error)

	UpdateMemberRole(ctx context.Context, workspaceID, userID, newRole string, now time.Time) error

	SoftRemoveMember(ctx context.Context, workspaceID, userID, removedBy string, now time.Time) error

	CreateInvitation(ctx context.Context, workspaceID, email, role, createdBy string, expiresAt time.Time) (Invitation, error)
	ListInvitations(ctx context.Context, workspaceID string, page, pageSize int) (Page[Invitation], error)
	RevokeInvitation(ctx context.Context, workspaceID, invitationID string, now time.Time) error
	HasPendingInvitation(ctx context.Context, workspaceID, email string) (bool, error)
	PendingInvitationsByEmail(ctx context.Context, email string) ([]InvitationWithWorkspace, error)
	AcceptInvitation(ctx context.Context, invitationID, userID, email string, now time.Time) (Workspace, error)
	DeclineInvitation(ctx context.Context, invitationID, email string, now time.Time) error

	CreateShareLink(ctx context.Context, workspaceID, codeHash, codePrefix, role, createdBy string, maxUses int, expiresAt time.Time) (ShareLink, error)
	ListShareLinks(ctx context.Context, workspaceID string, page, pageSize int) (Page[ShareLink], error)
	RevokeShareLink(ctx context.Context, workspaceID, linkID string, now time.Time) error
	RedeemShareLink(ctx context.Context, codeHash, userID string, now time.Time) (Workspace, error)

	RunInTx(ctx context.Context, fn func(ctx context.Context, tx pgx.Tx) error) error

	CountWorkspacesByUserForCreateTx(ctx context.Context, tx pgx.Tx, userID string) (int, error)

	CreateWorkspaceWithOwnerTx(ctx context.Context, tx pgx.Tx, slug, name, ownerUserID string, now time.Time) (Workspace, error)

	// CreatePersonalWorkspaceTx inserts the ADR 0016 personal workspace —
	// kind='personal', name 'Personal', the plan's default (free) — plus the
	// owner membership, on the caller's transaction. The partial unique index
	// uq_workspace_personal(created_by) makes a second insert for the same
	// owner fail with ErrDuplicate: provisioning callers treat that as
	// "already provisioned".
	CreatePersonalWorkspaceTx(ctx context.Context, tx pgx.Tx, slug, ownerUserID string, now time.Time) (Workspace, error)

	AcceptInvitationTx(ctx context.Context, tx pgx.Tx, invitationID, userID, email string, now time.Time) (Workspace, error)
	RedeemShareLinkTx(ctx context.Context, tx pgx.Tx, codeHash, userID string, now time.Time) (Workspace, error)

	UpdateWorkspaceName(ctx context.Context, id, name string, now time.Time) error

	DeleteWorkspaceCascade(ctx context.Context, tx pgx.Tx, workspaceID string) error

	GetMemberUserStatus(ctx context.Context, workspaceID, userID string) (Member, string, bool, error)

	OldestActiveOwner(ctx context.Context, workspaceID string) (Member, bool, error)

	TransferOwnershipTx(ctx context.Context, tx pgx.Tx, workspaceID, fromUserID, toUserID string) error

	// Role-config write side (read side lives in authz's Provider). An
	// override row REPLACES the role's built-in permission set entirely —
	// not a merge.
	ListRoleOverrides(ctx context.Context, workspaceID string) (map[string][]authz.Permission, error)
	UpsertRolePerms(ctx context.Context, workspaceID, role string, perms []authz.Permission, now time.Time) error
	DeleteRolePerms(ctx context.Context, workspaceID, role string) error
}

type RoleConfig added in v0.4.0

type RoleConfig struct {
	Role       string             `json:"role"`
	Perms      []authz.Permission `json:"perms"`
	Overridden bool               `json:"overridden"`
	Defaults   []authz.Permission `json:"defaults"`
}

RoleConfig is one row of the workspace role-permission matrix: the role's effective permission set plus whether it comes from an override row (an override REPLACES the built-in set — not a merge) and what the built-in default is.

type ShareLink struct {
	ID         string    `json:"id"`
	CodePrefix string    `json:"code_prefix"`
	Role       string    `json:"role"`
	MaxUses    int       `json:"max_uses"`
	Uses       int       `json:"uses"`
	ExpiresAt  time.Time `json:"expires_at"`
	CreatedAt  time.Time `json:"created_at"`
}

type Workspace

type Workspace struct {
	ID        string    `json:"id"`
	Slug      string    `json:"slug"`
	Name      string    `json:"name"`
	PlanCode  string    `json:"plan_code"`
	CreatedAt time.Time `json:"created_at"`
}

type WorkspaceConfig

type WorkspaceConfig struct {
	MaxPerUser int
}

WorkspaceConfig configures the workspace service. The zero value is safe: MaxPerUser <= 0 (including the unset zero value) means unlimited workspaces per user; only an explicit positive value caps creation (orkome F10 — the old "0 blocks everything, -1 is unlimited" reading violated Go zero-value conventions and disguised itself as E_QUOTA_EXCEEDED).

type WorkspaceMembership

type WorkspaceMembership struct {
	Workspace
	Role string `json:"role"`
}

type WorkspaceService

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

func NewWorkspaceService

func NewWorkspaceService(repo Repo, az *authz.Authorizer, mail EmailSender, auditor Auditor, cfg WorkspaceConfig, now Clock) *WorkspaceService

func (*WorkspaceService) AcceptInvitation

func (s *WorkspaceService) AcceptInvitation(ctx context.Context, p *webx.Principal, invID string) (Workspace, error)

func (*WorkspaceService) Create

func (s *WorkspaceService) Create(ctx context.Context, p *webx.Principal, slug, name string) (Workspace, error)
func (s *WorkspaceService) CreateShareLink(ctx context.Context, actor *webx.Principal, wsID, role string, maxUses int, ttl time.Duration) (ShareLink, string, error)

func (*WorkspaceService) DeclineInvitation

func (s *WorkspaceService) DeclineInvitation(ctx context.Context, p *webx.Principal, invID string) error

func (*WorkspaceService) DeleteWorkspace

func (s *WorkspaceService) DeleteWorkspace(ctx context.Context, p *webx.Principal, workspaceID string) error

func (*WorkspaceService) EffectivePerms added in v0.9.2

func (s *WorkspaceService) EffectivePerms(ctx context.Context, wsID, role string) ([]authz.Permission, error)

EffectivePerms returns the given role's effective permission set in a workspace: owner carries the full registered catalog (owner permissions are immutable by design); admin/member use their override row when one exists, otherwise their built-in defaults. Used by the HTTP layer to attach role_perms to workspace responses.

func (*WorkspaceService) Invite

func (s *WorkspaceService) Invite(ctx context.Context, actor *webx.Principal, wsID, rawEmail, role string) (Invitation, error)

func (*WorkspaceService) Leave

func (s *WorkspaceService) Leave(ctx context.Context, p *webx.Principal, wsID string) error

func (*WorkspaceService) ListInvitations

func (s *WorkspaceService) ListInvitations(ctx context.Context, _ *webx.Principal, wsID string, page, pageSize int) (Page[Invitation], error)

func (*WorkspaceService) ListMembers

func (s *WorkspaceService) ListMembers(ctx context.Context, wsID string, page, pageSize int) (Page[Member], error)

func (*WorkspaceService) ListMine

func (*WorkspaceService) ListRoleConfigs added in v0.4.0

func (s *WorkspaceService) ListRoleConfigs(ctx context.Context, actor *webx.Principal, wsID string) ([]RoleConfig, []authz.Permission, error)

ListRoleConfigs returns the full matrix (defaults, overrides, effective sets) plus the permission vocabulary for the management UI.

func (s *WorkspaceService) ListShareLinks(ctx context.Context, _ *webx.Principal, wsID string, page, pageSize int) (Page[ShareLink], error)

func (*WorkspaceService) MyInvitations

func (s *WorkspaceService) RedeemShareLink(ctx context.Context, p *webx.Principal, code string) (Workspace, error)

func (*WorkspaceService) RemoveMember

func (s *WorkspaceService) RemoveMember(ctx context.Context, actor *webx.Principal, wsID, targetUserID string) error

func (*WorkspaceService) RenameWorkspace

func (s *WorkspaceService) RenameWorkspace(ctx context.Context, p *webx.Principal, workspaceID, name string) (Workspace, error)

func (*WorkspaceService) ResetRolePerms added in v0.4.0

func (s *WorkspaceService) ResetRolePerms(ctx context.Context, actor *webx.Principal, wsID, role string) error

ResetRolePerms removes the override row so the role falls back to its built-in set. Idempotent: resetting a role without an override succeeds.

func (*WorkspaceService) RevokeInvitation

func (s *WorkspaceService) RevokeInvitation(ctx context.Context, actor *webx.Principal, wsID, invID string) error
func (s *WorkspaceService) RevokeShareLink(ctx context.Context, actor *webx.Principal, wsID, linkID string) error

func (*WorkspaceService) SetRolePerms added in v0.4.0

func (s *WorkspaceService) SetRolePerms(ctx context.Context, actor *webx.Principal, wsID, role string, perms []authz.Permission) (*RoleConfig, error)

SetRolePerms writes a role override. The write boundary IS the drift guard (ADR 0012): every permission must exist in the code-registered catalog — UI-edited data cannot drift from the vocabulary because unknown permissions are rejected fail-closed at write time.

func (*WorkspaceService) TransferOwnership

func (s *WorkspaceService) TransferOwnership(ctx context.Context, actor *webx.Principal, wsID, newOwnerUserID string) error

func (*WorkspaceService) UpdateMemberRole

func (s *WorkspaceService) UpdateMemberRole(ctx context.Context, actor *webx.Principal, wsID, targetUserID, newRole string) error

func (*WorkspaceService) WithHooks

Jump to

Keyboard shortcuts

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