feat(users): admin promote/demote endpoints

POST /users/{id}/promote and /demote set a user's role (admin/user), admin-only
(403 otherwise). Mirrors the verify endpoints: SetRole use case + repo method,
validated role. Unit, admin-flow, and integration tests included.
This commit is contained in:
Efremenko Arhip
2026-07-26 21:14:32 +03:00
parent 49eb662798
commit 9a458ec4ef
13 changed files with 167 additions and 0 deletions
@@ -145,3 +145,21 @@ func TestUserRepoSetVerifiedUnknown(t *testing.T) {
t.Errorf("got %v, want ErrUserNotFound", err)
}
}
func TestUserRepoSetRole(t *testing.T) {
repo := NewUserRepo(testPool(t))
ctx := context.Background()
u := seedUser(t, repo)
if err := repo.SetRole(ctx, u.ID, domain.RoleAdmin); err != nil {
t.Fatalf("promote: %v", err)
}
got, _ := repo.GetByID(ctx, u.ID)
if got.Role != domain.RoleAdmin {
t.Errorf("role = %q, want admin", got.Role)
}
if err := repo.SetRole(ctx, uuid.New(), domain.RoleAdmin); !errors.Is(err, usecase.ErrUserNotFound) {
t.Errorf("unknown user: got %v, want ErrUserNotFound", err)
}
}
@@ -85,6 +85,27 @@ func (r *UserRepo) SetVerified(ctx context.Context, id uuid.UUID, verified bool)
return nil
}
// SetRole changes a user's role and returns ErrUserNotFound when the id matches
// no row.
func (r *UserRepo) SetRole(ctx context.Context, id uuid.UUID, role domain.Role) error {
sql, args, err := psql.Update("users").
Set("role", string(role)).
Set("updated_at", sq.Expr("now()")).
Where(sq.Eq{"id": id}).
ToSql()
if err != nil {
return err
}
tag, err := conn(ctx, r.pool).Exec(ctx, sql, args...)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return usecase.ErrUserNotFound
}
return nil
}
func scanUser(row pgx.Row) (*domain.User, error) {
var (
u domain.User