Files
kandev/apps/backend/internal/backendapp/orgunits.go
T
João Salavisa 481359ddb2 feat(auth): organization units, tenancy, roles and scopes for team access (#2919)
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 13:40:43 +00:00

79 lines
2.3 KiB
Go

package backendapp
import (
"context"
"fmt"
"github.com/kandev/kandev/internal/common/logger"
"github.com/kandev/kandev/internal/db"
"github.com/kandev/kandev/internal/orgunit"
tasksqlite "github.com/kandev/kandev/internal/task/repository/sqlite"
"github.com/kandev/kandev/internal/user/store"
)
// buildOrgUnitService creates the unit tree, wires its occupancy seam, and runs
// the one-shot placement so no workspace is left without a home.
//
// It runs after the tenancy migration, which is what assigns organization ids:
// placing a workspace before it knows its organization would put it under the
// wrong root.
func buildOrgUnitService(
pool *db.Pool,
taskRepo *tasksqlite.Repository,
accounts store.AccountRepository,
log *logger.Logger,
) (*orgunit.Service, error) {
unitStore, err := orgunit.NewStore(pool)
if err != nil {
return nil, err
}
svc := orgunit.NewService(unitStore, log)
svc.SetWorkspaceCounter(taskRepo)
if _, err := svc.Backfill(
context.Background(),
unitAccountAdapter{accounts: accounts},
unitPlacementAdapter{repo: taskRepo},
); err != nil {
return nil, fmt.Errorf("organization unit backfill: %w", err)
}
return svc, nil
}
// unitAccountAdapter narrows the user repository to what a personal unit needs.
type unitAccountAdapter struct{ accounts store.AccountRepository }
func (a unitAccountAdapter) ListUnitUsers(ctx context.Context) ([]orgunit.UserRef, error) {
users, err := a.accounts.ListUsers(ctx)
if err != nil {
return nil, err
}
out := make([]orgunit.UserRef, 0, len(users))
for _, u := range users {
if u == nil {
continue
}
out = append(out, orgunit.UserRef{ID: u.ID, OrgID: u.OrgID, DisplayName: u.DisplayName})
}
return out, nil
}
// unitPlacementAdapter narrows the task repository to workspace placement.
type unitPlacementAdapter struct{ repo *tasksqlite.Repository }
func (a unitPlacementAdapter) UnplacedWorkspaces(ctx context.Context) ([]orgunit.WorkspaceRef, error) {
rows, err := a.repo.UnplacedWorkspaces(ctx)
if err != nil {
return nil, err
}
out := make([]orgunit.WorkspaceRef, 0, len(rows))
for _, r := range rows {
out = append(out, orgunit.WorkspaceRef{ID: r.ID, OrgID: r.OrgID, OwnerID: r.OwnerID})
}
return out, nil
}
func (a unitPlacementAdapter) PlaceWorkspace(ctx context.Context, workspaceID, unitID string) error {
return a.repo.PlaceWorkspace(ctx, workspaceID, unitID)
}