481359ddb2
Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
package backendapp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/kandev/kandev/internal/common/config"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
"github.com/kandev/kandev/internal/db"
|
|
"github.com/kandev/kandev/internal/org"
|
|
userstore "github.com/kandev/kandev/internal/user/store"
|
|
)
|
|
|
|
// orgAccountAdapter exposes the account operations the organization service
|
|
// needs without making internal/user depend on internal/org.
|
|
type orgAccountAdapter struct {
|
|
accounts userstore.AccountRepository
|
|
}
|
|
|
|
func (a orgAccountAdapter) AssignUsersWithoutOrg(ctx context.Context, orgID string) (int64, error) {
|
|
return a.tenancy().AssignUsersWithoutOrg(ctx, orgID)
|
|
}
|
|
|
|
func (a orgAccountAdapter) SetOperator(ctx context.Context, id string, operator bool) error {
|
|
return a.tenancy().SetOperator(ctx, id, operator)
|
|
}
|
|
|
|
func (a orgAccountAdapter) CountOperators(ctx context.Context) (int, error) {
|
|
return a.tenancy().CountOperators(ctx)
|
|
}
|
|
|
|
func (a orgAccountAdapter) FirstAdminID(ctx context.Context) (string, error) {
|
|
return a.tenancy().FirstAdminID(ctx)
|
|
}
|
|
|
|
func (a orgAccountAdapter) DeleteUsersByOrg(ctx context.Context, orgID string) error {
|
|
return a.tenancy().DeleteUsersByOrg(ctx, orgID)
|
|
}
|
|
|
|
// tenancy narrows the account repository to its tenancy operations. The
|
|
// concrete SQLite repository implements them; the interface assertion keeps
|
|
// existing AccountRepository fakes unaffected.
|
|
func (a orgAccountAdapter) tenancy() userstore.TenancyRepository {
|
|
if t, ok := a.accounts.(userstore.TenancyRepository); ok {
|
|
return t
|
|
}
|
|
return userstore.NoopTenancy{}
|
|
}
|
|
|
|
var _ org.AccountMigrator = orgAccountAdapter{}
|
|
|
|
type orgMigrationData interface {
|
|
AssignWorkspacesWithoutOrg(ctx context.Context, orgID string) (int64, error)
|
|
DropCrossOrgWorkspaceMembers(ctx context.Context) (int64, error)
|
|
}
|
|
|
|
type orgWorkspaceLifecycle interface {
|
|
DeleteOrganizationWorkspaces(ctx context.Context, orgID string) error
|
|
}
|
|
|
|
// orgDataAdapter keeps migration SQL in the repository while routing
|
|
// destructive workspace removal through the task service's full lifecycle.
|
|
type orgDataAdapter struct {
|
|
migrations orgMigrationData
|
|
workspaces orgWorkspaceLifecycle
|
|
}
|
|
|
|
func (a orgDataAdapter) AssignWorkspacesWithoutOrg(ctx context.Context, orgID string) (int64, error) {
|
|
return a.migrations.AssignWorkspacesWithoutOrg(ctx, orgID)
|
|
}
|
|
|
|
func (a orgDataAdapter) DropCrossOrgWorkspaceMembers(ctx context.Context) (int64, error) {
|
|
return a.migrations.DropCrossOrgWorkspaceMembers(ctx)
|
|
}
|
|
|
|
func (a orgDataAdapter) DeleteOrgData(ctx context.Context, orgID string) error {
|
|
if a.workspaces == nil {
|
|
return errors.New("workspace deletion lifecycle is unavailable")
|
|
}
|
|
return a.workspaces.DeleteOrganizationWorkspaces(ctx, orgID)
|
|
}
|
|
|
|
var _ org.DataMigrator = orgDataAdapter{}
|
|
|
|
// buildOrgService constructs the organization service. It is built even when
|
|
// organizations are off so callers never branch on nil; the service itself
|
|
// reports Enabled() false and every lifecycle operation is a no-op.
|
|
func buildOrgService(
|
|
cfg *config.Config,
|
|
pool *db.Pool,
|
|
repos *Repositories,
|
|
workspaces orgWorkspaceLifecycle,
|
|
log *logger.Logger,
|
|
) (*org.Service, error) {
|
|
store, err := org.NewStore(pool)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return org.NewService(
|
|
store,
|
|
orgAccountAdapter{accounts: repos.UserAccounts},
|
|
orgDataAdapter{migrations: repos.Task, workspaces: workspaces},
|
|
cfg.Features.MultiTenancy,
|
|
log,
|
|
), nil
|
|
}
|