1
0
forked from wrenn/wrenn
This commit is contained in:
2026-04-16 19:24:25 +00:00
parent 172413e91e
commit 605ad666a0
239 changed files with 19966 additions and 3454 deletions

107
pkg/service/user.go Normal file
View File

@ -0,0 +1,107 @@
package service
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/jackc/pgx/v5/pgtype"
"git.omukk.dev/wrenn/wrenn/pkg/db"
)
// UserService provides user management operations.
type UserService struct {
DB *db.Queries
SandboxSvc *SandboxService
}
// AdminUserRow is the shape returned by AdminListUsers.
type AdminUserRow struct {
ID pgtype.UUID
Email string
Name string
IsAdmin bool
Status string
CreatedAt time.Time
TeamsJoined int32
TeamsOwned int32
}
// AdminListUsers returns a paginated list of all non-deleted users with team counts.
func (s *UserService) AdminListUsers(ctx context.Context, limit, offset int32) ([]AdminUserRow, int32, error) {
users, err := s.DB.ListUsersAdmin(ctx, db.ListUsersAdminParams{
Limit: limit,
Offset: offset,
})
if err != nil {
return nil, 0, fmt.Errorf("list users: %w", err)
}
total, err := s.DB.CountUsersAdmin(ctx)
if err != nil {
return nil, 0, fmt.Errorf("count users: %w", err)
}
rows := make([]AdminUserRow, len(users))
for i, u := range users {
rows[i] = AdminUserRow{
ID: u.ID,
Email: u.Email,
Name: u.Name,
IsAdmin: u.IsAdmin,
Status: u.Status,
CreatedAt: u.CreatedAt.Time,
TeamsJoined: u.TeamsJoined,
TeamsOwned: u.TeamsOwned,
}
}
return rows, total, nil
}
// SetUserStatus sets the status of a user account.
func (s *UserService) SetUserStatus(ctx context.Context, userID pgtype.UUID, status string) error {
if err := s.DB.SetUserStatus(ctx, db.SetUserStatusParams{
ID: userID,
Status: status,
}); err != nil {
return fmt.Errorf("set user status: %w", err)
}
if status == "disabled" || status == "deleted" {
if err := s.DB.DeleteAPIKeysByCreator(ctx, userID); err != nil {
slog.Warn("failed to delete API keys for deactivated user", "user_id", userID, "error", err)
}
s.destroySandboxesForOwnedTeams(ctx, userID)
}
return nil
}
// destroySandboxesForOwnedTeams destroys all active sandboxes (running, paused,
// hibernated, starting) for every team the user owns. Best-effort: errors are
// logged but do not prevent the user from being disabled.
func (s *UserService) destroySandboxesForOwnedTeams(ctx context.Context, userID pgtype.UUID) {
if s.SandboxSvc == nil {
return
}
teamIDs, err := s.DB.GetOwnedTeamIDs(ctx, userID)
if err != nil {
slog.Warn("failed to list owned teams for sandbox cleanup", "user_id", userID, "error", err)
return
}
for _, teamID := range teamIDs {
sandboxes, err := s.DB.ListActiveSandboxesByTeam(ctx, teamID)
if err != nil {
slog.Warn("failed to list active sandboxes for team", "team_id", teamID, "user_id", userID, "error", err)
continue
}
for _, sb := range sandboxes {
if err := s.SandboxSvc.Destroy(ctx, sb.ID, teamID); err != nil {
slog.Warn("failed to destroy sandbox during user disable",
"sandbox_id", sb.ID, "team_id", teamID, "user_id", userID, "error", err)
}
}
}
}