diff --git a/Makefile b/Makefile index 3ee8aa4..077a1b8 100644 --- a/Makefile +++ b/Makefile @@ -112,6 +112,10 @@ mock: -destination=internal/app/auth/mock/auth.go \ github.com/ozontech/seq-ui/internal/app/auth \ OIDCProvider,JWTProvider + PATH="$(LOCAL_BIN):$(PATH)" mockgen \ + -destination=internal/pkg/service/admin/mock/service.go \ + github.com/ozontech/seq-ui/internal/pkg/service/admin \ + Service .PHONY: protoc protoc: diff --git a/api/admin/v1/admin.proto b/api/admin/v1/admin.proto new file mode 100644 index 0000000..cfb163a --- /dev/null +++ b/api/admin/v1/admin.proto @@ -0,0 +1,86 @@ +syntax = "proto3"; + +package admin.v1; + +option go_package = "github.com/ozontech/seq-ui/pkg/admin/v1;admin"; + +service AdminService { + rpc CreateRole(CreateRoleRequest) returns (CreateRoleResponse); + + rpc AddUsersToRole(AddUsersToRoleRequest) returns (AddUsersToRoleResponse); + + rpc GetRoles(GetRolesRequest) returns (GetRolesResponse); + + rpc GetRole(GetRoleRequest) returns (GetRoleResponse); + + rpc UpdateRole(UpdateRoleRequest) returns (UpdateRoleResponse); + + rpc DeleteRole(DeleteRoleRequest) returns (DeleteRoleResponse); + + rpc DeleteUsersFromRole(DeleteUsersFromRoleRequest) returns (DeleteUsersFromRoleResponse); +} + +message Role { + int32 id = 1; + string name = 2; + repeated uint64 permissions = 3; +} + +message CreateRoleRequest { + string name = 1; + repeated uint64 permissions = 2; +} + +message CreateRoleResponse { + int32 role_id = 1; +} + +message AddUsersToRoleRequest { + int32 role_id = 1; + repeated string usernames = 2; +} + +message AddUsersToRoleResponse {} + +message GetRolesRequest {} + +message GetRolesResponse { + message Permission { + uint64 value = 1; + string name = 2; + string description = 3; + } + + repeated Role roles = 1; + repeated Permission available_permissions = 2; +} + +message GetRoleRequest { + int32 id = 1; +} + +message GetRoleResponse { + repeated string usernames = 1; +} + +message UpdateRoleRequest { + int32 id = 1; + optional string name = 2; + repeated uint64 permissions = 3; +} + +message UpdateRoleResponse {} + +message DeleteRoleRequest { + int32 id = 1; + optional int32 replacement_role_id = 2; +} + +message DeleteRoleResponse {} + +message DeleteUsersFromRoleRequest { + int32 role_id = 1; + repeated string usernames = 2; +} + +message DeleteUsersFromRoleResponse {} diff --git a/api/userprofile/v1/userprofile.proto b/api/userprofile/v1/userprofile.proto index 4391901..eb4c767 100644 --- a/api/userprofile/v1/userprofile.proto +++ b/api/userprofile/v1/userprofile.proto @@ -26,6 +26,7 @@ message GetUserProfileResponse { string timezone = 1; string onboarding_version = 2; LogColumns log_columns = 3; + optional int32 role_id = 4; } message UpdateUserProfileRequest { @@ -36,7 +37,6 @@ message UpdateUserProfileRequest { message UpdateUserProfileResponse {} - message GetFavoriteQueriesRequest {} message GetFavoriteQueriesResponse { @@ -66,7 +66,6 @@ message DeleteFavoriteQueryRequest { message DeleteFavoriteQueryResponse {} - message GetDashboardsRequest {} message GetDashboardsResponse { @@ -109,4 +108,4 @@ message DeleteDashboardRequest { string uuid = 1; } -message DeleteDashboardResponse {} \ No newline at end of file +message DeleteDashboardResponse {} diff --git a/cmd/seq-ui/main.go b/cmd/seq-ui/main.go index d661fcd..2156353 100644 --- a/cmd/seq-ui/main.go +++ b/cmd/seq-ui/main.go @@ -16,6 +16,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" "github.com/joho/godotenv" "github.com/ozontech/seq-ui/internal/api" + admin_v1 "github.com/ozontech/seq-ui/internal/api/admin/v1" dashboards_v1 "github.com/ozontech/seq-ui/internal/api/dashboards/v1" errorgroups_v1 "github.com/ozontech/seq-ui/internal/api/errorgroups/v1" massexport_v1 "github.com/ozontech/seq-ui/internal/api/massexport/v1" @@ -29,6 +30,7 @@ import ( "github.com/ozontech/seq-ui/internal/pkg/repository" repositorych "github.com/ozontech/seq-ui/internal/pkg/repository_ch" "github.com/ozontech/seq-ui/internal/pkg/service" + adminservice "github.com/ozontech/seq-ui/internal/pkg/service/admin" asyncsearches "github.com/ozontech/seq-ui/internal/pkg/service/async_searches" "github.com/ozontech/seq-ui/internal/pkg/service/errorgroups" "github.com/ozontech/seq-ui/internal/pkg/service/massexport" @@ -152,6 +154,7 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar { var ( asyncSearchesService *asyncsearches.Service p *profiles.Profiles + adminV1 *admin_v1.Admin userProfileV1 *userprofile_v1.UserProfile dashboardsV1 *dashboards_v1.Dashboards ) @@ -164,6 +167,11 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar { dashboardsV1 = dashboards_v1.New(svc, p) asyncSearchesService = asyncsearches.New(ctx, repo, defaultClient, cfg.Handlers.AsyncSearch) + + if cfg.Handlers.Admin != nil { + adminSvc := adminservice.New(repo, cfg.Handlers.Admin) + adminV1 = admin_v1.New(adminSvc) + } } seqApiV1 := seqapi_v1.New(cfg.Handlers.SeqAPI, seqDBClients, inmemWithRedisCache, redisCache, asyncSearchesService, p) @@ -182,7 +190,7 @@ func initApp(ctx context.Context, cfg config.Config) *api.Registrar { errorGroupsV1 = errorgroups_v1.New(svc) } - return api.NewRegistrar(seqApiV1, userProfileV1, dashboardsV1, massExportV1, errorGroupsV1) + return api.NewRegistrar(adminV1, seqApiV1, userProfileV1, dashboardsV1, massExportV1, errorGroupsV1) } func initSeqDBClients(ctx context.Context, cfg config.Config) (map[string]seqdb.Client, error) { @@ -200,9 +208,7 @@ func initSeqDBClients(ctx context.Context, cfg config.Config) (map[string]seqdb. func createSeqBDClient(ctx context.Context, cfg config.SeqDBClient, seqAPI config.SeqAPI) (seqdb.Client, error) { clientMaxRecvMsgSize := cfg.AvgDocSize * 1024 * int(seqAPI.MaxSearchLimit) - if clientMaxRecvMsgSize < defaultClientMaxRecvMsgSize { - clientMaxRecvMsgSize = defaultClientMaxRecvMsgSize - } + clientMaxRecvMsgSize = max(clientMaxRecvMsgSize, defaultClientMaxRecvMsgSize) clientParams := seqdb.ClientParams{ Addrs: cfg.Addrs, diff --git a/docs/en/02-configuration.md b/docs/en/02-configuration.md index 303a021..73acb41 100644 --- a/docs/en/02-configuration.md +++ b/docs/en/02-configuration.md @@ -505,6 +505,7 @@ handlers: error_groups: mass_export: async_search: + admin: ``` ### SeqAPI @@ -831,6 +832,18 @@ Configuration for async search request. Maximum length of `request.query` in async searches list responses. Requests exceeding the limit will be truncated to it +### Admin + +**`admin`** *`Admin`* *`optional`* + +Configuration for `/admin` API. + +`Admin` fields: + ++ **`super_users`** *`[]string`* *`required`* + + List of users with full access to admin features. + ## Tracing The tracing configuration is set through environment variables. diff --git a/docs/ru/02-configuration.md b/docs/ru/02-configuration.md index e9dde32..2e1d2b3 100644 --- a/docs/ru/02-configuration.md +++ b/docs/ru/02-configuration.md @@ -505,6 +505,7 @@ handlers: error_groups: mass_export: async_search: + admin: ``` ### SeqAPI @@ -513,7 +514,7 @@ handlers: Конфигурация `/seqapi` API. -`SeqAPI` fields: +Поля `SeqAPI`: + **`max_search_limit`** *`int`* *`default=0`* @@ -831,6 +832,18 @@ handlers: Максимальная длина `request.query` в ответе списка отложенных запросов. Запросы, превышающие лимит, будут обрезаны до этого значения. +### Admin + +**`admin`** *`Admin`* *`optional`* + +Конфигурация `/admin` API. + +Поля `Admin`: + ++ **`super_users`** *`[]string`* *`required`* + + Список пользователей с полным доступом к административным функциям. + ## Tracing Конфигурация трейсинга задается переменными окружения. diff --git a/internal/api/admin/v1/admin.go b/internal/api/admin/v1/admin.go new file mode 100644 index 0000000..aa0a734 --- /dev/null +++ b/internal/api/admin/v1/admin.go @@ -0,0 +1,28 @@ +package admin_v1 + +import ( + "github.com/go-chi/chi/v5" + grpc_api "github.com/ozontech/seq-ui/internal/api/admin/v1/grpc" + http_api "github.com/ozontech/seq-ui/internal/api/admin/v1/http" + "github.com/ozontech/seq-ui/internal/pkg/service/admin" +) + +type Admin struct { + grpcAPI *grpc_api.API + httpAPI *http_api.API +} + +func New(svc admin.Service) *Admin { + return &Admin{ + grpcAPI: grpc_api.New(svc), + httpAPI: http_api.New(svc), + } +} + +func (a *Admin) GRPCServer() *grpc_api.API { + return a.grpcAPI +} + +func (a *Admin) HTTPRouter() chi.Router { + return a.httpAPI.Router() +} diff --git a/internal/api/admin/v1/grpc/api.go b/internal/api/admin/v1/grpc/api.go new file mode 100644 index 0000000..e1847ea --- /dev/null +++ b/internal/api/admin/v1/grpc/api.go @@ -0,0 +1,33 @@ +package grpc + +import ( + "github.com/ozontech/seq-ui/internal/app/types" + adminservice "github.com/ozontech/seq-ui/internal/pkg/service/admin" + "github.com/ozontech/seq-ui/pkg/admin/v1" +) + +type API struct { + admin.UnimplementedAdminServiceServer + + service adminservice.Service + availablePermissions []*admin.GetRolesResponse_Permission +} + +func New(svc adminservice.Service) *API { + return &API{ + service: svc, + availablePermissions: availablePermissionsToProto(svc.GetAvailablePermissions()), + } +} + +func availablePermissionsToProto(source []types.Permission) []*admin.GetRolesResponse_Permission { + availablePermissions := make([]*admin.GetRolesResponse_Permission, 0, len(source)) + for _, aPermission := range source { + availablePermissions = append(availablePermissions, &admin.GetRolesResponse_Permission{ + Value: aPermission.Value, + Name: aPermission.Name, + Description: aPermission.Description, + }) + } + return availablePermissions +} diff --git a/internal/api/admin/v1/grpc/roles.go b/internal/api/admin/v1/grpc/roles.go new file mode 100644 index 0000000..1c0dbbb --- /dev/null +++ b/internal/api/admin/v1/grpc/roles.go @@ -0,0 +1,199 @@ +package grpc + +import ( + "context" + + "github.com/ozontech/seq-ui/internal/api/grpcutil" + "github.com/ozontech/seq-ui/internal/app/types" + "github.com/ozontech/seq-ui/pkg/admin/v1" + "github.com/ozontech/seq-ui/tracing" + "go.opentelemetry.io/otel/attribute" +) + +func (a *API) CreateRole(ctx context.Context, req *admin.CreateRoleRequest) (*admin.CreateRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_create_role") + defer span.End() + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_name", + Value: attribute.StringValue(req.GetName()), + }, + attribute.KeyValue{ + Key: "permissions_count", + Value: attribute.IntValue(len(req.GetPermissions())), + }, + ) + + request := types.CreateRoleRequest{ + Name: req.Name, + Permissions: req.Permissions, + } + + roleID, err := a.service.CreateRole(ctx, request) + if err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.CreateRoleResponse{ + RoleId: roleID, + }, nil +} + +func (a *API) AddUsersToRole(ctx context.Context, req *admin.AddUsersToRoleRequest) (*admin.AddUsersToRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_add_users_to_role") + defer span.End() + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(req.GetRoleId())), + }, + attribute.KeyValue{ + Key: "users_count", + Value: attribute.IntValue(len(req.GetUsernames())), + }, + ) + + if err := a.service.AddUsersToRole(ctx, types.AddUsersToRoleRequest{ + RoleID: req.RoleId, + Usernames: req.Usernames, + }); err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.AddUsersToRoleResponse{}, nil +} + +func (a *API) GetRoles(ctx context.Context, _ *admin.GetRolesRequest) (*admin.GetRolesResponse, error) { + resp, err := a.service.GetRoles(ctx) + if err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.GetRolesResponse{ + Roles: rolesToProto(resp.Roles), + AvailablePermissions: a.availablePermissions, + }, nil +} + +func (a *API) GetRole(ctx context.Context, req *admin.GetRoleRequest) (*admin.GetRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_get_role") + defer span.End() + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(req.GetId())), + }, + ) + + roleInfo, err := a.service.GetRole(ctx, types.GetRoleRequest{ + RoleID: req.Id, + }) + if err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.GetRoleResponse{ + Usernames: roleInfo.Usernames, + }, nil +} + +func (a *API) UpdateRole(ctx context.Context, req *admin.UpdateRoleRequest) (*admin.UpdateRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_update_role") + defer span.End() + + spanAttributes := []attribute.KeyValue{ + { + Key: "role_id", + Value: attribute.IntValue(int(req.GetId())), + }, + { + Key: "permissions_count", + Value: attribute.IntValue(len(req.GetPermissions())), + }, + } + if req.Name != nil { + spanAttributes = append(spanAttributes, attribute.KeyValue{ + Key: "role_name", + Value: attribute.StringValue(req.GetName()), + }) + } + span.SetAttributes(spanAttributes...) + + if err := a.service.UpdateRole(ctx, types.UpdateRoleRequest{ + RoleID: req.Id, + Name: req.Name, + Permissions: req.Permissions, + }); err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.UpdateRoleResponse{}, nil +} + +func (a *API) DeleteRole(ctx context.Context, req *admin.DeleteRoleRequest) (*admin.DeleteRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_delete_role") + defer span.End() + + spanAttributes := []attribute.KeyValue{ + { + Key: "role_id", + Value: attribute.IntValue(int(req.GetId())), + }, + } + if req.ReplacementRoleId != nil { + spanAttributes = append(spanAttributes, attribute.KeyValue{ + Key: "replacement_role_id", + Value: attribute.IntValue(int(req.GetReplacementRoleId())), + }) + } + span.SetAttributes(spanAttributes...) + + if err := a.service.DeleteRole(ctx, types.DeleteRoleRequest{ + RoleID: req.Id, + ReplacementRoleID: req.ReplacementRoleId, + }); err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.DeleteRoleResponse{}, nil +} + +func (a *API) DeleteUsersFromRole(ctx context.Context, req *admin.DeleteUsersFromRoleRequest) (*admin.DeleteUsersFromRoleResponse, error) { + ctx, span := tracing.StartSpan(ctx, "admin_v1_delete_users_from_role") + defer span.End() + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(req.GetRoleId())), + }, + attribute.KeyValue{ + Key: "users_count", + Value: attribute.IntValue(len(req.GetUsernames())), + }, + ) + + if err := a.service.DeleteUsersFromRole(ctx, types.DeleteUsersFromRoleRequest{ + RoleID: req.RoleId, + Usernames: req.Usernames, + }); err != nil { + return nil, grpcutil.ProcessError(err) + } + + return &admin.DeleteUsersFromRoleResponse{}, nil +} + +func rolesToProto(source []types.Role) []*admin.Role { + roles := make([]*admin.Role, 0, len(source)) + for _, role := range source { + roles = append(roles, &admin.Role{ + Id: role.ID, + Name: role.Name, + Permissions: role.Permissions, + }) + } + return roles +} diff --git a/internal/api/admin/v1/grpc/roles_test.go b/internal/api/admin/v1/grpc/roles_test.go new file mode 100644 index 0000000..b068468 --- /dev/null +++ b/internal/api/admin/v1/grpc/roles_test.go @@ -0,0 +1,603 @@ +package grpc + +import ( + "context" + "errors" + "testing" + + "github.com/ozontech/seq-ui/internal/app/types" + mock "github.com/ozontech/seq-ui/internal/pkg/service/admin/mock" + "github.com/ozontech/seq-ui/pkg/admin/v1" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + errSomethingWrong = errors.New("something happened wrong") + + defaultAvailablePermissions = []types.Permission{ + { + Value: 1, + Name: "manage_roles", + Description: "Manage roles", + }, + } +) + +func setupAPI(t *testing.T) (*API, *mock.MockService) { + ctrl := gomock.NewController(t) + mockedSvc := mock.NewMockService(ctrl) + + mockedSvc.EXPECT(). + GetAvailablePermissions(). + Return(defaultAvailablePermissions). + Times(1) + + api := New(mockedSvc) + + return api, mockedSvc +} + +func TestCreateRole(t *testing.T) { + type mockArgs struct { + req types.CreateRoleRequest + roleID int32 + err error + } + + tests := []struct { + name string + + req *admin.CreateRoleRequest + want *admin.CreateRoleResponse + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok", + req: &admin.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + want: &admin.CreateRoleResponse{RoleId: 1}, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + roleID: 1, + }, + }, + { + name: "err_svc_validation", + req: &admin.CreateRoleRequest{ + Name: "", + }, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.CreateRoleRequest{ + Name: "", + }, + err: types.NewErrInvalidRequestField("empty role name"), + }, + }, + { + name: "err_svc_permission_denied", + req: &admin.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + wantCode: codes.PermissionDenied, + mockArgs: &mockArgs{ + req: types.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + err: types.ErrPermissionDenied, + }, + }, + { + name: "err_svc_internal", + req: &admin.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.CreateRoleRequest{ + Name: "manager", + Permissions: []uint64{1}, + }, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + CreateRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.roleID, tt.mockArgs.err). + Times(1) + } + + gotResp, err := api.CreateRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + if tt.wantCode != codes.OK { + return + } + + require.Equal(t, tt.want, gotResp) + }) + } +} + +func TestAddUsersToRole(t *testing.T) { + type mockArgs struct { + req types.AddUsersToRoleRequest + err error + } + + tests := []struct { + name string + + req *admin.AddUsersToRoleRequest + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok", + req: &admin.AddUsersToRoleRequest{ + RoleId: 1, + Usernames: []string{"user1", "user2"}, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", "user2"}, + }, + }, + }, + { + name: "err_svc_validation", + req: &admin.AddUsersToRoleRequest{ + RoleId: 0, + Usernames: []string{"user1"}, + }, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.AddUsersToRoleRequest{ + RoleID: 0, + Usernames: []string{"user1"}, + }, + err: types.NewErrInvalidRequestField("value role_id must be greater than 0"), + }, + }, + { + name: "err_svc_internal", + req: &admin.AddUsersToRoleRequest{ + RoleId: 1, + Usernames: []string{"user1"}, + }, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + AddUsersToRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + _, err := api.AddUsersToRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + }) + } +} + +func TestGetRoles(t *testing.T) { + type mockArgs struct { + resp types.GetRolesResponse + err error + } + + tests := []struct { + name string + + want *admin.GetRolesResponse + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok", + want: &admin.GetRolesResponse{ + Roles: []*admin.Role{ + {Id: 1, Name: "manager", Permissions: []uint64{1}}, + }, + AvailablePermissions: []*admin.GetRolesResponse_Permission{ + {Value: 1, Name: "manage_roles", Description: "Manage roles"}, + }, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + resp: types.GetRolesResponse{ + Roles: []types.Role{ + {ID: 1, Name: "manager", Permissions: []uint64{1}}, + }, + }, + }, + }, + { + name: "err_svc_permission_denied", + wantCode: codes.PermissionDenied, + mockArgs: &mockArgs{ + err: types.ErrPermissionDenied, + }, + }, + { + name: "err_svc_internal", + wantCode: codes.Internal, + mockArgs: &mockArgs{ + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + GetRoles(gomock.Any()). + Return(tt.mockArgs.resp, tt.mockArgs.err). + Times(1) + } + + gotResp, err := api.GetRoles(context.Background(), &admin.GetRolesRequest{}) + require.Equal(t, tt.wantCode, status.Code(err)) + if tt.wantCode != codes.OK { + return + } + + require.Equal(t, tt.want, gotResp) + }) + } +} + +func TestGetRole(t *testing.T) { + type mockArgs struct { + req types.GetRoleRequest + roleInfo types.RoleInfo + err error + } + + tests := []struct { + name string + + req *admin.GetRoleRequest + want *admin.GetRoleResponse + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok", + req: &admin.GetRoleRequest{Id: 1}, + want: &admin.GetRoleResponse{ + Usernames: []string{"user1", "user2"}, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.GetRoleRequest{RoleID: 1}, + roleInfo: types.RoleInfo{Usernames: []string{"user1", "user2"}}, + }, + }, + { + name: "err_svc_validation", + req: &admin.GetRoleRequest{Id: 0}, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.GetRoleRequest{RoleID: 0}, + err: types.NewErrInvalidRequestField("value role_id must be greater than 0"), + }, + }, + { + name: "err_svc_internal", + req: &admin.GetRoleRequest{Id: 1}, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.GetRoleRequest{RoleID: 1}, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + GetRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.roleInfo, tt.mockArgs.err). + Times(1) + } + + gotResp, err := api.GetRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + if tt.wantCode != codes.OK { + return + } + + require.Equal(t, tt.want, gotResp) + }) + } +} + +func TestUpdateRole(t *testing.T) { + name := "new role name" + + type mockArgs struct { + req types.UpdateRoleRequest + err error + } + + tests := []struct { + name string + + req *admin.UpdateRoleRequest + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok_name_and_permissions", + req: &admin.UpdateRoleRequest{ + Id: 1, + Name: &name, + Permissions: []uint64{1}, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + Permissions: []uint64{1}, + }, + }, + }, + { + name: "err_svc_empty_update", + req: &admin.UpdateRoleRequest{ + Id: 1, + }, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.UpdateRoleRequest{ + RoleID: 1, + }, + err: types.ErrEmptyUpdateRequest, + }, + }, + { + name: "err_svc_internal", + req: &admin.UpdateRoleRequest{ + Id: 1, + Name: &name, + }, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + UpdateRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + _, err := api.UpdateRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + }) + } +} + +func TestDeleteRole(t *testing.T) { + replacementID := int32(2) + + type mockArgs struct { + req types.DeleteRoleRequest + err error + } + + tests := []struct { + name string + + req *admin.DeleteRoleRequest + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok_no_replacement", + req: &admin.DeleteRoleRequest{Id: 1}, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.DeleteRoleRequest{RoleID: 1}, + }, + }, + { + name: "ok_with_replacement", + req: &admin.DeleteRoleRequest{ + Id: 1, + ReplacementRoleId: &replacementID, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.DeleteRoleRequest{ + RoleID: 1, + ReplacementRoleID: &replacementID, + }, + }, + }, + { + name: "err_svc_validation", + req: &admin.DeleteRoleRequest{Id: 0}, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.DeleteRoleRequest{RoleID: 0}, + err: types.NewErrInvalidRequestField("value role_id must be greater than 0"), + }, + }, + { + name: "err_svc_internal", + req: &admin.DeleteRoleRequest{Id: 1}, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.DeleteRoleRequest{RoleID: 1}, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + DeleteRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + _, err := api.DeleteRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + }) + } +} + +func TestDeleteUsersFromRole(t *testing.T) { + type mockArgs struct { + req types.DeleteUsersFromRoleRequest + err error + } + + tests := []struct { + name string + + req *admin.DeleteUsersFromRoleRequest + wantCode codes.Code + + mockArgs *mockArgs + }{ + { + name: "ok", + req: &admin.DeleteUsersFromRoleRequest{ + RoleId: 1, + Usernames: []string{"user1", "user2"}, + }, + wantCode: codes.OK, + mockArgs: &mockArgs{ + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", "user2"}, + }, + }, + }, + { + name: "err_svc_validation", + req: &admin.DeleteUsersFromRoleRequest{ + RoleId: 0, + Usernames: []string{"user1"}, + }, + wantCode: codes.InvalidArgument, + mockArgs: &mockArgs{ + req: types.DeleteUsersFromRoleRequest{ + RoleID: 0, + Usernames: []string{"user1"}, + }, + err: types.NewErrInvalidRequestField("value role_id must be greater than 0"), + }, + }, + { + name: "err_svc_internal", + req: &admin.DeleteUsersFromRoleRequest{ + RoleId: 1, + Usernames: []string{"user1"}, + }, + wantCode: codes.Internal, + mockArgs: &mockArgs{ + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + api, mockedSvc := setupAPI(t) + + if tt.mockArgs != nil { + mockedSvc.EXPECT(). + DeleteUsersFromRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + _, err := api.DeleteUsersFromRole(context.Background(), tt.req) + require.Equal(t, tt.wantCode, status.Code(err)) + }) + } +} diff --git a/internal/api/admin/v1/http/api.go b/internal/api/admin/v1/http/api.go new file mode 100644 index 0000000..6bfcb7a --- /dev/null +++ b/internal/api/admin/v1/http/api.go @@ -0,0 +1,50 @@ +package http + +import ( + "github.com/go-chi/chi/v5" + "github.com/ozontech/seq-ui/internal/app/types" + adminservice "github.com/ozontech/seq-ui/internal/pkg/service/admin" +) + +type API struct { + service adminservice.Service + availablePermissions []permission +} + +func New(svc adminservice.Service) *API { + return &API{ + service: svc, + availablePermissions: parsePermissions(svc.GetAvailablePermissions()), + } +} + +func (a *API) Router() chi.Router { + mux := chi.NewMux() + + mux.Route("/roles", func(r chi.Router) { + r.Post("/", a.serveCreateRole) + r.Get("/", a.serveGetRoles) + + r.Route("/{id}", func(r chi.Router) { + r.Post("/users", a.serveAddUsersToRole) + r.Delete("/users", a.serveDeleteUsersFromRole) + r.Get("/", a.serveGetRole) + r.Patch("/", a.serveUpdateRole) + r.Delete("/", a.serveDeleteRole) + }) + }) + + return mux +} + +func parsePermissions(source []types.Permission) []permission { + permissions := make([]permission, 0, len(source)) + for _, s := range source { + permissions = append(permissions, permission{ + Value: s.Value, + Name: s.Name, + Description: s.Description, + }) + } + return permissions +} diff --git a/internal/api/admin/v1/http/roles.go b/internal/api/admin/v1/http/roles.go new file mode 100644 index 0000000..5e7d91a --- /dev/null +++ b/internal/api/admin/v1/http/roles.go @@ -0,0 +1,408 @@ +package http + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + "github.com/ozontech/seq-ui/internal/api/httputil" + "github.com/ozontech/seq-ui/internal/app/types" + "github.com/ozontech/seq-ui/tracing" + "go.opentelemetry.io/otel/attribute" +) + +// serveCreateRole go doc. +// +// @Router /admin/v1/roles [post] +// @ID admin_v1_create_role +// @Tags admin_v1 +// @Param body body createRoleRequest true "Request body" +// @Success 200 {object} createRoleResponse "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveCreateRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_create_role") + defer span.End() + + wr := httputil.NewWriter(w) + + var httpReq createRoleRequest + if err := json.NewDecoder(r.Body).Decode(&httpReq); err != nil { + wr.Error(fmt.Errorf("failed to parse request: %w", err), http.StatusBadRequest) + return + } + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_name", + Value: attribute.StringValue(httpReq.Name), + }, + attribute.KeyValue{ + Key: "permissions_count", + Value: attribute.IntValue(len(httpReq.Permissions)), + }, + ) + + roleID, err := a.service.CreateRole(ctx, types.CreateRoleRequest{ + Name: httpReq.Name, + Permissions: httpReq.Permissions, + }) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + wr.WriteJson(createRoleResponse{RoleID: roleID}) +} + +// serveAddUsersToRole go doc. +// +// @Router /admin/v1/roles/{id}/users [post] +// @ID admin_v1_add_users_to_role +// @Tags admin_v1 +// @Param id path int32 true "Role ID" +// @Param body body addUsersToRoleRequest true "Request body" +// @Success 200 {object} nil "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveAddUsersToRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_add_users_to_role") + defer span.End() + + wr := httputil.NewWriter(w) + + roleID, err := getRoleID(r) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + var httpReq addUsersToRoleRequest + if err := json.NewDecoder(r.Body).Decode(&httpReq); err != nil { + wr.Error(fmt.Errorf("failed to parse request: %w", err), http.StatusBadRequest) + return + } + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(roleID)), + }, + attribute.KeyValue{ + Key: "users_count", + Value: attribute.IntValue(len(httpReq.Usernames)), + }, + ) + + if err := a.service.AddUsersToRole(ctx, types.AddUsersToRoleRequest{ + RoleID: roleID, + Usernames: httpReq.Usernames, + }); err != nil { + httputil.ProcessError(wr, err) + return + } + + w.WriteHeader(http.StatusOK) +} + +// serveGetRoles go doc. +// +// @Router /admin/v1/roles [get] +// @ID admin_v1_get_roles +// @Tags admin_v1 +// @Success 200 {object} getRolesResponse "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveGetRoles(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_get_roles") + defer span.End() + + wr := httputil.NewWriter(w) + + resp, err := a.service.GetRoles(ctx) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + wr.WriteJson(getRolesResponse{ + Roles: parseRoles(resp.Roles), + AvailablePermissions: a.availablePermissions, + }) +} + +// serveGetRole go doc. +// +// @Router /admin/v1/roles/{id} [get] +// @ID admin_v1_get_role +// @Tags admin_v1 +// @Param id path int32 true "Role ID" +// @Success 200 {object} getRoleResponse "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveGetRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_get_role") + defer span.End() + + wr := httputil.NewWriter(w) + + roleID, err := getRoleID(r) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(roleID)), + }, + ) + + roleInfo, err := a.service.GetRole(ctx, types.GetRoleRequest{ + RoleID: roleID, + }) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + wr.WriteJson(getRoleResponse{ + Usernames: roleInfo.Usernames, + }) +} + +// serveUpdateRole go doc. +// +// @Router /admin/v1/roles/{id} [patch] +// @ID admin_v1_update_role +// @Tags admin_v1 +// @Param id path int32 true "Role ID" +// @Param body body updateRoleRequest true "Request body" +// @Success 200 {object} nil "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveUpdateRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_update_role") + defer span.End() + + wr := httputil.NewWriter(w) + + roleID, err := getRoleID(r) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + var httpReq updateRoleRequest + if err := json.NewDecoder(r.Body).Decode(&httpReq); err != nil { + wr.Error(fmt.Errorf("failed to parse request: %w", err), http.StatusBadRequest) + return + } + + spanAttributes := []attribute.KeyValue{ + { + Key: "role_id", + Value: attribute.IntValue(int(roleID)), + }, + { + Key: "permissions_count", + Value: attribute.IntValue(len(httpReq.Permissions)), + }, + } + if httpReq.Name != nil { + spanAttributes = append(spanAttributes, attribute.KeyValue{ + Key: "role_name", + Value: attribute.StringValue(*httpReq.Name), + }) + } + span.SetAttributes(spanAttributes...) + + if err := a.service.UpdateRole(ctx, types.UpdateRoleRequest{ + RoleID: roleID, + Name: httpReq.Name, + Permissions: httpReq.Permissions, + }); err != nil { + httputil.ProcessError(wr, err) + return + } + + w.WriteHeader(http.StatusOK) +} + +// serveDeleteRole go doc. +// +// @Router /admin/v1/roles/{id} [delete] +// @ID admin_v1_delete_role +// @Tags admin_v1 +// @Param id path int32 true "Role ID" +// @Param body body deleteRoleRequest false "Request body" +// @Success 200 {object} nil "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveDeleteRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_delete_role") + defer span.End() + + wr := httputil.NewWriter(w) + + roleID, err := getRoleID(r) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + var httpReq deleteRoleRequest + if err := json.NewDecoder(r.Body).Decode(&httpReq); err != nil && !errors.Is(err, io.EOF) { + wr.Error(fmt.Errorf("failed to parse request: %w", err), http.StatusBadRequest) + return + } + + spanAttributes := []attribute.KeyValue{ + { + Key: "role_id", + Value: attribute.IntValue(int(roleID)), + }, + } + if httpReq.ReplacementRoleID != nil { + spanAttributes = append(spanAttributes, attribute.KeyValue{ + Key: "replacement_role_id", + Value: attribute.IntValue(int(*httpReq.ReplacementRoleID)), + }) + } + span.SetAttributes(spanAttributes...) + + if err := a.service.DeleteRole(ctx, types.DeleteRoleRequest{ + RoleID: roleID, + ReplacementRoleID: httpReq.ReplacementRoleID, + }); err != nil { + httputil.ProcessError(wr, err) + return + } + + w.WriteHeader(http.StatusOK) +} + +// serveDeleteUsersFromRole go doc. +// +// @Router /admin/v1/roles/{id}/users [delete] +// @ID admin_v1_delete_users_from_role +// @Tags admin_v1 +// @Param id path int32 true "Role ID" +// @Param body body deleteUsersFromRoleRequest true "Request body" +// @Success 200 {object} nil "A successful response" +// @Failure default {object} httputil.Error "An unexpected error response" +// @Security bearer +func (a *API) serveDeleteUsersFromRole(w http.ResponseWriter, r *http.Request) { + ctx, span := tracing.StartSpan(r.Context(), "admin_v1_delete_users_from_role") + defer span.End() + + wr := httputil.NewWriter(w) + + roleID, err := getRoleID(r) + if err != nil { + httputil.ProcessError(wr, err) + return + } + + var httpReq deleteUsersFromRoleRequest + if err := json.NewDecoder(r.Body).Decode(&httpReq); err != nil { + wr.Error(fmt.Errorf("failed to parse request: %w", err), http.StatusBadRequest) + return + } + + span.SetAttributes( + attribute.KeyValue{ + Key: "role_id", + Value: attribute.IntValue(int(roleID)), + }, + attribute.KeyValue{ + Key: "users_count", + Value: attribute.IntValue(len(httpReq.Usernames)), + }, + ) + + if err := a.service.DeleteUsersFromRole(ctx, types.DeleteUsersFromRoleRequest{ + RoleID: roleID, + Usernames: httpReq.Usernames, + }); err != nil { + httputil.ProcessError(wr, err) + return + } + + w.WriteHeader(http.StatusOK) +} + +func getRoleID(r *http.Request) (int32, error) { + idString := chi.URLParam(r, "id") + + id, err := strconv.ParseInt(idString, 10, 32) + if err != nil { + return 0, types.NewErrInvalidRequestField("invalid role_id") + } + + return int32(id), nil +} + +func parseRoles(source []types.Role) []role { + roles := make([]role, 0, len(source)) + for _, s := range source { + roles = append(roles, role{ + ID: s.ID, + Name: s.Name, + Permissions: s.Permissions, + }) + } + return roles +} + +type role struct { + ID int32 `json:"id"` + Name string `json:"name"` + Permissions []uint64 `json:"permissions"` +} // @name admin.v1.Role + +type permission struct { + Value uint64 `json:"value"` + Name string `json:"name"` + Description string `json:"description"` +} // @name admin.v1.Permission + +type createRoleRequest struct { + Name string `json:"name"` + Permissions []uint64 `json:"permissions"` +} // @name admin.v1.CreateRoleRequest + +type createRoleResponse struct { + RoleID int32 `json:"role_id"` +} // @name admin.v1.CreateRoleResponse + +type addUsersToRoleRequest struct { + Usernames []string `json:"usernames"` +} // @name admin.v1.AddUsersToRoleRequest + +type getRolesResponse struct { + Roles []role `json:"roles"` + AvailablePermissions []permission `json:"available_permissions"` +} // @name admin.v1.GetRolesResponse + +type getRoleResponse struct { + Usernames []string `json:"usernames"` +} // @name admin.v1.GetRoleResponse + +type updateRoleRequest struct { + Name *string `json:"name"` + Permissions []uint64 `json:"permissions"` +} // @name admin.v1.UpdateRoleResponse + +type deleteRoleRequest struct { + ReplacementRoleID *int32 `json:"replacement_role_id"` +} // @name admin.v1.DeleteRoleResponse + +type deleteUsersFromRoleRequest struct { + Usernames []string `json:"usernames"` +} // @name admin.v1.DeleteUsersFromRoleRequest diff --git a/internal/api/admin/v1/http/roles_test.go b/internal/api/admin/v1/http/roles_test.go new file mode 100644 index 0000000..00f53c9 --- /dev/null +++ b/internal/api/admin/v1/http/roles_test.go @@ -0,0 +1,37 @@ +package http + +import ( + "errors" + "testing" + + "github.com/ozontech/seq-ui/internal/app/types" + mock "github.com/ozontech/seq-ui/internal/pkg/service/admin/mock" + "go.uber.org/mock/gomock" +) + +var ( + errSomethingWrong = errors.New("something happened wrong") //nolint:unused + + defaultAvailablePermissions = []types.Permission{ //nolint:unused + { + Value: 1, + Name: "manage_roles", + Description: "Manage roles", + }, + } +) + +//nolint:unused +func setupAPI(t *testing.T) (*API, *mock.MockService) { + ctrl := gomock.NewController(t) + mockedSvc := mock.NewMockService(ctrl) + + mockedSvc.EXPECT(). + GetAvailablePermissions(). + Return(defaultAvailablePermissions). + Times(1) + + api := New(mockedSvc) + + return api, mockedSvc +} diff --git a/internal/api/dashboards/v1/grpc/create_test.go b/internal/api/dashboards/v1/grpc/create_test.go index c22ef3a..e284c92 100644 --- a/internal/api/dashboards/v1/grpc/create_test.go +++ b/internal/api/dashboards/v1/grpc/create_test.go @@ -92,7 +92,6 @@ func TestCreate(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -105,7 +104,7 @@ func TestCreate(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/delete_test.go b/internal/api/dashboards/v1/grpc/delete_test.go index 6d9885b..0a3f0c4 100644 --- a/internal/api/dashboards/v1/grpc/delete_test.go +++ b/internal/api/dashboards/v1/grpc/delete_test.go @@ -89,7 +89,6 @@ func TestDelete(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -102,7 +101,7 @@ func TestDelete(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/get_all_test.go b/internal/api/dashboards/v1/grpc/get_all_test.go index 1a87bab..29673b1 100644 --- a/internal/api/dashboards/v1/grpc/get_all_test.go +++ b/internal/api/dashboards/v1/grpc/get_all_test.go @@ -107,7 +107,6 @@ func TestGetAll(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -120,7 +119,7 @@ func TestGetAll(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/get_by_uuid_test.go b/internal/api/dashboards/v1/grpc/get_by_uuid_test.go index c2a459e..7085af9 100644 --- a/internal/api/dashboards/v1/grpc/get_by_uuid_test.go +++ b/internal/api/dashboards/v1/grpc/get_by_uuid_test.go @@ -93,7 +93,6 @@ func TestGetByUUID(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -106,7 +105,7 @@ func TestGetByUUID(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/get_my_test.go b/internal/api/dashboards/v1/grpc/get_my_test.go index 27b2c34..678c6d9 100644 --- a/internal/api/dashboards/v1/grpc/get_my_test.go +++ b/internal/api/dashboards/v1/grpc/get_my_test.go @@ -97,7 +97,6 @@ func TestGetMy(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -110,7 +109,7 @@ func TestGetMy(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/search_test.go b/internal/api/dashboards/v1/grpc/search_test.go index 04f66d2..9fb0378 100644 --- a/internal/api/dashboards/v1/grpc/search_test.go +++ b/internal/api/dashboards/v1/grpc/search_test.go @@ -147,7 +147,6 @@ func TestSearch(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -160,7 +159,7 @@ func TestSearch(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/grpc/update_test.go b/internal/api/dashboards/v1/grpc/update_test.go index b3c784f..d220f71 100644 --- a/internal/api/dashboards/v1/grpc/update_test.go +++ b/internal/api/dashboards/v1/grpc/update_test.go @@ -162,7 +162,6 @@ func TestUpdate(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -175,7 +174,7 @@ func TestUpdate(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/create_test.go b/internal/api/dashboards/v1/http/create_test.go index 9bd86d5..add93e1 100644 --- a/internal/api/dashboards/v1/http/create_test.go +++ b/internal/api/dashboards/v1/http/create_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "errors" "fmt" "net/http" @@ -92,7 +91,6 @@ func TestServeCreate(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -104,7 +102,7 @@ func TestServeCreate(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/delete_test.go b/internal/api/dashboards/v1/http/delete_test.go index fc50e03..dd5c6d9 100644 --- a/internal/api/dashboards/v1/http/delete_test.go +++ b/internal/api/dashboards/v1/http/delete_test.go @@ -80,7 +80,6 @@ func TestServeDelete(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -95,7 +94,7 @@ func TestServeDelete(t *testing.T) { Return(tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/get_all.go b/internal/api/dashboards/v1/http/get_all.go index 3c1fb6d..0355f16 100644 --- a/internal/api/dashboards/v1/http/get_all.go +++ b/internal/api/dashboards/v1/http/get_all.go @@ -14,7 +14,7 @@ import ( // serveGetAll go doc. // // @Router /dashboards/v1/all [post] -// @ID dashboards_v1_getAll +// @ID dashboards_v1_get_all // @Tags dashboards_v1 // @Param body body getAllRequest true "Request body" // @Success 200 {object} getAllResponse "A successful response" diff --git a/internal/api/dashboards/v1/http/get_all_test.go b/internal/api/dashboards/v1/http/get_all_test.go index 927bb52..e4a5787 100644 --- a/internal/api/dashboards/v1/http/get_all_test.go +++ b/internal/api/dashboards/v1/http/get_all_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "errors" "fmt" "net/http" @@ -104,7 +103,6 @@ func TestServeGetAll(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -116,7 +114,7 @@ func TestServeGetAll(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/get_by_uuid.go b/internal/api/dashboards/v1/http/get_by_uuid.go index 19bed79..808f14c 100644 --- a/internal/api/dashboards/v1/http/get_by_uuid.go +++ b/internal/api/dashboards/v1/http/get_by_uuid.go @@ -12,7 +12,7 @@ import ( // serveGetByUUID go doc. // // @Router /dashboards/v1/{uuid} [get] -// @ID dashboards_v1_getByUUID +// @ID dashboards_v1_get_by_uuid // @Tags dashboards_v1 // @Param uuid path string true "Dashboard UUID" // @Success 200 {object} dashboard "A successful response" diff --git a/internal/api/dashboards/v1/http/get_by_uuid_test.go b/internal/api/dashboards/v1/http/get_by_uuid_test.go index cde63ce..aca84e9 100644 --- a/internal/api/dashboards/v1/http/get_by_uuid_test.go +++ b/internal/api/dashboards/v1/http/get_by_uuid_test.go @@ -79,7 +79,6 @@ func TestServeGetByUUID(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -94,7 +93,7 @@ func TestServeGetByUUID(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/get_my.go b/internal/api/dashboards/v1/http/get_my.go index 80f0258..1315fa8 100644 --- a/internal/api/dashboards/v1/http/get_my.go +++ b/internal/api/dashboards/v1/http/get_my.go @@ -14,7 +14,7 @@ import ( // serveGetMy go doc. // // @Router /dashboards/v1/my [post] -// @ID dashboards_v1_getMy +// @ID dashboards_v1_get_my // @Tags dashboards_v1 // @Param body body getMyRequest true "Request body" // @Success 200 {object} getMyResponse "A successful response" diff --git a/internal/api/dashboards/v1/http/get_my_test.go b/internal/api/dashboards/v1/http/get_my_test.go index 30f7dcf..3ac4a08 100644 --- a/internal/api/dashboards/v1/http/get_my_test.go +++ b/internal/api/dashboards/v1/http/get_my_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "errors" "fmt" "net/http" @@ -94,7 +93,6 @@ func TestServeGetMy(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -106,7 +104,7 @@ func TestServeGetMy(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/search_test.go b/internal/api/dashboards/v1/http/search_test.go index c825936..5f4ac28 100644 --- a/internal/api/dashboards/v1/http/search_test.go +++ b/internal/api/dashboards/v1/http/search_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "errors" "fmt" "net/http" @@ -26,11 +25,11 @@ func TestServeSearch(t *testing.T) { formatReqBody := func(query string, limit, offset int, filter *types.SearchDashboardsFilter) string { var sb strings.Builder - sb.WriteString(fmt.Sprintf(`{"query":%q,"limit":%d,"offset":%d`, query, limit, offset)) + fmt.Fprintf(&sb, `{"query":%q,"limit":%d,"offset":%d`, query, limit, offset) if filter != nil { sb.WriteString(`,"filter":{`) if filter.OwnerName != nil { - sb.WriteString(fmt.Sprintf(`"owner_name":%q`, *filter.OwnerName)) + fmt.Fprintf(&sb, `"owner_name":%q`, *filter.OwnerName) } sb.WriteString("}") } @@ -143,7 +142,6 @@ func TestServeSearch(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -155,7 +153,7 @@ func TestServeSearch(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/dashboards/v1/http/update_test.go b/internal/api/dashboards/v1/http/update_test.go index bf0d29f..25761a7 100644 --- a/internal/api/dashboards/v1/http/update_test.go +++ b/internal/api/dashboards/v1/http/update_test.go @@ -26,13 +26,13 @@ func TestServeUpdate(t *testing.T) { var sb strings.Builder sb.WriteString("{") if name != "" { - sb.WriteString(fmt.Sprintf(`"name":%q`, name)) + fmt.Fprintf(&sb, `"name":%q`, name) } if meta != "" { if name != "" { sb.WriteString(",") } - sb.WriteString(fmt.Sprintf(`"meta":%q`, meta)) + fmt.Fprintf(&sb, `"meta":%q`, meta) } sb.WriteString("}") return sb.String() @@ -164,7 +164,6 @@ func TestServeUpdate(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -179,7 +178,7 @@ func TestServeUpdate(t *testing.T) { Return(tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/errorgroups/v1/grpc/get_details_test.go b/internal/api/errorgroups/v1/grpc/get_details_test.go index 967cc37..8f1e71b 100644 --- a/internal/api/errorgroups/v1/grpc/get_details_test.go +++ b/internal/api/errorgroups/v1/grpc/get_details_test.go @@ -152,7 +152,6 @@ func TestGetDetails(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/grpc/get_groups_test.go b/internal/api/errorgroups/v1/grpc/get_groups_test.go index 31f3c57..c8b1fa5 100644 --- a/internal/api/errorgroups/v1/grpc/get_groups_test.go +++ b/internal/api/errorgroups/v1/grpc/get_groups_test.go @@ -169,7 +169,6 @@ func TestGetGroups(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/grpc/get_hist_test.go b/internal/api/errorgroups/v1/grpc/get_hist_test.go index 010b41c..8439130 100644 --- a/internal/api/errorgroups/v1/grpc/get_hist_test.go +++ b/internal/api/errorgroups/v1/grpc/get_hist_test.go @@ -110,7 +110,6 @@ func TestGetHist(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/grpc/get_releases_test.go b/internal/api/errorgroups/v1/grpc/get_releases_test.go index 206786f..70eee96 100644 --- a/internal/api/errorgroups/v1/grpc/get_releases_test.go +++ b/internal/api/errorgroups/v1/grpc/get_releases_test.go @@ -116,7 +116,6 @@ func TestGetReleases(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/grpc/get_services_test.go b/internal/api/errorgroups/v1/grpc/get_services_test.go index 8df4a5a..959b36e 100644 --- a/internal/api/errorgroups/v1/grpc/get_services_test.go +++ b/internal/api/errorgroups/v1/grpc/get_services_test.go @@ -103,7 +103,6 @@ func TestGetServices(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/http/get_details_test.go b/internal/api/errorgroups/v1/http/get_details_test.go index 24a6567..f3ce59e 100644 --- a/internal/api/errorgroups/v1/http/get_details_test.go +++ b/internal/api/errorgroups/v1/http/get_details_test.go @@ -127,7 +127,6 @@ func TestServeGetDetails(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/http/get_groups_test.go b/internal/api/errorgroups/v1/http/get_groups_test.go index bc26e46..60cc2d7 100644 --- a/internal/api/errorgroups/v1/http/get_groups_test.go +++ b/internal/api/errorgroups/v1/http/get_groups_test.go @@ -134,7 +134,6 @@ func TestServeGetGroups(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/http/get_hist_test.go b/internal/api/errorgroups/v1/http/get_hist_test.go index 2106bdf..0464c1c 100644 --- a/internal/api/errorgroups/v1/http/get_hist_test.go +++ b/internal/api/errorgroups/v1/http/get_hist_test.go @@ -102,7 +102,6 @@ func TestServeGetHist(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/http/get_releases_test.go b/internal/api/errorgroups/v1/http/get_releases_test.go index 3100312..218d96b 100644 --- a/internal/api/errorgroups/v1/http/get_releases_test.go +++ b/internal/api/errorgroups/v1/http/get_releases_test.go @@ -87,7 +87,6 @@ func TestServeGetReleases(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/errorgroups/v1/http/get_services_test.go b/internal/api/errorgroups/v1/http/get_services_test.go index c60c22a..2bf712b 100644 --- a/internal/api/errorgroups/v1/http/get_services_test.go +++ b/internal/api/errorgroups/v1/http/get_services_test.go @@ -91,7 +91,6 @@ func TestServeGetServices(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/httputil/error.go b/internal/api/httputil/error.go index ce5d5b4..0070478 100644 --- a/internal/api/httputil/error.go +++ b/internal/api/httputil/error.go @@ -15,7 +15,7 @@ func ProcessError(w *Writer, err error) { switch { case err == nil: return - case errors.Is(err, types.ErrUnauthenticated) || errors.Is(err, types.ErrBadUserKeyValueType): + case errors.Is(err, types.ErrUnauthenticated): w.Error(err, http.StatusUnauthorized) case errors.Is(err, types.ErrEmptyUpdateRequest) || errors.Is(err, types.ErrInvalidRequestField): w.Error(err, http.StatusBadRequest) diff --git a/internal/api/registrar.go b/internal/api/registrar.go index 70d4f67..ecc8ca8 100644 --- a/internal/api/registrar.go +++ b/internal/api/registrar.go @@ -14,11 +14,13 @@ package api import ( "github.com/go-chi/chi/v5" + admin_v1_api "github.com/ozontech/seq-ui/internal/api/admin/v1" dashboards_v1_api "github.com/ozontech/seq-ui/internal/api/dashboards/v1" errorgroups_v1_api "github.com/ozontech/seq-ui/internal/api/errorgroups/v1" massexport_v1_api "github.com/ozontech/seq-ui/internal/api/massexport/v1" seqapi_v1_api "github.com/ozontech/seq-ui/internal/api/seqapi/v1" userprofile_v1_api "github.com/ozontech/seq-ui/internal/api/userprofile/v1" + admin_v1 "github.com/ozontech/seq-ui/pkg/admin/v1" dashboards_v1 "github.com/ozontech/seq-ui/pkg/dashboards/v1" errorgroups_v1 "github.com/ozontech/seq-ui/pkg/errorgroups/v1" massexport_v1 "github.com/ozontech/seq-ui/pkg/massexport/v1" @@ -29,6 +31,7 @@ import ( // Registrar is registrar of gRPC and gRPC-Gateway handlers. type Registrar struct { + adminV1 *admin_v1_api.Admin seqApiV1 *seqapi_v1_api.SeqAPI userProfileV1 *userprofile_v1_api.UserProfile dashboardsV1 *dashboards_v1_api.Dashboards @@ -38,6 +41,7 @@ type Registrar struct { // NewRegistrar returns new registrar instance. func NewRegistrar( + adminV1 *admin_v1_api.Admin, seqApiV1 *seqapi_v1_api.SeqAPI, userProfileV1 *userprofile_v1_api.UserProfile, dashboardsV1 *dashboards_v1_api.Dashboards, @@ -45,6 +49,7 @@ func NewRegistrar( errorGroupsV1 *errorgroups_v1_api.ErrorGroups, ) *Registrar { return &Registrar{ + adminV1: adminV1, seqApiV1: seqApiV1, userProfileV1: userProfileV1, dashboardsV1: dashboardsV1, @@ -70,6 +75,9 @@ func (r *Registrar) RegisterGRPCHandlers(grpcServer *grpc.Server) { if r.errorGroupsV1 != nil { errorgroups_v1.RegisterErrorGroupsServiceServer(grpcServer, r.errorGroupsV1.GRPCServer()) } + if r.adminV1 != nil { + admin_v1.RegisterAdminServiceServer(grpcServer, r.adminV1.GRPCServer()) + } } // RegisterHTTPHandlers registers all handlers for mux. @@ -89,4 +97,7 @@ func (r *Registrar) RegisterHTTPHandlers(mux *chi.Mux) { if r.errorGroupsV1 != nil { mux.Mount("/errorgroups/v1", r.errorGroupsV1.HTTPRouter()) } + if r.adminV1 != nil { + mux.Mount("/admin/v1", r.adminV1.HTTPRouter()) + } } diff --git a/internal/api/seqapi/v1/grpc/aggregation_test.go b/internal/api/seqapi/v1/grpc/aggregation_test.go index 2166159..0efc647 100644 --- a/internal/api/seqapi/v1/grpc/aggregation_test.go +++ b/internal/api/seqapi/v1/grpc/aggregation_test.go @@ -90,7 +90,6 @@ func TestGetAggregation(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -227,7 +226,6 @@ func TestGetAggregationWithNormalization(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/cancel_async_search_test.go b/internal/api/seqapi/v1/grpc/cancel_async_search_test.go index 610cd92..bf4ec7f 100644 --- a/internal/api/seqapi/v1/grpc/cancel_async_search_test.go +++ b/internal/api/seqapi/v1/grpc/cancel_async_search_test.go @@ -135,7 +135,7 @@ func TestServeCancelAsyncSearch(t *testing.T) { api := initTestAPIWithAsyncSearches(seqData) ctx := context.Background() - ctx = context.WithValue(ctx, types.UserKey{}, tt.mockArgs.userName) + ctx = types.SetUserKey(ctx, tt.mockArgs.userName) resp, err := api.CancelAsyncSearch(ctx, tt.req) if tt.err == nil { diff --git a/internal/api/seqapi/v1/grpc/cluster_status_test.go b/internal/api/seqapi/v1/grpc/cluster_status_test.go index 5a0d73e..1a3dd19 100644 --- a/internal/api/seqapi/v1/grpc/cluster_status_test.go +++ b/internal/api/seqapi/v1/grpc/cluster_status_test.go @@ -54,7 +54,6 @@ func TestStatus(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/delete_async_search_test.go b/internal/api/seqapi/v1/grpc/delete_async_search_test.go index f785ad4..3141f50 100644 --- a/internal/api/seqapi/v1/grpc/delete_async_search_test.go +++ b/internal/api/seqapi/v1/grpc/delete_async_search_test.go @@ -146,7 +146,7 @@ func TestServeDeleteAsyncSearch(t *testing.T) { api := initTestAPIWithAsyncSearches(seqData) ctx := context.Background() - ctx = context.WithValue(ctx, types.UserKey{}, tt.mockArgs.userName) + ctx = types.SetUserKey(ctx, tt.mockArgs.userName) resp, err := api.DeleteAsyncSearch(ctx, tt.req) if tt.err == nil { diff --git a/internal/api/seqapi/v1/grpc/events_test.go b/internal/api/seqapi/v1/grpc/events_test.go index d5d9cab..956748b 100644 --- a/internal/api/seqapi/v1/grpc/events_test.go +++ b/internal/api/seqapi/v1/grpc/events_test.go @@ -97,7 +97,6 @@ func TestGetEvent(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -350,13 +349,11 @@ func TestGetEventWithMasking(t *testing.T) { } eventsData := make([]eventData, 0, len(tests)) - for i := 0; i < len(tests); i++ { + for i := range tests { eventsData = append(eventsData, formEventData(i, tests[i].shouldMask)) } for i, tt := range tests { - i := i - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/fields_test.go b/internal/api/seqapi/v1/grpc/fields_test.go index a2d63fb..7c781e5 100644 --- a/internal/api/seqapi/v1/grpc/fields_test.go +++ b/internal/api/seqapi/v1/grpc/fields_test.go @@ -103,7 +103,6 @@ func TestGetFields(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -212,7 +211,6 @@ func TestGetPinnedFields(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/get_envs_test.go b/internal/api/seqapi/v1/grpc/get_envs_test.go index 5dbbd8e..968fc5d 100644 --- a/internal/api/seqapi/v1/grpc/get_envs_test.go +++ b/internal/api/seqapi/v1/grpc/get_envs_test.go @@ -140,7 +140,6 @@ func TestGetEnvs(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() api := API{ diff --git a/internal/api/seqapi/v1/grpc/histogram_test.go b/internal/api/seqapi/v1/grpc/histogram_test.go index 95967bc..3940ab6 100644 --- a/internal/api/seqapi/v1/grpc/histogram_test.go +++ b/internal/api/seqapi/v1/grpc/histogram_test.go @@ -53,7 +53,6 @@ func TestGetHistogram(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/limits_test.go b/internal/api/seqapi/v1/grpc/limits_test.go index 971198c..996745b 100644 --- a/internal/api/seqapi/v1/grpc/limits_test.go +++ b/internal/api/seqapi/v1/grpc/limits_test.go @@ -43,7 +43,6 @@ func TestGetLimits(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/logs_lifespan_test.go b/internal/api/seqapi/v1/grpc/logs_lifespan_test.go index 3b884ff..c20953e 100644 --- a/internal/api/seqapi/v1/grpc/logs_lifespan_test.go +++ b/internal/api/seqapi/v1/grpc/logs_lifespan_test.go @@ -104,7 +104,6 @@ func TestGetLogsLifespan(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/search_test.go b/internal/api/seqapi/v1/grpc/search_test.go index 478a8b4..905cb46 100644 --- a/internal/api/seqapi/v1/grpc/search_test.go +++ b/internal/api/seqapi/v1/grpc/search_test.go @@ -172,7 +172,6 @@ func TestSearch(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/grpc/start_async_search_test.go b/internal/api/seqapi/v1/grpc/start_async_search_test.go index 14c0dd9..70e9456 100644 --- a/internal/api/seqapi/v1/grpc/start_async_search_test.go +++ b/internal/api/seqapi/v1/grpc/start_async_search_test.go @@ -118,7 +118,7 @@ func TestServeStartAsyncSearch(t *testing.T) { api := initTestAPIWithAsyncSearches(seqData) ctx := context.Background() - ctx = context.WithValue(ctx, types.UserKey{}, mockUserName) + ctx = types.SetUserKey(ctx, mockUserName) resp, err := api.StartAsyncSearch(ctx, tt.req) require.NoError(t, err) diff --git a/internal/api/seqapi/v1/http/aggregation.go b/internal/api/seqapi/v1/http/aggregation.go index 1395255..87df6fd 100644 --- a/internal/api/seqapi/v1/http/aggregation.go +++ b/internal/api/seqapi/v1/http/aggregation.go @@ -17,7 +17,7 @@ import ( // serveGetAggregation go doc. // // @Router /seqapi/v1/aggregation [post] -// @ID seqapi_v1_getAggregation +// @ID seqapi_v1_get_aggregation // @Tags seqapi_v1 // @Accept json // @Produce json diff --git a/internal/api/seqapi/v1/http/aggregation_test.go b/internal/api/seqapi/v1/http/aggregation_test.go index f3fcdf5..30533ed 100644 --- a/internal/api/seqapi/v1/http/aggregation_test.go +++ b/internal/api/seqapi/v1/http/aggregation_test.go @@ -221,7 +221,6 @@ func TestServeGetAggregation(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/aggregation_ts.go b/internal/api/seqapi/v1/http/aggregation_ts.go index 1ea702d..9cd58ff 100644 --- a/internal/api/seqapi/v1/http/aggregation_ts.go +++ b/internal/api/seqapi/v1/http/aggregation_ts.go @@ -19,7 +19,7 @@ import ( // serveGetAggregationTs go doc. // // @Router /seqapi/v1/aggregation_ts [post] -// @ID seqapi_v1_getAggregationTs +// @ID seqapi_v1_get_aggregation_ts // @Tags seqapi_v1 // @Accept json // @Produce json diff --git a/internal/api/seqapi/v1/http/cancel_async_search_test.go b/internal/api/seqapi/v1/http/cancel_async_search_test.go index 053df8b..3ea0168 100644 --- a/internal/api/seqapi/v1/http/cancel_async_search_test.go +++ b/internal/api/seqapi/v1/http/cancel_async_search_test.go @@ -147,7 +147,7 @@ func TestServeCancelAsyncSearch(t *testing.T) { fmt.Sprintf("/seqapi/v1/async_search/%s/cancel", tt.mockArgs.proxyReq.SearchId), http.NoBody, ) - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, tt.mockArgs.userName)) + req = req.WithContext(types.SetUserKey(req.Context(), tt.mockArgs.userName)) rCtx := chi.NewRouteContext() rCtx.URLParams.Add("id", tt.mockArgs.proxyReq.SearchId) req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rCtx)) diff --git a/internal/api/seqapi/v1/http/cluster_status_test.go b/internal/api/seqapi/v1/http/cluster_status_test.go index 3ef9056..5ca1a40 100644 --- a/internal/api/seqapi/v1/http/cluster_status_test.go +++ b/internal/api/seqapi/v1/http/cluster_status_test.go @@ -59,7 +59,6 @@ func TestStatus(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) diff --git a/internal/api/seqapi/v1/http/delete_async_search_test.go b/internal/api/seqapi/v1/http/delete_async_search_test.go index 878adf6..e739ce2 100644 --- a/internal/api/seqapi/v1/http/delete_async_search_test.go +++ b/internal/api/seqapi/v1/http/delete_async_search_test.go @@ -157,7 +157,7 @@ func TestServeDeleteAsyncSearch(t *testing.T) { fmt.Sprintf("/seqapi/v1/async_search/%s", tt.mockArgs.proxyReq.SearchId), http.NoBody, ) - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, tt.mockArgs.userName)) + req = req.WithContext(types.SetUserKey(req.Context(), tt.mockArgs.userName)) rCtx := chi.NewRouteContext() rCtx.URLParams.Add("id", tt.mockArgs.proxyReq.SearchId) req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rCtx)) diff --git a/internal/api/seqapi/v1/http/events.go b/internal/api/seqapi/v1/http/events.go index 80c7e2e..b8f6fe2 100644 --- a/internal/api/seqapi/v1/http/events.go +++ b/internal/api/seqapi/v1/http/events.go @@ -17,7 +17,7 @@ import ( // serveGetEvent go doc. // // @Router /seqapi/v1/events/{id} [get] -// @ID seqapi_v1_getEvent +// @ID seqapi_v1_get_event // @Tags seqapi_v1 // @Param env query string false "Environment" // @Param id path string true "Event ID" diff --git a/internal/api/seqapi/v1/http/events_test.go b/internal/api/seqapi/v1/http/events_test.go index f4dd625..20e86c9 100644 --- a/internal/api/seqapi/v1/http/events_test.go +++ b/internal/api/seqapi/v1/http/events_test.go @@ -119,7 +119,6 @@ func TestServeGetEvent(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) @@ -378,13 +377,11 @@ func TestGetEventWithMasking(t *testing.T) { } eventsData := make([]eventData, 0, len(tests)) - for i := 0; i < len(tests); i++ { + for i := range tests { eventsData = append(eventsData, formEventData(i, tests[i].shouldMask)) } for i, tt := range tests { - i := i - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) diff --git a/internal/api/seqapi/v1/http/export_test.go b/internal/api/seqapi/v1/http/export_test.go index ad69b9e..5ddb5e9 100644 --- a/internal/api/seqapi/v1/http/export_test.go +++ b/internal/api/seqapi/v1/http/export_test.go @@ -28,16 +28,16 @@ func TestServeExport(t *testing.T) { formatReqBody := func(limit int, format exportFormat, fields []string) string { var sb strings.Builder - sb.WriteString(fmt.Sprintf(`{"query":%q,"from":%q,"to":%q,"offset":0,"limit":%d`, - query, from.Format(time.RFC3339), to.Format(time.RFC3339), limit)) + fmt.Fprintf(&sb, `{"query":%q,"from":%q,"to":%q,"offset":0,"limit":%d`, + query, from.Format(time.RFC3339), to.Format(time.RFC3339), limit) if format != "" { - sb.WriteString(fmt.Sprintf(`,"format":%q`, format)) + fmt.Fprintf(&sb, `,"format":%q`, format) } if len(fields) > 0 { fieldsRaw, err := json.Marshal(fields) assert.NoError(t, err) - sb.WriteString(fmt.Sprintf(`,"fields":%s`, fieldsRaw)) + fmt.Fprintf(&sb, `,"fields":%s`, fieldsRaw) } sb.WriteString("}") @@ -160,7 +160,6 @@ func TestServeExport(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/fetch_async_search_result.go b/internal/api/seqapi/v1/http/fetch_async_search_result.go index 8bbe311..7c4c93d 100644 --- a/internal/api/seqapi/v1/http/fetch_async_search_result.go +++ b/internal/api/seqapi/v1/http/fetch_async_search_result.go @@ -115,7 +115,7 @@ type asyncSearchResponse struct { AggregationsTs aggregationsTs `json:"aggregations_ts,omitempty"` Total string `json:"total,omitempty" format:"int64"` Error apiError `json:"error"` -} // @name seqapi.v1.AsyncSearchResponse +} // @name seqapi.v1.AsyncSearchResponse func fetchAsyncSearchResultResponseFromProto(resp *seqapi.FetchAsyncSearchResultResponse) fetchAsyncSearchResultResponse { var canceledAt *time.Time diff --git a/internal/api/seqapi/v1/http/fields.go b/internal/api/seqapi/v1/http/fields.go index 72a883f..00c1072 100644 --- a/internal/api/seqapi/v1/http/fields.go +++ b/internal/api/seqapi/v1/http/fields.go @@ -91,7 +91,7 @@ func (a *API) serveGetFields(w http.ResponseWriter, r *http.Request) { // serveGetPinnedFields go doc. // // @Router /seqapi/v1/fields/pinned [get] -// @ID seqapi_v1_getPinnedFields +// @ID seqapi_v1_get_pinned_fields // @Tags seqapi_v1 // @Param env query string false "Environment" // @Success 200 {object} getFieldsResponse "A successful response" diff --git a/internal/api/seqapi/v1/http/fields_test.go b/internal/api/seqapi/v1/http/fields_test.go index c92e455..e9d520b 100644 --- a/internal/api/seqapi/v1/http/fields_test.go +++ b/internal/api/seqapi/v1/http/fields_test.go @@ -92,7 +92,6 @@ func TestServeGetFields(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) @@ -221,7 +220,6 @@ func TestServeGetPinnedFields(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/get_envs.go b/internal/api/seqapi/v1/http/get_envs.go index 3c4fa1b..7805a78 100644 --- a/internal/api/seqapi/v1/http/get_envs.go +++ b/internal/api/seqapi/v1/http/get_envs.go @@ -20,7 +20,7 @@ func (a *API) serveGetEnvs(w http.ResponseWriter, r *http.Request) { type getEnvsResponse struct { Envs []envInfo `json:"envs"` -} // @name seqapi.v1.GetEnvsResponse +} // @name seqapi.v1.GetEnvsResponse type envInfo struct { Env string `json:"env,omitempty"` @@ -29,4 +29,4 @@ type envInfo struct { MaxParallelExportRequests uint32 `json:"max_parallel_export_requests"` MaxAggregationsPerRequest uint32 `json:"max_aggregations_per_request"` SeqCliMaxSearchLimit uint32 `json:"seq_cli_max_search_limit"` -} // @name seqapi.v1.EnvInfo +} // @name seqapi.v1.EnvInfo diff --git a/internal/api/seqapi/v1/http/get_envs_test.go b/internal/api/seqapi/v1/http/get_envs_test.go index 6916208..d498e14 100644 --- a/internal/api/seqapi/v1/http/get_envs_test.go +++ b/internal/api/seqapi/v1/http/get_envs_test.go @@ -143,7 +143,6 @@ func TestServeGetEnvs(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/histogram.go b/internal/api/seqapi/v1/http/histogram.go index d4855e8..e23a9a0 100644 --- a/internal/api/seqapi/v1/http/histogram.go +++ b/internal/api/seqapi/v1/http/histogram.go @@ -17,7 +17,7 @@ import ( // serveGetHistogram go doc. // // @Router /seqapi/v1/histogram [post] -// @ID seqapi_v1_getHistogram +// @ID seqapi_v1_get_histogram // @Tags seqapi_v1 // @Param env query string false "Environment" // @Param body body getHistogramRequest true "Request body" diff --git a/internal/api/seqapi/v1/http/histogram_test.go b/internal/api/seqapi/v1/http/histogram_test.go index 42db89b..23fe70a 100644 --- a/internal/api/seqapi/v1/http/histogram_test.go +++ b/internal/api/seqapi/v1/http/histogram_test.go @@ -104,7 +104,6 @@ func TestServeGetHistogram(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/limits.go b/internal/api/seqapi/v1/http/limits.go index 3d60473..b9f719a 100644 --- a/internal/api/seqapi/v1/http/limits.go +++ b/internal/api/seqapi/v1/http/limits.go @@ -10,7 +10,7 @@ import ( // serveGetLimits go doc. // // @Router /seqapi/v1/limits [get] -// @ID seqapi_v1_getLimits +// @ID seqapi_v1_get_limits // @Tags seqapi_v1 // @Param env query string false "Environment" // @Success 200 {object} getLimitsResponse "A successful response" diff --git a/internal/api/seqapi/v1/http/limits_test.go b/internal/api/seqapi/v1/http/limits_test.go index 28d3748..24aac63 100644 --- a/internal/api/seqapi/v1/http/limits_test.go +++ b/internal/api/seqapi/v1/http/limits_test.go @@ -38,7 +38,6 @@ func TestServeGetLimits(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/logs_lifespan_test.go b/internal/api/seqapi/v1/http/logs_lifespan_test.go index a272aac..4852729 100644 --- a/internal/api/seqapi/v1/http/logs_lifespan_test.go +++ b/internal/api/seqapi/v1/http/logs_lifespan_test.go @@ -105,7 +105,6 @@ func TestServeGetLogsLifespan(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/search_test.go b/internal/api/seqapi/v1/http/search_test.go index 19a6a8a..eaad304 100644 --- a/internal/api/seqapi/v1/http/search_test.go +++ b/internal/api/seqapi/v1/http/search_test.go @@ -29,22 +29,22 @@ func TestServeSearch(t *testing.T) { formatReqBody := func(limit, offset int, withTotal bool, histInterval string, aggQueries aggregationQueries, order string) string { var sb strings.Builder - sb.WriteString(fmt.Sprintf(`{"query":%q,"from":%q,"to":%q,"limit":%d,"offset":%d`, - query, from.Format(time.RFC3339), to.Format(time.RFC3339), limit, offset)) + fmt.Fprintf(&sb, `{"query":%q,"from":%q,"to":%q,"limit":%d,"offset":%d`, + query, from.Format(time.RFC3339), to.Format(time.RFC3339), limit, offset) if withTotal { - sb.WriteString(fmt.Sprintf(`,"withTotal":%v`, withTotal)) + fmt.Fprintf(&sb, `,"withTotal":%v`, withTotal) } if histInterval != "" { - sb.WriteString(fmt.Sprintf(`,"histogram":{"interval":%q}`, histInterval)) + fmt.Fprintf(&sb, `,"histogram":{"interval":%q}`, histInterval) } if len(aggQueries) > 0 { aggQueriesRaw, err := json.Marshal(aggQueries) assert.NoError(t, err) - sb.WriteString(fmt.Sprintf(`,"aggregations":%s`, aggQueriesRaw)) + fmt.Fprintf(&sb, `,"aggregations":%s`, aggQueriesRaw) } if order != "" { - sb.WriteString(fmt.Sprintf(`,"order":%q`, order)) + fmt.Fprintf(&sb, `,"order":%q`, order) } sb.WriteString("}") @@ -367,7 +367,6 @@ func TestServeSearch(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/internal/api/seqapi/v1/http/start_async_search_test.go b/internal/api/seqapi/v1/http/start_async_search_test.go index d1ac82d..c705221 100644 --- a/internal/api/seqapi/v1/http/start_async_search_test.go +++ b/internal/api/seqapi/v1/http/start_async_search_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -137,7 +136,7 @@ func TestServeStartAsyncSearch(t *testing.T) { api := initTestAPIWithAsyncSearches(seqData) req := httptest.NewRequest(http.MethodPost, "/seqapi/v1/async_search/start", strings.NewReader(tt.reqBody)) - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, mockUserName)) + req = req.WithContext(types.SetUserKey(req.Context(), mockUserName)) httputil.DoTestHTTP(t, httputil.TestDataHTTP{ Req: req, diff --git a/internal/api/userprofile/v1/grpc/favorite_queries_test.go b/internal/api/userprofile/v1/grpc/favorite_queries_test.go index c82ce3a..50237a9 100644 --- a/internal/api/userprofile/v1/grpc/favorite_queries_test.go +++ b/internal/api/userprofile/v1/grpc/favorite_queries_test.go @@ -110,7 +110,6 @@ func TestGetFavoriteQueries(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -123,7 +122,7 @@ func TestGetFavoriteQueries(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } @@ -227,7 +226,6 @@ func TestCreateFavoriteQuery(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -240,7 +238,7 @@ func TestCreateFavoriteQuery(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } @@ -318,7 +316,6 @@ func TestDeleteFavoriteQuery(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -331,7 +328,7 @@ func TestDeleteFavoriteQuery(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/userprofile/v1/grpc/user_profiles_test.go b/internal/api/userprofile/v1/grpc/user_profiles_test.go index d5feeb2..edc0701 100644 --- a/internal/api/userprofile/v1/grpc/user_profiles_test.go +++ b/internal/api/userprofile/v1/grpc/user_profiles_test.go @@ -72,7 +72,6 @@ func TestGetUserProfile(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -85,7 +84,7 @@ func TestGetUserProfile(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) } got, err := api.GetUserProfile(ctx, &userprofile.GetUserProfileRequest{}) @@ -243,7 +242,6 @@ func TestUpdateUserProfile(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -256,7 +254,7 @@ func TestUpdateUserProfile(t *testing.T) { ctx := context.Background() if !tt.noUser { - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) } got, err := api.UpdateUserProfile(ctx, tt.req) diff --git a/internal/api/userprofile/v1/http/favorite_queries.go b/internal/api/userprofile/v1/http/favorite_queries.go index 4f375b2..c4167ff 100644 --- a/internal/api/userprofile/v1/http/favorite_queries.go +++ b/internal/api/userprofile/v1/http/favorite_queries.go @@ -17,7 +17,7 @@ import ( // serveGetFavoriteQueries go doc. // // @Router /userprofile/v1/queries/favorite [get] -// @ID userprofile_v1_getFavoriteQueries +// @ID userprofile_v1_get_favorite_queries // @Tags userprofile_v1 // @Success 200 {object} getFavoriteQueriesResponse "A successful response" // @Failure default {object} httputil.Error "An unexpected error response" @@ -51,7 +51,7 @@ func (a *API) serveGetFavoriteQueries(w http.ResponseWriter, r *http.Request) { // serveCreateFavoriteQuery go doc. // // @Router /userprofile/v1/queries/favorite [post] -// @ID userprofile_v1_createFavoriteQuery +// @ID userprofile_v1_create_favorite_query // @Tags userprofile_v1 // @Param body body createFavoriteQueryRequest true "Request body" // @Success 200 {object} createFavoriteQueryResponse "A successful response" @@ -118,7 +118,7 @@ func (a *API) serveCreateFavoriteQuery(w http.ResponseWriter, r *http.Request) { // serveDeleteFavoriteQuery go doc. // // @Router /userprofile/v1/queries/favorite/{id} [delete] -// @ID userprofile_v1_deleteFavoriteQuery +// @ID userprofile_v1_delete_favorite_query // @Tags userprofile_v1 // @Param id path string true "Favorite Query ID" Format(int64) // @Success 200 {object} nil "A successful response" diff --git a/internal/api/userprofile/v1/http/favorite_queries_test.go b/internal/api/userprofile/v1/http/favorite_queries_test.go index b07c7ca..a419993 100644 --- a/internal/api/userprofile/v1/http/favorite_queries_test.go +++ b/internal/api/userprofile/v1/http/favorite_queries_test.go @@ -83,7 +83,6 @@ func TestServeGetFavoriteQueries(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -95,7 +94,7 @@ func TestServeGetFavoriteQueries(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } @@ -117,12 +116,12 @@ func TestServeCreateFavoriteQuery(t *testing.T) { formatReqBody := func(query, name, relativeFrom string) string { var sb strings.Builder - sb.WriteString(fmt.Sprintf(`{"query":%q`, query)) + fmt.Fprintf(&sb, `{"query":%q`, query) if name != "" { - sb.WriteString(fmt.Sprintf(`,"name":%q`, name)) + fmt.Fprintf(&sb, `,"name":%q`, name) } if relativeFrom != "" { - sb.WriteString(fmt.Sprintf(`,"relativeFrom":%q`, relativeFrom)) + fmt.Fprintf(&sb, `,"relativeFrom":%q`, relativeFrom) } sb.WriteString("}") return sb.String() @@ -208,7 +207,6 @@ func TestServeCreateFavoriteQuery(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -220,7 +218,7 @@ func TestServeCreateFavoriteQuery(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } @@ -294,7 +292,6 @@ func TestServeDeleteFavoriteQuery(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -309,7 +306,7 @@ func TestServeDeleteFavoriteQuery(t *testing.T) { Return(tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) api.profiles.SetID(userName, profileID) } diff --git a/internal/api/userprofile/v1/http/user_profiles.go b/internal/api/userprofile/v1/http/user_profiles.go index d95b310..a100fc2 100644 --- a/internal/api/userprofile/v1/http/user_profiles.go +++ b/internal/api/userprofile/v1/http/user_profiles.go @@ -14,7 +14,7 @@ import ( // serveGetUserProfile go doc. // // @Router /userprofile/v1/profile [get] -// @ID userprofile_v1_getUserProfile +// @ID userprofile_v1_get_user_profile // @Tags userprofile_v1 // @Success 200 {object} userProfile "A successful response" // @Failure default {object} httputil.Error "An unexpected error response" @@ -48,7 +48,7 @@ func (a *API) serveGetUserProfile(w http.ResponseWriter, r *http.Request) { // serveUpdateUserProfile go doc. // // @Router /userprofile/v1/profile [patch] -// @ID userprofile_v1_updateUserProfile +// @ID userprofile_v1_update_user_profile // @Tags userprofile_v1 // @Param body body updateUserProfileRequest true "Request body" // @Success 200 {object} nil "A successful response" diff --git a/internal/api/userprofile/v1/http/user_profiles_test.go b/internal/api/userprofile/v1/http/user_profiles_test.go index 57da0f2..ee1f3a5 100644 --- a/internal/api/userprofile/v1/http/user_profiles_test.go +++ b/internal/api/userprofile/v1/http/user_profiles_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "encoding/json" "errors" "fmt" @@ -70,7 +69,6 @@ func TestServeGetUserProfile(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -82,7 +80,7 @@ func TestServeGetUserProfile(t *testing.T) { Return(tt.mockArgs.resp, tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) } httputil.DoTestHTTP(t, httputil.TestDataHTTP{ @@ -106,20 +104,20 @@ func TestServeUpdateUserProfile(t *testing.T) { var sb strings.Builder sb.WriteString("{") if timezone != "" { - sb.WriteString(fmt.Sprintf(`"timezone":%q`, timezone)) + fmt.Fprintf(&sb, `"timezone":%q`, timezone) } if onboardingVersion != "" { if sb.Len() > 1 { sb.WriteString(",") } - sb.WriteString(fmt.Sprintf(`"onboardingVersion":%q`, onboardingVersion)) + fmt.Fprintf(&sb, `"onboardingVersion":%q`, onboardingVersion) } if logColumns != nil { if sb.Len() > 1 { sb.WriteString(",") } v, _ := json.Marshal(logColumns) - sb.WriteString(fmt.Sprintf(`"log_columns":{"columns":%s}`, v)) + fmt.Fprintf(&sb, `"log_columns":{"columns":%s}`, v) } sb.WriteString("}") return sb.String() @@ -244,7 +242,6 @@ func TestServeUpdateUserProfile(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -256,7 +253,7 @@ func TestServeUpdateUserProfile(t *testing.T) { Return(tt.mockArgs.err).Times(1) } if !tt.noUser { - req = req.WithContext(context.WithValue(req.Context(), types.UserKey{}, userName)) + req = req.WithContext(types.SetUserKey(req.Context(), userName)) } httputil.DoTestHTTP(t, httputil.TestDataHTTP{ diff --git a/internal/app/config/config.go b/internal/app/config/config.go index c744b57..8bb507c 100644 --- a/internal/app/config/config.go +++ b/internal/app/config/config.go @@ -239,6 +239,7 @@ type Handlers struct { ErrorGroups ErrorGroups `yaml:"error_groups"` MassExport *MassExport `yaml:"mass_export"` AsyncSearch AsyncSearch `yaml:"async_search"` + Admin *Admin `yaml:"admin"` } type Field struct { @@ -319,6 +320,10 @@ type AsyncSearch struct { ListQueryLengthLimit int `yaml:"list_query_length_limit"` } +type Admin struct { + SuperUsers []string `yaml:"super_users"` +} + // FromFile parse config from config path. func FromFile(cfgPath string) (Config, error) { cfgBytes, err := os.ReadFile(cfgPath) //nolint:gosec diff --git a/internal/app/mw/auth_test.go b/internal/app/mw/auth_test.go index fc56b91..1cd272c 100644 --- a/internal/app/mw/auth_test.go +++ b/internal/app/mw/auth_test.go @@ -124,7 +124,6 @@ func TestAuthProvidersAuth(t *testing.T) { } for _, tCase := range tCases { - tCase := tCase t.Run(tCase.name, func(t *testing.T) { t.Parallel() diff --git a/internal/app/mw/grpc_mw.go b/internal/app/mw/grpc_mw.go index 0b645ad..7489475 100644 --- a/internal/app/mw/grpc_mw.go +++ b/internal/app/mw/grpc_mw.go @@ -162,7 +162,7 @@ func GRPCAuthInterceptor(providers *AuthProviders) grpc.UnaryServerInterceptor { logger.Error("token auth failed", zap.Error(err)) return nil, errUnauth } - ctx = context.WithValue(ctx, types.UserKey{}, userName) + ctx = types.SetUserKey(ctx, userName) return h(ctx, req) } diff --git a/internal/app/mw/http_mw.go b/internal/app/mw/http_mw.go index abf00d0..173427c 100644 --- a/internal/app/mw/http_mw.go +++ b/internal/app/mw/http_mw.go @@ -168,7 +168,7 @@ func HTTPAuthInterceptor(providers *AuthProviders) func(next http.Handler) http. http.Error(w, "Unauthenticated", http.StatusUnauthorized) return } - r = r.WithContext(context.WithValue(ctx, types.UserKey{}, username)) + r = r.WithContext(types.SetUserKey(ctx, username)) next.ServeHTTP(w, r) } diff --git a/internal/app/mw/log.go b/internal/app/mw/log.go index 478a358..484f504 100644 --- a/internal/app/mw/log.go +++ b/internal/app/mw/log.go @@ -60,7 +60,7 @@ type requestLogArgs struct { clientError string serverError string user string - details []interface{} + details []any } // logRequestBeforeHandler logs incoming request before the handler call. diff --git a/internal/app/mw/parser_test.go b/internal/app/mw/parser_test.go index 7d773f0..b0103db 100644 --- a/internal/app/mw/parser_test.go +++ b/internal/app/mw/parser_test.go @@ -57,7 +57,6 @@ func TestParseURI(t *testing.T) { } for _, tCase := range tCases { - tCase := tCase t.Run(tCase.name, func(t *testing.T) { t.Parallel() @@ -107,7 +106,6 @@ func TestParseGRPCFullMethod(t *testing.T) { } for _, tCase := range tCases { - tCase := tCase t.Run(tCase.name, func(t *testing.T) { t.Parallel() diff --git a/internal/app/tokenlimiter/tokenlimiter_test.go b/internal/app/tokenlimiter/tokenlimiter_test.go index 497a04e..2868b31 100644 --- a/internal/app/tokenlimiter/tokenlimiter_test.go +++ b/internal/app/tokenlimiter/tokenlimiter_test.go @@ -7,6 +7,8 @@ import ( ) func TestLimiter(t *testing.T) { + t.Parallel() + user := "unnamed" maxTokens := 2 @@ -15,7 +17,7 @@ func TestLimiter(t *testing.T) { // check nil channel l.Fill(user) - for i := 0; i < maxTokens; i++ { + for range maxTokens { require.Equal(t, false, l.Limited(user)) } require.Equal(t, true, l.Limited(user)) @@ -23,7 +25,7 @@ func TestLimiter(t *testing.T) { l.Fill(user) require.Equal(t, false, l.Limited(user)) - for i := 0; i < maxTokens; i++ { + for range maxTokens { l.Fill(user) } diff --git a/internal/app/types/admin.go b/internal/app/types/admin.go new file mode 100644 index 0000000..5bfea4d --- /dev/null +++ b/internal/app/types/admin.go @@ -0,0 +1,85 @@ +package types + +type Role struct { + ID int32 + Name string + Permissions []uint64 +} + +type RoleRepo struct { + ID int32 + Name string + Permissions uint64 +} + +type RoleInfo struct { + Usernames []string +} + +type Permission struct { + Value uint64 + Name string + Description string +} + +type CreateRoleRequest struct { + Name string + Permissions []uint64 +} + +type CreateRoleRepoRequest struct { + Name string + Permissions uint64 +} + +type CreateRoleResponse struct { + RoleID int32 +} + +type AddUsersToRoleRequest struct { + RoleID int32 + Usernames []string +} + +type GetRolesResponse struct { + Roles []Role + AvailablePermissions []Permission +} + +type GetRoleRequest struct { + RoleID int32 +} + +type GetRoleResponse struct { + Usernames []string +} + +type UpdateRoleRequest struct { + RoleID int32 + Name *string + Permissions []uint64 +} + +type UpdateRoleRepoRequest struct { + RoleID int32 + Name *string + Permissions *uint64 +} + +type DeleteRoleRequest struct { + RoleID int32 + ReplacementRoleID *int32 +} + +type GetUserPermissionsRequest struct { + Username string +} + +type GetUserPermissionsResponse struct { + Permissions uint64 +} + +type DeleteUsersFromRoleRequest struct { + RoleID int32 + Usernames []string +} diff --git a/internal/app/types/ctx.go b/internal/app/types/ctx.go index b03cea1..b07e972 100644 --- a/internal/app/types/ctx.go +++ b/internal/app/types/ctx.go @@ -5,29 +5,23 @@ import ( "errors" ) -var ( - ErrUnauthenticated = errors.New("unauthenticated") - ErrBadUserKeyValueType = errors.New("invalid JWT token") -) +var ErrUnauthenticated = errors.New("unauthenticated") type UserKey struct{} +// SetUserKey returns a new context with the username. +func SetUserKey(ctx context.Context, username string) context.Context { + return context.WithValue(ctx, UserKey{}, username) +} + // GetUserKey returns username from context. func GetUserKey(ctx context.Context) (string, error) { - userStr := "" userVal := ctx.Value(UserKey{}) - if userVal == nil { return "", ErrUnauthenticated } - userStr, ok := userVal.(string) - // foolproof. - if !ok { - return "", ErrBadUserKeyValueType - } - - return userStr, nil + return userVal.(string), nil } type UseSeqQL struct{} diff --git a/internal/app/types/ctx_test.go b/internal/app/types/ctx_test.go index cbaec49..7408f4a 100644 --- a/internal/app/types/ctx_test.go +++ b/internal/app/types/ctx_test.go @@ -16,7 +16,7 @@ func TestGetUserKey(t *testing.T) { }{ { name: "success", - ctx: context.WithValue(context.Background(), UserKey{}, "unnamed"), + ctx: SetUserKey(context.Background(), "unnamed"), wantUserName: "unnamed", }, { @@ -24,15 +24,9 @@ func TestGetUserKey(t *testing.T) { ctx: context.Background(), wantErr: ErrUnauthenticated, }, - { - name: "err_bad_value_type", - ctx: context.WithValue(context.Background(), UserKey{}, 99999), - wantErr: ErrBadUserKeyValueType, - }, } for _, tCase := range tCases { - tCase := tCase t.Run(tCase.name, func(t *testing.T) { t.Parallel() diff --git a/internal/pkg/client/seqdb/export_test.go b/internal/pkg/client/seqdb/export_test.go index eedcc29..3d3e4b8 100644 --- a/internal/pkg/client/seqdb/export_test.go +++ b/internal/pkg/client/seqdb/export_test.go @@ -185,7 +185,6 @@ func Test_GRPCClient_Export(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/internal/pkg/client/seqdb/get_aggregation_test.go b/internal/pkg/client/seqdb/get_aggregation_test.go index 9bd55c0..29c4a15 100644 --- a/internal/pkg/client/seqdb/get_aggregation_test.go +++ b/internal/pkg/client/seqdb/get_aggregation_test.go @@ -180,7 +180,6 @@ func Test_GRPCClient_GetAggregation(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/internal/pkg/client/seqdb/get_event_test.go b/internal/pkg/client/seqdb/get_event_test.go index 10ee46b..3ecf8d1 100644 --- a/internal/pkg/client/seqdb/get_event_test.go +++ b/internal/pkg/client/seqdb/get_event_test.go @@ -159,7 +159,6 @@ func Test_GRPCClient_GetEvent(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctrl := gomock.NewController(t) diff --git a/internal/pkg/client/seqdb/get_fields_test.go b/internal/pkg/client/seqdb/get_fields_test.go index 85d21a8..a14110d 100644 --- a/internal/pkg/client/seqdb/get_fields_test.go +++ b/internal/pkg/client/seqdb/get_fields_test.go @@ -55,7 +55,6 @@ func Test_GRPCClient_GetFields(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/internal/pkg/client/seqdb/get_histogram_test.go b/internal/pkg/client/seqdb/get_histogram_test.go index 3c2cce1..9af66d4 100644 --- a/internal/pkg/client/seqdb/get_histogram_test.go +++ b/internal/pkg/client/seqdb/get_histogram_test.go @@ -115,7 +115,6 @@ func Test_GRPCClient_GetHistogram(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/internal/pkg/client/seqdb/search_test.go b/internal/pkg/client/seqdb/search_test.go index a803c36..ee41acc 100644 --- a/internal/pkg/client/seqdb/search_test.go +++ b/internal/pkg/client/seqdb/search_test.go @@ -24,12 +24,12 @@ func Test_GRPCClient_Search(t *testing.T) { eventTime := timestamppb.New(time.Date(2024, time.December, 31, 10, 20, 30, 400000, time.UTC)) events := make([]*seqapi.Event, limit) - for i := 0; i < len(events); i++ { + for i := range events { events[i] = makeEvent(fmt.Sprintf("test%d", i+1), i+1, eventTime) } docs := make([]*seqproxyapi.Document, 0, len(events)) - for i := 0; i < len(events); i++ { + for i := range events { data, err := json.Marshal(events[i].Data) assert.NoError(t, err) docs = append(docs, &seqproxyapi.Document{ @@ -272,7 +272,6 @@ func Test_GRPCClient_Search(t *testing.T) { }, } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ctx := context.Background() diff --git a/internal/pkg/repository/admin_repo.go b/internal/pkg/repository/admin_repo.go new file mode 100644 index 0000000..3d2263c --- /dev/null +++ b/internal/pkg/repository/admin_repo.go @@ -0,0 +1,181 @@ +package repository + +import ( + "context" + "fmt" + + sq "github.com/Masterminds/squirrel" + "github.com/jackc/pgx/v5" + "github.com/ozontech/seq-ui/internal/app/types" + sqlb "github.com/ozontech/seq-ui/internal/pkg/repository/sql_builder" + "github.com/ozontech/seq-ui/internal/pkg/repository/txmanager" +) + +const defaultRoleID = 0 + +type adminRepository struct { + *pool + txManager *txmanager.Manager +} + +func newAdminRepository(pool *pool) *adminRepository { + return &adminRepository{ + pool: pool, + txManager: txmanager.New(pool.pool), + } +} + +func (r *adminRepository) CreateRole(ctx context.Context, req types.CreateRoleRepoRequest) (int32, error) { + query, args := "INSERT INTO roles (name, permissions) VALUES ($1, $2) RETURNING id", []any{req.Name, req.Permissions} + metricLabels := []string{"roles", "INSERT"} + + var roleID int32 + if err := r.pool.queryRow(ctx, metricLabels, query, args...).Scan(&roleID); err != nil { + incErrorMetric(err, metricLabels) + return 0, fmt.Errorf("failed to create role: %w", err) + } + + return roleID, nil +} + +func (r *adminRepository) AddUsersToRole(ctx context.Context, req types.AddUsersToRoleRequest) error { + query, args := "UPDATE user_profiles SET role_id=$1 WHERE user_name=ANY($2)", []any{req.RoleID, req.Usernames} + metricLabels := []string{"user_profiles", "UPDATE"} + + if _, err := r.pool.exec(ctx, metricLabels, query, args...); err != nil { + incErrorMetric(err, metricLabels) + return fmt.Errorf("failed to add users to role: %w", err) + } + + return nil +} + +func (r *adminRepository) GetRoles(ctx context.Context) ([]types.RoleRepo, error) { + query := "SELECT id, name, permissions FROM roles ORDER BY name" + metricLabels := []string{"roles", "SELECT"} + + rows, err := r.pool.query(ctx, metricLabels, query) + if err != nil { + incErrorMetric(err, metricLabels) + return nil, fmt.Errorf("failed to get roles: %w", err) + } + defer rows.Close() + + var roles []types.RoleRepo + for rows.Next() { + var role types.RoleRepo + if err := rows.Scan(&role.ID, &role.Name, &role.Permissions); err != nil { + incErrorMetric(err, metricLabels) + return nil, fmt.Errorf("failed to scan role: %w", err) + } + + roles = append(roles, role) + } + + return roles, nil +} + +func (r *adminRepository) GetRole(ctx context.Context, req types.GetRoleRequest) (types.RoleInfo, error) { + metricLabels := []string{"user_profiles", "SELECT"} + query, args := "SELECT user_name FROM user_profiles WHERE role_id=$1 ORDER BY user_name", []any{req.RoleID} + + rows, err := r.pool.query(ctx, metricLabels, query, args...) + if err != nil { + incErrorMetric(err, metricLabels) + return types.RoleInfo{}, fmt.Errorf("failed to get role users: %w", err) + } + defer rows.Close() + + var usernames []string + for rows.Next() { + var username string + if err := rows.Scan(&username); err != nil { + incErrorMetric(err, metricLabels) + return types.RoleInfo{}, fmt.Errorf("failed to scan role user: %w", err) + } + + usernames = append(usernames, username) + } + + return types.RoleInfo{Usernames: usernames}, nil +} + +func (r *adminRepository) UpdateRole(ctx context.Context, req types.UpdateRoleRepoRequest) error { + metricLabels := []string{"roles", "UPDATE"} + + qb := sqlb.Update("roles").Where(sq.Eq{"id": req.RoleID}) + if req.Name != nil { + qb = qb.Set("name", *req.Name) + } + if req.Permissions != nil { + qb = qb.Set("permissions", *req.Permissions) + } + query, args := qb.MustSql() + + tag, err := r.pool.exec(ctx, metricLabels, query, args...) + if err != nil { + incErrorMetric(err, metricLabels) + return fmt.Errorf("failed to update role: %w", err) + } + + if tag.RowsAffected() == 0 { + return types.NewErrNotFound("role") + } + + return nil +} + +func (r *adminRepository) DeleteRole(ctx context.Context, req types.DeleteRoleRequest) error { + metricLabels := []string{"user_profiles", "UPDATE"} + + return r.txManager.Do(ctx, func(tx pgx.Tx) error { + query, args := "UPDATE user_profiles SET role_id=$1 WHERE role_id=$2", []any{defaultRoleID, req.RoleID} + if req.ReplacementRoleID != nil { + args[0] = *req.ReplacementRoleID + } + + if _, err := r.pool.execTx(ctx, tx, metricLabels, query, args...); err != nil { + incErrorMetric(err, metricLabels) + return fmt.Errorf("failed to update user_profiles when delete role: %w", err) + } + + metricLabels = []string{"roles", "DELETE"} + query, args = "DELETE FROM roles WHERE id=$1", []any{req.RoleID} + tag, err := r.pool.execTx(ctx, tx, metricLabels, query, args...) + if err != nil { + incErrorMetric(err, metricLabels) + return fmt.Errorf("failed to delete role: %w", err) + } + + if tag.RowsAffected() == 0 { + return types.NewErrNotFound("role") + } + + return nil + }) +} + +func (r *adminRepository) DeleteUsersFromRole(ctx context.Context, req types.DeleteUsersFromRoleRequest) error { + metricLabels := []string{"user_profiles", "UPDATE"} + query, args := "UPDATE user_profiles SET role_id=$1 WHERE role_id=$2 AND user_name=ANY($3)", []any{defaultRoleID, req.RoleID, req.Usernames} + + if _, err := r.pool.exec(ctx, metricLabels, query, args...); err != nil { + incErrorMetric(err, metricLabels) + return fmt.Errorf("failed to delete users from role: %w", err) + } + + return nil +} + +func (r *adminRepository) GetUserPermissions(ctx context.Context, req types.GetUserPermissionsRequest) (uint64, error) { + metricLabels := []string{"user_profiles", "SELECT"} + query, args := "SELECT r.permissions FROM user_profiles up JOIN roles r ON r.id=up.role_id WHERE up.user_name=$1", []any{req.Username} + + var permissions uint64 + if err := r.pool.queryRow(ctx, metricLabels, query, args...).Scan(&permissions); err != nil { + incErrorMetric(err, metricLabels) + return 0, fmt.Errorf("failed to get user permissions: %w", err) + } + + return permissions, nil +} diff --git a/internal/pkg/repository/mock/repository.go b/internal/pkg/repository/mock/repository.go index 221f703..e7fe9bd 100644 --- a/internal/pkg/repository/mock/repository.go +++ b/internal/pkg/repository/mock/repository.go @@ -360,3 +360,143 @@ func (mr *MockAsyncSearchesMockRecorder) SaveAsyncSearch(arg0, arg1 any) *gomock mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveAsyncSearch", reflect.TypeOf((*MockAsyncSearches)(nil).SaveAsyncSearch), arg0, arg1) } + +// MockAdmin is a mock of Admin interface. +type MockAdmin struct { + ctrl *gomock.Controller + recorder *MockAdminMockRecorder + isgomock struct{} +} + +// MockAdminMockRecorder is the mock recorder for MockAdmin. +type MockAdminMockRecorder struct { + mock *MockAdmin +} + +// NewMockAdmin creates a new mock instance. +func NewMockAdmin(ctrl *gomock.Controller) *MockAdmin { + mock := &MockAdmin{ctrl: ctrl} + mock.recorder = &MockAdminMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAdmin) EXPECT() *MockAdminMockRecorder { + return m.recorder +} + +// AddUsersToRole mocks base method. +func (m *MockAdmin) AddUsersToRole(arg0 context.Context, arg1 types.AddUsersToRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUsersToRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddUsersToRole indicates an expected call of AddUsersToRole. +func (mr *MockAdminMockRecorder) AddUsersToRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUsersToRole", reflect.TypeOf((*MockAdmin)(nil).AddUsersToRole), arg0, arg1) +} + +// CreateRole mocks base method. +func (m *MockAdmin) CreateRole(arg0 context.Context, arg1 types.CreateRoleRepoRequest) (int32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRole", arg0, arg1) + ret0, _ := ret[0].(int32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRole indicates an expected call of CreateRole. +func (mr *MockAdminMockRecorder) CreateRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRole", reflect.TypeOf((*MockAdmin)(nil).CreateRole), arg0, arg1) +} + +// DeleteRole mocks base method. +func (m *MockAdmin) DeleteRole(arg0 context.Context, arg1 types.DeleteRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRole indicates an expected call of DeleteRole. +func (mr *MockAdminMockRecorder) DeleteRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRole", reflect.TypeOf((*MockAdmin)(nil).DeleteRole), arg0, arg1) +} + +// DeleteUsersFromRole mocks base method. +func (m *MockAdmin) DeleteUsersFromRole(arg0 context.Context, arg1 types.DeleteUsersFromRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteUsersFromRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteUsersFromRole indicates an expected call of DeleteUsersFromRole. +func (mr *MockAdminMockRecorder) DeleteUsersFromRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUsersFromRole", reflect.TypeOf((*MockAdmin)(nil).DeleteUsersFromRole), arg0, arg1) +} + +// GetRole mocks base method. +func (m *MockAdmin) GetRole(arg0 context.Context, arg1 types.GetRoleRequest) (types.RoleInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRole", arg0, arg1) + ret0, _ := ret[0].(types.RoleInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRole indicates an expected call of GetRole. +func (mr *MockAdminMockRecorder) GetRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRole", reflect.TypeOf((*MockAdmin)(nil).GetRole), arg0, arg1) +} + +// GetRoles mocks base method. +func (m *MockAdmin) GetRoles(arg0 context.Context) ([]types.RoleRepo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoles", arg0) + ret0, _ := ret[0].([]types.RoleRepo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoles indicates an expected call of GetRoles. +func (mr *MockAdminMockRecorder) GetRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoles", reflect.TypeOf((*MockAdmin)(nil).GetRoles), arg0) +} + +// GetUserPermissions mocks base method. +func (m *MockAdmin) GetUserPermissions(arg0 context.Context, arg1 types.GetUserPermissionsRequest) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserPermissions", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserPermissions indicates an expected call of GetUserPermissions. +func (mr *MockAdminMockRecorder) GetUserPermissions(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserPermissions", reflect.TypeOf((*MockAdmin)(nil).GetUserPermissions), arg0, arg1) +} + +// UpdateRole mocks base method. +func (m *MockAdmin) UpdateRole(arg0 context.Context, arg1 types.UpdateRoleRepoRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRole indicates an expected call of UpdateRole. +func (mr *MockAdminMockRecorder) UpdateRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRole", reflect.TypeOf((*MockAdmin)(nil).UpdateRole), arg0, arg1) +} diff --git a/internal/pkg/repository/pool.go b/internal/pkg/repository/pool.go index 3df1379..91c35d7 100644 --- a/internal/pkg/repository/pool.go +++ b/internal/pkg/repository/pool.go @@ -60,3 +60,13 @@ func (p *pool) exec(ctx context.Context, metricLabels []string, query string, ar return tag, err } + +func (p *pool) execTx(ctx context.Context, tx pgx.Tx, metricLabels []string, query string, args ...any) (pgconn.CommandTag, error) { + metric.RepositoryRequestSent.WithLabelValues(metricLabels...).Inc() + start := time.Now() + tag, err := tx.Exec(ctx, query, args...) + took := time.Since(start) + metric.RepositoryRequestDuration.WithLabelValues(metricLabels...).Observe(took.Seconds()) + + return tag, err +} diff --git a/internal/pkg/repository/repository.go b/internal/pkg/repository/repository.go index 78044dc..f85ee3c 100644 --- a/internal/pkg/repository/repository.go +++ b/internal/pkg/repository/repository.go @@ -37,6 +37,17 @@ type ( DeleteExpiredAsyncSearches(context.Context) error GetAsyncSearchesList(context.Context, types.GetAsyncSearchesListRequest) ([]types.AsyncSearchInfo, error) } + + Admin interface { + CreateRole(context.Context, types.CreateRoleRepoRequest) (int32, error) + AddUsersToRole(context.Context, types.AddUsersToRoleRequest) error + DeleteUsersFromRole(context.Context, types.DeleteUsersFromRoleRequest) error + GetRoles(context.Context) ([]types.RoleRepo, error) + GetRole(context.Context, types.GetRoleRequest) (types.RoleInfo, error) + UpdateRole(context.Context, types.UpdateRoleRepoRequest) error + DeleteRole(context.Context, types.DeleteRoleRequest) error + GetUserPermissions(context.Context, types.GetUserPermissionsRequest) (uint64, error) + } ) type Repository struct { @@ -44,6 +55,7 @@ type Repository struct { FavoriteQueries Dashboards AsyncSearches + Admin } func New(pool *pgxpool.Pool, requestTimeout time.Duration) *Repository { @@ -53,5 +65,6 @@ func New(pool *pgxpool.Pool, requestTimeout time.Duration) *Repository { FavoriteQueries: newFavoriteQueriesRepository(p), Dashboards: newDashboardsRepository(p), AsyncSearches: newAsyncSearchesRepository(p), + Admin: newAdminRepository(p), } } diff --git a/internal/pkg/repository/txmanager/manager.go b/internal/pkg/repository/txmanager/manager.go index b480632..347dbfc 100644 --- a/internal/pkg/repository/txmanager/manager.go +++ b/internal/pkg/repository/txmanager/manager.go @@ -8,42 +8,43 @@ import ( ) type ( - // TxManager for spawning new txs. - TxManager interface { - Do(ctx context.Context, starter TxStarter, fn TxFunc) error - } - // TxStarter must implement new transaction spawn. TxStarter interface { + Begin(ctx context.Context) (pgx.Tx, error) BeginTx(ctx context.Context, txOptions pgx.TxOptions) (pgx.Tx, error) } TxFunc func(t pgx.Tx) error ) -type Manager struct{} +type Manager struct { + db TxStarter +} // New transaction manager. -func New() *Manager { - return &Manager{} +func New(db TxStarter) *Manager { + return &Manager{db: db} } -// Do execs given fn in transaction and commits or rollbacks it. -func (m *Manager) Do(ctx context.Context, db TxStarter, fn TxFunc) (err error) { - // todo add configurable transaction opts - tx, err := db.BeginTx(ctx, pgx.TxOptions{}) +// DoTx executes fn in a transaction with the given options. +func (m *Manager) DoTx(ctx context.Context, txOptions pgx.TxOptions, fn TxFunc) error { + tx, err := m.db.BeginTx(ctx, txOptions) if err != nil { return fmt.Errorf("failed begin pg tx: %w", err) } defer func() { - // it's safe to rollback committed tx _ = tx.Rollback(ctx) }() - err = fn(tx) - if err != nil { + if err := fn(tx); err != nil { return err } + return tx.Commit(ctx) } + +// Do executes fn in a transaction with default options. +func (m *Manager) Do(ctx context.Context, fn TxFunc) error { + return m.DoTx(ctx, pgx.TxOptions{}, fn) +} diff --git a/internal/pkg/service/admin/cache.go b/internal/pkg/service/admin/cache.go new file mode 100644 index 0000000..c504c56 --- /dev/null +++ b/internal/pkg/service/admin/cache.go @@ -0,0 +1,71 @@ +package admin + +import ( + "sync" + + "github.com/ozontech/seq-ui/internal/app/types" +) + +type adminCache struct { + roles []types.Role + userPermissions map[string]uint64 + mu sync.RWMutex +} + +func newAdminCache() *adminCache { + return &adminCache{ + userPermissions: make(map[string]uint64), + } +} + +func (c *adminCache) getRoles() ([]types.Role, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + return c.roles, c.roles != nil +} + +func (c *adminCache) getPermissions(username string) (perm uint64, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + perms, ok := c.userPermissions[username] + return perms, ok +} + +func (c *adminCache) setRoles(roles []types.Role) { + c.mu.Lock() + defer c.mu.Unlock() + + c.roles = roles +} + +func (c *adminCache) setPermissions(username string, permissions uint64) { + c.mu.Lock() + defer c.mu.Unlock() + + c.userPermissions[username] = permissions +} + +func (c *adminCache) resetRoles() { + c.mu.Lock() + defer c.mu.Unlock() + + c.roles = nil +} + +func (c *adminCache) resetPermissions(usernames ...string) { + c.mu.Lock() + defer c.mu.Unlock() + + for _, un := range usernames { + delete(c.userPermissions, un) + } +} + +func (c *adminCache) resetAllPermissions() { + c.mu.Lock() + defer c.mu.Unlock() + + c.userPermissions = make(map[string]uint64) +} diff --git a/internal/pkg/service/admin/mock/service.go b/internal/pkg/service/admin/mock/service.go new file mode 100644 index 0000000..3ff3848 --- /dev/null +++ b/internal/pkg/service/admin/mock/service.go @@ -0,0 +1,172 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ozontech/seq-ui/internal/pkg/service/admin (interfaces: Service) +// +// Generated by this command: +// +// mockgen -destination=internal/pkg/service/admin/mock/service.go github.com/ozontech/seq-ui/internal/pkg/service/admin Service +// + +// Package mock_admin is a generated GoMock package. +package mock_admin + +import ( + context "context" + reflect "reflect" + + types "github.com/ozontech/seq-ui/internal/app/types" + gomock "go.uber.org/mock/gomock" +) + +// MockService is a mock of Service interface. +type MockService struct { + ctrl *gomock.Controller + recorder *MockServiceMockRecorder + isgomock struct{} +} + +// MockServiceMockRecorder is the mock recorder for MockService. +type MockServiceMockRecorder struct { + mock *MockService +} + +// NewMockService creates a new mock instance. +func NewMockService(ctrl *gomock.Controller) *MockService { + mock := &MockService{ctrl: ctrl} + mock.recorder = &MockServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockService) EXPECT() *MockServiceMockRecorder { + return m.recorder +} + +// AddUsersToRole mocks base method. +func (m *MockService) AddUsersToRole(arg0 context.Context, arg1 types.AddUsersToRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddUsersToRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddUsersToRole indicates an expected call of AddUsersToRole. +func (mr *MockServiceMockRecorder) AddUsersToRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddUsersToRole", reflect.TypeOf((*MockService)(nil).AddUsersToRole), arg0, arg1) +} + +// CreateRole mocks base method. +func (m *MockService) CreateRole(arg0 context.Context, arg1 types.CreateRoleRequest) (int32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRole", arg0, arg1) + ret0, _ := ret[0].(int32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRole indicates an expected call of CreateRole. +func (mr *MockServiceMockRecorder) CreateRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRole", reflect.TypeOf((*MockService)(nil).CreateRole), arg0, arg1) +} + +// DeleteRole mocks base method. +func (m *MockService) DeleteRole(arg0 context.Context, arg1 types.DeleteRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRole indicates an expected call of DeleteRole. +func (mr *MockServiceMockRecorder) DeleteRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRole", reflect.TypeOf((*MockService)(nil).DeleteRole), arg0, arg1) +} + +// DeleteUsersFromRole mocks base method. +func (m *MockService) DeleteUsersFromRole(arg0 context.Context, arg1 types.DeleteUsersFromRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteUsersFromRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteUsersFromRole indicates an expected call of DeleteUsersFromRole. +func (mr *MockServiceMockRecorder) DeleteUsersFromRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteUsersFromRole", reflect.TypeOf((*MockService)(nil).DeleteUsersFromRole), arg0, arg1) +} + +// GetAvailablePermissions mocks base method. +func (m *MockService) GetAvailablePermissions() []types.Permission { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAvailablePermissions") + ret0, _ := ret[0].([]types.Permission) + return ret0 +} + +// GetAvailablePermissions indicates an expected call of GetAvailablePermissions. +func (mr *MockServiceMockRecorder) GetAvailablePermissions() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAvailablePermissions", reflect.TypeOf((*MockService)(nil).GetAvailablePermissions)) +} + +// GetRole mocks base method. +func (m *MockService) GetRole(arg0 context.Context, arg1 types.GetRoleRequest) (types.RoleInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRole", arg0, arg1) + ret0, _ := ret[0].(types.RoleInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRole indicates an expected call of GetRole. +func (mr *MockServiceMockRecorder) GetRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRole", reflect.TypeOf((*MockService)(nil).GetRole), arg0, arg1) +} + +// GetRoles mocks base method. +func (m *MockService) GetRoles(arg0 context.Context) (types.GetRolesResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRoles", arg0) + ret0, _ := ret[0].(types.GetRolesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRoles indicates an expected call of GetRoles. +func (mr *MockServiceMockRecorder) GetRoles(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRoles", reflect.TypeOf((*MockService)(nil).GetRoles), arg0) +} + +// GetUserPermissions mocks base method. +func (m *MockService) GetUserPermissions(arg0 context.Context, arg1 types.GetUserPermissionsRequest) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUserPermissions", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUserPermissions indicates an expected call of GetUserPermissions. +func (mr *MockServiceMockRecorder) GetUserPermissions(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserPermissions", reflect.TypeOf((*MockService)(nil).GetUserPermissions), arg0, arg1) +} + +// UpdateRole mocks base method. +func (m *MockService) UpdateRole(arg0 context.Context, arg1 types.UpdateRoleRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateRole", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateRole indicates an expected call of UpdateRole. +func (mr *MockServiceMockRecorder) UpdateRole(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateRole", reflect.TypeOf((*MockService)(nil).UpdateRole), arg0, arg1) +} diff --git a/internal/pkg/service/admin/permissions.go b/internal/pkg/service/admin/permissions.go new file mode 100644 index 0000000..de63851 --- /dev/null +++ b/internal/pkg/service/admin/permissions.go @@ -0,0 +1,100 @@ +package admin + +import ( + "context" + "fmt" + + "github.com/ozontech/seq-ui/internal/app/types" +) + +const ( + permissionManageRoles uint64 = 1 << iota +) + +var availablePermissions = []types.Permission{ + { + Value: permissionManageRoles, + Name: "manage_roles", + Description: "Manage roles", + }, +} + +var availablePermissionsMap = map[uint64]struct{}{ + permissionManageRoles: {}, +} + +//nolint:unparam +func (s *service) checkAccess(ctx context.Context, requiredPermission uint64) error { + username, err := types.GetUserKey(ctx) + if err != nil { + return types.ErrUnauthenticated + } + + if _, ok := s.superUsers[username]; ok { + return nil + } + + permissions, err := s.GetUserPermissions(ctx, types.GetUserPermissionsRequest{Username: username}) + if err != nil { + return fmt.Errorf("can't get user permissions: %w", err) + } + + if permissions&requiredPermission == 0 { + return types.ErrPermissionDenied + } + + return nil +} + +func (s *service) GetUserPermissions(ctx context.Context, req types.GetUserPermissionsRequest) (uint64, error) { + if perms, ok := s.cache.getPermissions(req.Username); ok { + return perms, nil + } + + perms, err := s.repo.GetUserPermissions(ctx, req) + if err != nil { + return 0, err + } + + s.cache.setPermissions(req.Username, perms) + + return perms, nil +} + +func (s *service) GetAvailablePermissions() []types.Permission { + return availablePermissions +} + +func unmaskPermissions(value uint64) []uint64 { + permissions := make([]uint64, 0) + + for _, permission := range availablePermissions { + if value&permission.Value != 0 { + permissions = append(permissions, permission.Value) + } + } + + return permissions +} + +func validatePermissions(permissions []uint64) error { + if len(permissions) == 0 { + return types.NewErrInvalidRequestField("empty permissions") + } + + for _, permission := range permissions { + if _, ok := availablePermissionsMap[permission]; !ok { + return fmt.Errorf("unknown permission: %d", permission) + } + } + + return nil +} + +func maskPermissions(permissions []uint64) uint64 { + var value uint64 + for _, permission := range permissions { + value |= permission + } + return value +} diff --git a/internal/pkg/service/admin/permissions_test.go b/internal/pkg/service/admin/permissions_test.go new file mode 100644 index 0000000..bfbb71b --- /dev/null +++ b/internal/pkg/service/admin/permissions_test.go @@ -0,0 +1,240 @@ +package admin + +import ( + "context" + "testing" + + "github.com/ozontech/seq-ui/internal/app/types" + mock "github.com/ozontech/seq-ui/internal/pkg/repository/mock" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestCheckAccess(t *testing.T) { + type mockArgs struct { + req types.GetUserPermissionsRequest + perms uint64 + err error + } + + tests := []struct { + name string + username string + requiredPerm uint64 + wantErr error + mockArgs *mockArgs + }{ + { + name: "ok_super_user", + username: defaultSuperUser, + requiredPerm: permissionManageRoles, + }, + { + name: "err_no_auth", + requiredPerm: permissionManageRoles, + wantErr: types.ErrUnauthenticated, + }, + { + name: "err_permission_denied", + username: "typical bad boy", + requiredPerm: permissionManageRoles, + wantErr: types.ErrPermissionDenied, + mockArgs: &mockArgs{ + req: types.GetUserPermissionsRequest{Username: "typical bad boy"}, + perms: 0, + }, + }, + { + name: "ok_allowed", + username: "typical good boy", + requiredPerm: permissionManageRoles, + mockArgs: &mockArgs{ + req: types.GetUserPermissionsRequest{Username: "typical good boy"}, + perms: permissionManageRoles, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg).(*service) + + if tt.mockArgs != nil { + repo.EXPECT(). + GetUserPermissions(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.perms, tt.mockArgs.err). + Times(1) + } + + ctx := context.Background() + if tt.username != "" { + ctx = types.SetUserKey(ctx, tt.username) + } + + err := svc.checkAccess(ctx, tt.requiredPerm) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGetUserPermissions(t *testing.T) { + type mockArgs struct { + req types.GetUserPermissionsRequest + perms uint64 + err error + } + + tests := []struct { + name string + req types.GetUserPermissionsRequest + wantPerms uint64 + wantErr bool + mockArgs *mockArgs + }{ + { + name: "ok", + req: types.GetUserPermissionsRequest{Username: "user1"}, + wantPerms: permissionManageRoles, + mockArgs: &mockArgs{ + req: types.GetUserPermissionsRequest{Username: "user1"}, + perms: permissionManageRoles, + }, + }, + { + name: "ok_no_permissions", + req: types.GetUserPermissionsRequest{Username: "user1"}, + wantPerms: 0, + mockArgs: &mockArgs{ + req: types.GetUserPermissionsRequest{Username: "user1"}, + perms: 0, + }, + }, + { + name: "err_repo", + req: types.GetUserPermissionsRequest{Username: "user1"}, + wantPerms: 0, + wantErr: true, + mockArgs: &mockArgs{ + req: types.GetUserPermissionsRequest{Username: "user1"}, + err: errSomethingWrong, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + + if tt.mockArgs != nil { + repo.EXPECT(). + GetUserPermissions(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.perms, tt.mockArgs.err). + Times(1) + } + + // first call goes to repo. + permsFromRepo, err := svc.GetUserPermissions(context.Background(), tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.wantPerms, permsFromRepo) + + // second call served from cache. + permsFromCache, err := svc.GetUserPermissions(context.Background(), tt.req) + require.NoError(t, err) + require.Equal(t, permsFromRepo, permsFromCache) + }) + } +} + +func TestGetAvailablePermissions(t *testing.T) { + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + + got := svc.GetAvailablePermissions() + require.Equal(t, availablePermissions, got) +} + +func TestMaskUnmaskPermissions(t *testing.T) { + tests := []struct { + name string + perms []uint64 + mask uint64 + }{ + { + name: "single_permission", + perms: []uint64{permissionManageRoles}, + mask: permissionManageRoles, + }, + { + name: "empty_permission", + perms: []uint64{}, + mask: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + masked := maskPermissions(tt.perms) + require.Equal(t, tt.mask, masked) + + unmasked := unmaskPermissions(masked) + require.Equal(t, tt.perms, unmasked) + }) + } +} + +func TestValidatePermissions(t *testing.T) { + tests := []struct { + name string + perms []uint64 + wantErr bool + }{ + { + name: "ok", + perms: []uint64{permissionManageRoles}, + }, + { + name: "err_empty", + perms: []uint64{}, + wantErr: true, + }, + { + name: "err_unknown", + perms: []uint64{52}, + wantErr: true, + }, + { + name: "err_mixed", + perms: []uint64{permissionManageRoles, 52}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + err := validatePermissions(tt.perms) + require.Equal(t, tt.wantErr, err != nil) + }) + } +} diff --git a/internal/pkg/service/admin/roles.go b/internal/pkg/service/admin/roles.go new file mode 100644 index 0000000..2db229b --- /dev/null +++ b/internal/pkg/service/admin/roles.go @@ -0,0 +1,200 @@ +package admin + +import ( + "context" + "slices" + + "github.com/ozontech/seq-ui/internal/app/types" +) + +func (s *service) CreateRole(ctx context.Context, req types.CreateRoleRequest) (int32, error) { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return 0, err + } + + if req.Name == "" { + return 0, types.NewErrInvalidRequestField("empty role name") + } + + if err := validatePermissions(req.Permissions); err != nil { + return 0, err + } + + roleID, err := s.repo.CreateRole(ctx, types.CreateRoleRepoRequest{ + Name: req.Name, + Permissions: maskPermissions(req.Permissions), + }) + if err != nil { + return 0, err + } + + s.cache.resetRoles() + + return roleID, nil +} + +func (s *service) AddUsersToRole(ctx context.Context, req types.AddUsersToRoleRequest) error { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return err + } + + if req.RoleID <= 0 { + return types.NewErrInvalidRequestField("value role_id must be greater than 0") + } + + if len(req.Usernames) == 0 { + return types.NewErrInvalidRequestField("empty usernames") + } + + if slices.Contains(req.Usernames, "") { + return types.NewErrInvalidRequestField("empty username") + } + + if err := s.repo.AddUsersToRole(ctx, req); err != nil { + return err + } + + s.cache.resetPermissions(req.Usernames...) + + return nil +} + +func (s *service) GetRoles(ctx context.Context) (types.GetRolesResponse, error) { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return types.GetRolesResponse{}, err + } + + if roles, ok := s.cache.getRoles(); ok { + return types.GetRolesResponse{ + Roles: roles, + AvailablePermissions: availablePermissions, + }, nil + } + + repoRoles, err := s.repo.GetRoles(ctx) + if err != nil { + return types.GetRolesResponse{}, err + } + + roles := make([]types.Role, 0, len(repoRoles)) + for _, role := range repoRoles { + roles = append(roles, types.Role{ + ID: role.ID, + Name: role.Name, + Permissions: unmaskPermissions(role.Permissions), + }) + } + + s.cache.setRoles(roles) + + return types.GetRolesResponse{ + Roles: roles, + AvailablePermissions: availablePermissions, + }, nil +} + +func (s *service) GetRole(ctx context.Context, req types.GetRoleRequest) (types.RoleInfo, error) { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return types.RoleInfo{}, err + } + + if req.RoleID <= 0 { + return types.RoleInfo{}, types.NewErrInvalidRequestField("value role_id must be greater than 0") + } + + return s.repo.GetRole(ctx, req) +} + +func (s *service) UpdateRole(ctx context.Context, req types.UpdateRoleRequest) error { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return err + } + + if req.RoleID <= 0 { + return types.NewErrInvalidRequestField("value role_id must be greater than 0") + } + + if (req.Name == nil || *req.Name == "") && len(req.Permissions) == 0 { + return types.ErrEmptyUpdateRequest + } + + var permissions *uint64 + if len(req.Permissions) > 0 { + if err := validatePermissions(req.Permissions); err != nil { + return err + } + + value := maskPermissions(req.Permissions) + permissions = &value + } + + if err := s.repo.UpdateRole(ctx, types.UpdateRoleRepoRequest{ + RoleID: req.RoleID, + Name: req.Name, + Permissions: permissions, + }); err != nil { + return err + } + + s.cache.resetRoles() + + if permissions != nil { + s.cache.resetAllPermissions() + } + + return nil +} + +func (s *service) DeleteRole(ctx context.Context, req types.DeleteRoleRequest) error { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return err + } + + if req.RoleID <= 0 { + return types.NewErrInvalidRequestField("value role_id must be greater than 0") + } + + if req.ReplacementRoleID != nil { + if *req.ReplacementRoleID <= 0 { + return types.NewErrInvalidRequestField("value replacement_role_id must be greater than 0") + } + if *req.ReplacementRoleID == req.RoleID { + return types.NewErrInvalidRequestField("replacement_role_id must be not equal to role_id") + } + } + + if err := s.repo.DeleteRole(ctx, req); err != nil { + return err + } + + s.cache.resetAllPermissions() + s.cache.resetRoles() + + return nil +} + +func (s *service) DeleteUsersFromRole(ctx context.Context, req types.DeleteUsersFromRoleRequest) error { + if err := s.checkAccess(ctx, permissionManageRoles); err != nil { + return err + } + + if req.RoleID <= 0 { + return types.NewErrInvalidRequestField("value role_id must be greater than 0") + } + + if len(req.Usernames) == 0 { + return types.NewErrInvalidRequestField("empty usernames") + } + + if slices.Contains(req.Usernames, "") { + return types.NewErrInvalidRequestField("empty username") + } + + if err := s.repo.DeleteUsersFromRole(ctx, req); err != nil { + return err + } + + s.cache.resetPermissions(req.Usernames...) + + return nil +} diff --git a/internal/pkg/service/admin/roles_test.go b/internal/pkg/service/admin/roles_test.go new file mode 100644 index 0000000..f6835ee --- /dev/null +++ b/internal/pkg/service/admin/roles_test.go @@ -0,0 +1,1050 @@ +package admin + +import ( + "context" + "errors" + "testing" + + "github.com/ozontech/seq-ui/internal/app/config" + "github.com/ozontech/seq-ui/internal/app/types" + mock "github.com/ozontech/seq-ui/internal/pkg/repository/mock" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +var ( + defaultSuperUser = "superuser" + adminCfg = &config.Admin{SuperUsers: []string{defaultSuperUser}} + errSomethingWrong = errors.New("something happened wrong") +) + +type accessMock struct { + permissions uint64 + err error +} + +func setupAccessMock(repo *mock.MockAdmin, actorUsername string, access *accessMock) { + if access == nil { + return + } + + repo.EXPECT(). + GetUserPermissions(gomock.Any(), types.GetUserPermissionsRequest{Username: actorUsername}). + Return(access.permissions, access.err). + Times(1) +} + +func TestCreateRole(t *testing.T) { + type MockArgs struct { + req types.CreateRoleRepoRequest + roleID int32 + err error + } + + tests := []struct { + name string + actorUsername string + req types.CreateRoleRequest + + accessMock *accessMock + mockArgs *MockArgs + + wantRoleID int32 + wantErr bool + }{ + { + name: "ok", + actorUsername: "admin", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{permissionManageRoles}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &MockArgs{ + req: types.CreateRoleRepoRequest{ + Name: "typical good boy", + Permissions: permissionManageRoles, + }, + roleID: 1, + }, + wantRoleID: 1, + }, + { + name: "ok_superuser", + actorUsername: defaultSuperUser, + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{permissionManageRoles}, + }, + mockArgs: &MockArgs{ + req: types.CreateRoleRepoRequest{ + Name: "typical good boy", + Permissions: permissionManageRoles, + }, + roleID: 2, + }, + wantRoleID: 2, + }, + { + name: "err_no_auth", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{permissionManageRoles}, + }, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "bad boy", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{permissionManageRoles}, + }, + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_empty_name", + actorUsername: "admin", + req: types.CreateRoleRequest{ + Name: "", + Permissions: []uint64{permissionManageRoles}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_empty_permissions", + actorUsername: "admin", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_unknown_permissions", + actorUsername: "admin", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{52}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "admin", + req: types.CreateRoleRequest{ + Name: "typical good boy", + Permissions: []uint64{permissionManageRoles}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &MockArgs{ + req: types.CreateRoleRepoRequest{ + Name: "typical good boy", + Permissions: permissionManageRoles, + }, + err: errSomethingWrong, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + ctx := context.Background() + + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + CreateRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.roleID, tt.mockArgs.err). + Times(1) + } + + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + roleID, err := svc.CreateRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.wantRoleID, roleID) + }) + } +} + +func TestAddUsersToRole(t *testing.T) { + type mockArgs struct { + err error + } + + tests := []struct { + name string + actorUsername string + req types.AddUsersToRoleRequest + + accessMock *accessMock + mockArgs *mockArgs + + wantErr bool + }{ + { + name: "ok", + actorUsername: "admin", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", "user2"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{}, + }, + { + name: "ok_superuser", + actorUsername: defaultSuperUser, + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + mockArgs: &mockArgs{}, + }, + { + name: "err_no_auth", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "user", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_invalid_role_id_zero", + actorUsername: "admin", + req: types.AddUsersToRoleRequest{ + RoleID: 0, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_empty_usernames", + actorUsername: "admin", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_contains_empty_username", + actorUsername: "admin", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", ""}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "admin", + req: types.AddUsersToRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + err: errSomethingWrong, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + ctx := context.Background() + + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + AddUsersToRole(gomock.Any(), tt.req). + Return(tt.mockArgs.err). + Times(1) + } + + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + err := svc.AddUsersToRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestGetRoles(t *testing.T) { + type mockArgs struct { + roles []types.RoleRepo + err error + } + + tests := []struct { + name string + actorUsername string + + accessMock *accessMock + mockArgs *mockArgs + + wantRoles []types.Role + wantErr bool + }{ + { + name: "ok", + actorUsername: "admin", + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + roles: []types.RoleRepo{ + { + ID: 1, + Name: "admin", + Permissions: permissionManageRoles, + }, + }, + }, + wantRoles: []types.Role{ + { + ID: 1, + Name: "admin", + Permissions: []uint64{permissionManageRoles}, + }, + }, + }, + { + name: "ok_empty", + actorUsername: "admin", + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + roles: []types.RoleRepo{}, + }, + wantRoles: []types.Role{}, + }, + { + name: "err_no_auth", + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "user", + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "admin", + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + err: errSomethingWrong, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + GetRoles(gomock.Any()). + Return(tt.mockArgs.roles, tt.mockArgs.err). + Times(1) + } + + ctx := context.Background() + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + // first call goes to repo. + respFromRepo, err := svc.GetRoles(ctx) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.wantRoles, respFromRepo.Roles) + require.Equal(t, availablePermissions, respFromRepo.AvailablePermissions) + + // second call served from cache. + respFromCache, err := svc.GetRoles(ctx) + require.NoError(t, err) + require.Equal(t, respFromRepo, respFromCache) + }) + } +} + +func TestGetRole(t *testing.T) { + type mockArgs struct { + roleInfo types.RoleInfo + err error + } + + tests := []struct { + name string + actorUsername string + req types.GetRoleRequest + + accessMock *accessMock + mockArgs *mockArgs + + want types.RoleInfo + wantErr bool + }{ + { + name: "ok", + actorUsername: "admin", + req: types.GetRoleRequest{RoleID: 1}, + want: types.RoleInfo{ + Usernames: []string{"user1", "user2"}, + }, + mockArgs: &mockArgs{ + roleInfo: types.RoleInfo{ + Usernames: []string{"user1", "user2"}, + }, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + }, + { + name: "err_no_auth", + req: types.GetRoleRequest{RoleID: 1}, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "user", + req: types.GetRoleRequest{RoleID: 1}, + wantErr: true, + accessMock: &accessMock{ + permissions: 0, + }, + }, + { + name: "err_invalid_role_id_zero", + actorUsername: "admin", + req: types.GetRoleRequest{RoleID: 0}, + wantErr: true, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + }, + { + name: "err_repo", + actorUsername: "admin", + req: types.GetRoleRequest{RoleID: 1}, + wantErr: true, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + err: errSomethingWrong, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + GetRole(gomock.Any(), tt.req). + Return(tt.mockArgs.roleInfo, tt.mockArgs.err). + Times(1) + } + + ctx := context.Background() + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + gotRoleInfo, err := svc.GetRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.want, gotRoleInfo) + }) + } +} + +func TestUpdateRole(t *testing.T) { + name := "new role name" + emptyName := "" + + type mockArgs struct { + req types.UpdateRoleRepoRequest + err error + } + + tests := []struct { + name string + actorUsername string + req types.UpdateRoleRequest + + mockArgs *mockArgs + accessMock *accessMock + + wantErr bool + }{ + { + name: "ok_name_and_permissions", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + Permissions: []uint64{permissionManageRoles}, + }, + mockArgs: &mockArgs{ + req: types.UpdateRoleRepoRequest{ + RoleID: 1, + Name: &name, + Permissions: ptr(permissionManageRoles), + }, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + }, + { + name: "ok_name_only", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + mockArgs: &mockArgs{ + req: types.UpdateRoleRepoRequest{ + RoleID: 1, + Name: &name, + }, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + }, + { + name: "ok_permissions_only", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Permissions: []uint64{permissionManageRoles}, + }, + mockArgs: &mockArgs{ + req: types.UpdateRoleRepoRequest{ + RoleID: 1, + Permissions: ptr(permissionManageRoles), + }, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + }, + { + name: "ok_superuser", + actorUsername: defaultSuperUser, + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + mockArgs: &mockArgs{ + req: types.UpdateRoleRepoRequest{ + RoleID: 1, + Name: &name, + }, + }, + }, + { + name: "err_no_auth", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "typical bad boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_invalid_role_id", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 0, + Name: &name, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_empty_update", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_empty_name_no_permissions", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &emptyName, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_unknown_permission", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Permissions: []uint64{52}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "typical good boy", + req: types.UpdateRoleRequest{ + RoleID: 1, + Name: &name, + }, + mockArgs: &mockArgs{ + req: types.UpdateRoleRepoRequest{ + RoleID: 1, + Name: &name, + }, + err: errSomethingWrong, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + UpdateRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + ctx := context.Background() + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + err := svc.UpdateRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestDeleteRole(t *testing.T) { + replacementID := int32(2) + + type mockArgs struct { + repoReq types.DeleteRoleRequest + errRepo error + } + + tests := []struct { + name string + actorUsername string + req types.DeleteRoleRequest + + accessMock *accessMock + mockArgs *mockArgs + + wantErr bool + }{ + { + name: "ok_no_replacement", + actorUsername: "admin", + req: types.DeleteRoleRequest{ + RoleID: 1, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + repoReq: types.DeleteRoleRequest{RoleID: 1}, + }, + }, + { + name: "ok_with_replacement", + actorUsername: "admin", + req: types.DeleteRoleRequest{ + RoleID: 1, + ReplacementRoleID: &replacementID, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + repoReq: types.DeleteRoleRequest{ + RoleID: 1, + ReplacementRoleID: &replacementID, + }, + }, + }, + { + name: "err_no_auth", + req: types.DeleteRoleRequest{ + RoleID: 1, + }, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "user", + req: types.DeleteRoleRequest{ + RoleID: 1, + }, + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_invalid_role_id", + actorUsername: "admin", + req: types.DeleteRoleRequest{ + RoleID: 0, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_replacement_equals_role_id", + actorUsername: "admin", + req: types.DeleteRoleRequest{ + RoleID: 1, + ReplacementRoleID: ptr[int32](1), + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "admin", + req: types.DeleteRoleRequest{ + RoleID: 1, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + repoReq: types.DeleteRoleRequest{RoleID: 1}, + errRepo: errSomethingWrong, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + DeleteRole(gomock.Any(), tt.mockArgs.repoReq). + Return(tt.mockArgs.errRepo). + Times(1) + } + + ctx := context.Background() + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + err := svc.DeleteRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestDeleteUsersFromRole(t *testing.T) { + type mockArgs struct { + req types.DeleteUsersFromRoleRequest + err error + } + + tests := []struct { + name string + actorUsername string + req types.DeleteUsersFromRoleRequest + + accessMock *accessMock + mockArgs *mockArgs + + wantErr bool + }{ + { + name: "ok", + actorUsername: "admin", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", "user2"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", "user2"}, + }, + }, + }, + { + name: "err_no_auth", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"u"}, + }, + wantErr: true, + }, + { + name: "err_no_access", + actorUsername: "user", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: 0, + }, + wantErr: true, + }, + { + name: "err_invalid_role_id", + actorUsername: "admin", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 0, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_empty_usernames", + actorUsername: "admin", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_contains_empty_username", + actorUsername: "admin", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1", ""}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + wantErr: true, + }, + { + name: "err_repo", + actorUsername: "admin", + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + accessMock: &accessMock{ + permissions: permissionManageRoles, + }, + mockArgs: &mockArgs{ + req: types.DeleteUsersFromRoleRequest{ + RoleID: 1, + Usernames: []string{"user1"}, + }, + err: errSomethingWrong, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + repo := mock.NewMockAdmin(ctrl) + svc := New(repo, adminCfg) + + setupAccessMock(repo, tt.actorUsername, tt.accessMock) + + if tt.mockArgs != nil { + repo.EXPECT(). + DeleteUsersFromRole(gomock.Any(), tt.mockArgs.req). + Return(tt.mockArgs.err). + Times(1) + } + + ctx := context.Background() + if tt.actorUsername != "" { + ctx = types.SetUserKey(ctx, tt.actorUsername) + } + + err := svc.DeleteUsersFromRole(ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func ptr[T any](value T) *T { + return &value +} diff --git a/internal/pkg/service/admin/service.go b/internal/pkg/service/admin/service.go new file mode 100644 index 0000000..3b7458b --- /dev/null +++ b/internal/pkg/service/admin/service.go @@ -0,0 +1,40 @@ +package admin + +import ( + "context" + + "github.com/ozontech/seq-ui/internal/app/config" + "github.com/ozontech/seq-ui/internal/app/types" + "github.com/ozontech/seq-ui/internal/pkg/repository" +) + +type Service interface { + CreateRole(context.Context, types.CreateRoleRequest) (int32, error) + AddUsersToRole(context.Context, types.AddUsersToRoleRequest) error + DeleteUsersFromRole(context.Context, types.DeleteUsersFromRoleRequest) error + GetRoles(context.Context) (types.GetRolesResponse, error) + GetRole(context.Context, types.GetRoleRequest) (types.RoleInfo, error) + UpdateRole(context.Context, types.UpdateRoleRequest) error + DeleteRole(context.Context, types.DeleteRoleRequest) error + GetUserPermissions(context.Context, types.GetUserPermissionsRequest) (uint64, error) + GetAvailablePermissions() []types.Permission +} + +type service struct { + repo repository.Admin + cache *adminCache + superUsers map[string]struct{} +} + +func New(repo repository.Admin, cfg *config.Admin) Service { + su := make(map[string]struct{}, len(cfg.SuperUsers)) + for _, u := range cfg.SuperUsers { + su[u] = struct{}{} + } + + return &service{ + repo: repo, + cache: newAdminCache(), + superUsers: su, + } +} diff --git a/migration/12_admin_initial.sql b/migration/12_admin_initial.sql new file mode 100644 index 0000000..d8dca9f --- /dev/null +++ b/migration/12_admin_initial.sql @@ -0,0 +1,21 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS roles( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + permissions BIGINT NOT NULL DEFAULT 0 +); + +ALTER TABLE IF EXISTS user_profiles ADD COLUMN IF NOT EXISTS role_id INTEGER; + +CREATE INDEX IF NOT EXISTS idx_user_profiles_role_id ON user_profiles USING HASH (role_id); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP INDEX IF EXISTS idx_user_profiles_role_id; + +ALTER TABLE IF EXISTS user_profiles DROP COLUMN IF EXISTS role_id; + +DROP TABLE IF EXISTS roles; +-- +goose StatementEnd diff --git a/pkg/admin/v1/admin.pb.go b/pkg/admin/v1/admin.pb.go new file mode 100644 index 0000000..6dfd7c5 --- /dev/null +++ b/pkg/admin/v1/admin.pb.go @@ -0,0 +1,1202 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v7.34.1 +// source: admin/v1/admin.proto + +package admin + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Role struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Permissions []uint64 `protobuf:"varint,3,rep,packed,name=permissions,proto3" json:"permissions,omitempty"` +} + +func (x *Role) Reset() { + *x = Role{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Role) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Role) ProtoMessage() {} + +func (x *Role) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Role.ProtoReflect.Descriptor instead. +func (*Role) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{0} +} + +func (x *Role) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Role) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Role) GetPermissions() []uint64 { + if x != nil { + return x.Permissions + } + return nil +} + +type CreateRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Permissions []uint64 `protobuf:"varint,2,rep,packed,name=permissions,proto3" json:"permissions,omitempty"` +} + +func (x *CreateRoleRequest) Reset() { + *x = CreateRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoleRequest) ProtoMessage() {} + +func (x *CreateRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoleRequest.ProtoReflect.Descriptor instead. +func (*CreateRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateRoleRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateRoleRequest) GetPermissions() []uint64 { + if x != nil { + return x.Permissions + } + return nil +} + +type CreateRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId int32 `protobuf:"varint,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` +} + +func (x *CreateRoleResponse) Reset() { + *x = CreateRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRoleResponse) ProtoMessage() {} + +func (x *CreateRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRoleResponse.ProtoReflect.Descriptor instead. +func (*CreateRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateRoleResponse) GetRoleId() int32 { + if x != nil { + return x.RoleId + } + return 0 +} + +type AddUsersToRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId int32 `protobuf:"varint,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + Usernames []string `protobuf:"bytes,2,rep,name=usernames,proto3" json:"usernames,omitempty"` +} + +func (x *AddUsersToRoleRequest) Reset() { + *x = AddUsersToRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddUsersToRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddUsersToRoleRequest) ProtoMessage() {} + +func (x *AddUsersToRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddUsersToRoleRequest.ProtoReflect.Descriptor instead. +func (*AddUsersToRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{3} +} + +func (x *AddUsersToRoleRequest) GetRoleId() int32 { + if x != nil { + return x.RoleId + } + return 0 +} + +func (x *AddUsersToRoleRequest) GetUsernames() []string { + if x != nil { + return x.Usernames + } + return nil +} + +type AddUsersToRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AddUsersToRoleResponse) Reset() { + *x = AddUsersToRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddUsersToRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddUsersToRoleResponse) ProtoMessage() {} + +func (x *AddUsersToRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddUsersToRoleResponse.ProtoReflect.Descriptor instead. +func (*AddUsersToRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{4} +} + +type GetRolesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *GetRolesRequest) Reset() { + *x = GetRolesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRolesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRolesRequest) ProtoMessage() {} + +func (x *GetRolesRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRolesRequest.ProtoReflect.Descriptor instead. +func (*GetRolesRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{5} +} + +type GetRolesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Roles []*Role `protobuf:"bytes,1,rep,name=roles,proto3" json:"roles,omitempty"` + AvailablePermissions []*GetRolesResponse_Permission `protobuf:"bytes,2,rep,name=available_permissions,json=availablePermissions,proto3" json:"available_permissions,omitempty"` +} + +func (x *GetRolesResponse) Reset() { + *x = GetRolesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRolesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRolesResponse) ProtoMessage() {} + +func (x *GetRolesResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRolesResponse.ProtoReflect.Descriptor instead. +func (*GetRolesResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{6} +} + +func (x *GetRolesResponse) GetRoles() []*Role { + if x != nil { + return x.Roles + } + return nil +} + +func (x *GetRolesResponse) GetAvailablePermissions() []*GetRolesResponse_Permission { + if x != nil { + return x.AvailablePermissions + } + return nil +} + +type GetRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetRoleRequest) Reset() { + *x = GetRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleRequest) ProtoMessage() {} + +func (x *GetRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleRequest.ProtoReflect.Descriptor instead. +func (*GetRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{7} +} + +func (x *GetRoleRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +type GetRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Usernames []string `protobuf:"bytes,1,rep,name=usernames,proto3" json:"usernames,omitempty"` +} + +func (x *GetRoleResponse) Reset() { + *x = GetRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRoleResponse) ProtoMessage() {} + +func (x *GetRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRoleResponse.ProtoReflect.Descriptor instead. +func (*GetRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{8} +} + +func (x *GetRoleResponse) GetUsernames() []string { + if x != nil { + return x.Usernames + } + return nil +} + +type UpdateRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"` + Permissions []uint64 `protobuf:"varint,3,rep,packed,name=permissions,proto3" json:"permissions,omitempty"` +} + +func (x *UpdateRoleRequest) Reset() { + *x = UpdateRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoleRequest) ProtoMessage() {} + +func (x *UpdateRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoleRequest.ProtoReflect.Descriptor instead. +func (*UpdateRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateRoleRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *UpdateRoleRequest) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *UpdateRoleRequest) GetPermissions() []uint64 { + if x != nil { + return x.Permissions + } + return nil +} + +type UpdateRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateRoleResponse) Reset() { + *x = UpdateRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRoleResponse) ProtoMessage() {} + +func (x *UpdateRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRoleResponse.ProtoReflect.Descriptor instead. +func (*UpdateRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{10} +} + +type DeleteRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + ReplacementRoleId *int32 `protobuf:"varint,2,opt,name=replacement_role_id,json=replacementRoleId,proto3,oneof" json:"replacement_role_id,omitempty"` +} + +func (x *DeleteRoleRequest) Reset() { + *x = DeleteRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleRequest) ProtoMessage() {} + +func (x *DeleteRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleRequest.ProtoReflect.Descriptor instead. +func (*DeleteRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{11} +} + +func (x *DeleteRoleRequest) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *DeleteRoleRequest) GetReplacementRoleId() int32 { + if x != nil && x.ReplacementRoleId != nil { + return *x.ReplacementRoleId + } + return 0 +} + +type DeleteRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteRoleResponse) Reset() { + *x = DeleteRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRoleResponse) ProtoMessage() {} + +func (x *DeleteRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRoleResponse.ProtoReflect.Descriptor instead. +func (*DeleteRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{12} +} + +type DeleteUsersFromRoleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RoleId int32 `protobuf:"varint,1,opt,name=role_id,json=roleId,proto3" json:"role_id,omitempty"` + Usernames []string `protobuf:"bytes,2,rep,name=usernames,proto3" json:"usernames,omitempty"` +} + +func (x *DeleteUsersFromRoleRequest) Reset() { + *x = DeleteUsersFromRoleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUsersFromRoleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUsersFromRoleRequest) ProtoMessage() {} + +func (x *DeleteUsersFromRoleRequest) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUsersFromRoleRequest.ProtoReflect.Descriptor instead. +func (*DeleteUsersFromRoleRequest) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{13} +} + +func (x *DeleteUsersFromRoleRequest) GetRoleId() int32 { + if x != nil { + return x.RoleId + } + return 0 +} + +func (x *DeleteUsersFromRoleRequest) GetUsernames() []string { + if x != nil { + return x.Usernames + } + return nil +} + +type DeleteUsersFromRoleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteUsersFromRoleResponse) Reset() { + *x = DeleteUsersFromRoleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteUsersFromRoleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUsersFromRoleResponse) ProtoMessage() {} + +func (x *DeleteUsersFromRoleResponse) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUsersFromRoleResponse.ProtoReflect.Descriptor instead. +func (*DeleteUsersFromRoleResponse) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{14} +} + +type GetRolesResponse_Permission struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value uint64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` +} + +func (x *GetRolesResponse_Permission) Reset() { + *x = GetRolesResponse_Permission{} + if protoimpl.UnsafeEnabled { + mi := &file_admin_v1_admin_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetRolesResponse_Permission) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRolesResponse_Permission) ProtoMessage() {} + +func (x *GetRolesResponse_Permission) ProtoReflect() protoreflect.Message { + mi := &file_admin_v1_admin_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRolesResponse_Permission.ProtoReflect.Descriptor instead. +func (*GetRolesResponse_Permission) Descriptor() ([]byte, []int) { + return file_admin_v1_admin_proto_rawDescGZIP(), []int{6, 0} +} + +func (x *GetRolesResponse_Permission) GetValue() uint64 { + if x != nil { + return x.Value + } + return 0 +} + +func (x *GetRolesResponse_Permission) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GetRolesResponse_Permission) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +var File_admin_v1_admin_proto protoreflect.FileDescriptor + +var file_admin_v1_admin_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, + 0x22, 0x4c, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x04, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x49, + 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0b, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x2d, 0x0a, 0x12, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x54, 0x6f, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, + 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x54, 0x6f, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xee, 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x05, 0x72, 0x6f, + 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x64, 0x6d, 0x69, + 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, + 0x12, 0x5a, 0x0a, 0x15, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x14, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x58, 0x0a, 0x0a, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2f, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x67, 0x0a, 0x11, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x04, 0x52, 0x0b, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x70, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x33, 0x0a, + 0x13, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, + 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x42, 0x16, 0x0a, 0x14, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x53, 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x46, + 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, + 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x1d, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xa5, 0x04, 0x0a, 0x0c, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x12, 0x1b, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, + 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x73, 0x54, 0x6f, 0x52, 0x6f, 0x6c, 0x65, + 0x12, 0x1f, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, + 0x73, 0x65, 0x72, 0x73, 0x54, 0x6f, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, + 0x55, 0x73, 0x65, 0x72, 0x73, 0x54, 0x6f, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, + 0x19, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, + 0x65, 0x12, 0x18, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x64, + 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1b, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x47, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x1b, 0x2e, + 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x64, 0x6d, + 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x6c, 0x65, 0x12, + 0x24, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x46, 0x72, 0x6f, 0x6d, + 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2f, 0x5a, 0x2d, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x7a, 0x6f, 0x6e, 0x74, + 0x65, 0x63, 0x68, 0x2f, 0x73, 0x65, 0x71, 0x2d, 0x75, 0x69, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, + 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_admin_v1_admin_proto_rawDescOnce sync.Once + file_admin_v1_admin_proto_rawDescData = file_admin_v1_admin_proto_rawDesc +) + +func file_admin_v1_admin_proto_rawDescGZIP() []byte { + file_admin_v1_admin_proto_rawDescOnce.Do(func() { + file_admin_v1_admin_proto_rawDescData = protoimpl.X.CompressGZIP(file_admin_v1_admin_proto_rawDescData) + }) + return file_admin_v1_admin_proto_rawDescData +} + +var file_admin_v1_admin_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_admin_v1_admin_proto_goTypes = []any{ + (*Role)(nil), // 0: admin.v1.Role + (*CreateRoleRequest)(nil), // 1: admin.v1.CreateRoleRequest + (*CreateRoleResponse)(nil), // 2: admin.v1.CreateRoleResponse + (*AddUsersToRoleRequest)(nil), // 3: admin.v1.AddUsersToRoleRequest + (*AddUsersToRoleResponse)(nil), // 4: admin.v1.AddUsersToRoleResponse + (*GetRolesRequest)(nil), // 5: admin.v1.GetRolesRequest + (*GetRolesResponse)(nil), // 6: admin.v1.GetRolesResponse + (*GetRoleRequest)(nil), // 7: admin.v1.GetRoleRequest + (*GetRoleResponse)(nil), // 8: admin.v1.GetRoleResponse + (*UpdateRoleRequest)(nil), // 9: admin.v1.UpdateRoleRequest + (*UpdateRoleResponse)(nil), // 10: admin.v1.UpdateRoleResponse + (*DeleteRoleRequest)(nil), // 11: admin.v1.DeleteRoleRequest + (*DeleteRoleResponse)(nil), // 12: admin.v1.DeleteRoleResponse + (*DeleteUsersFromRoleRequest)(nil), // 13: admin.v1.DeleteUsersFromRoleRequest + (*DeleteUsersFromRoleResponse)(nil), // 14: admin.v1.DeleteUsersFromRoleResponse + (*GetRolesResponse_Permission)(nil), // 15: admin.v1.GetRolesResponse.Permission +} +var file_admin_v1_admin_proto_depIdxs = []int32{ + 0, // 0: admin.v1.GetRolesResponse.roles:type_name -> admin.v1.Role + 15, // 1: admin.v1.GetRolesResponse.available_permissions:type_name -> admin.v1.GetRolesResponse.Permission + 1, // 2: admin.v1.AdminService.CreateRole:input_type -> admin.v1.CreateRoleRequest + 3, // 3: admin.v1.AdminService.AddUsersToRole:input_type -> admin.v1.AddUsersToRoleRequest + 5, // 4: admin.v1.AdminService.GetRoles:input_type -> admin.v1.GetRolesRequest + 7, // 5: admin.v1.AdminService.GetRole:input_type -> admin.v1.GetRoleRequest + 9, // 6: admin.v1.AdminService.UpdateRole:input_type -> admin.v1.UpdateRoleRequest + 11, // 7: admin.v1.AdminService.DeleteRole:input_type -> admin.v1.DeleteRoleRequest + 13, // 8: admin.v1.AdminService.DeleteUsersFromRole:input_type -> admin.v1.DeleteUsersFromRoleRequest + 2, // 9: admin.v1.AdminService.CreateRole:output_type -> admin.v1.CreateRoleResponse + 4, // 10: admin.v1.AdminService.AddUsersToRole:output_type -> admin.v1.AddUsersToRoleResponse + 6, // 11: admin.v1.AdminService.GetRoles:output_type -> admin.v1.GetRolesResponse + 8, // 12: admin.v1.AdminService.GetRole:output_type -> admin.v1.GetRoleResponse + 10, // 13: admin.v1.AdminService.UpdateRole:output_type -> admin.v1.UpdateRoleResponse + 12, // 14: admin.v1.AdminService.DeleteRole:output_type -> admin.v1.DeleteRoleResponse + 14, // 15: admin.v1.AdminService.DeleteUsersFromRole:output_type -> admin.v1.DeleteUsersFromRoleResponse + 9, // [9:16] is the sub-list for method output_type + 2, // [2:9] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_admin_v1_admin_proto_init() } +func file_admin_v1_admin_proto_init() { + if File_admin_v1_admin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_admin_v1_admin_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*Role); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*CreateRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*CreateRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*AddUsersToRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*AddUsersToRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*GetRolesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*GetRolesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*GetRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*GetRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*UpdateRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*UpdateRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*DeleteRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*DeleteRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*DeleteUsersFromRoleRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*DeleteUsersFromRoleResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_admin_v1_admin_proto_msgTypes[15].Exporter = func(v any, i int) any { + switch v := v.(*GetRolesResponse_Permission); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_admin_v1_admin_proto_msgTypes[9].OneofWrappers = []any{} + file_admin_v1_admin_proto_msgTypes[11].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_admin_v1_admin_proto_rawDesc, + NumEnums: 0, + NumMessages: 16, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_admin_v1_admin_proto_goTypes, + DependencyIndexes: file_admin_v1_admin_proto_depIdxs, + MessageInfos: file_admin_v1_admin_proto_msgTypes, + }.Build() + File_admin_v1_admin_proto = out.File + file_admin_v1_admin_proto_rawDesc = nil + file_admin_v1_admin_proto_goTypes = nil + file_admin_v1_admin_proto_depIdxs = nil +} diff --git a/pkg/admin/v1/admin_grpc.pb.go b/pkg/admin/v1/admin_grpc.pb.go new file mode 100644 index 0000000..3bbfcfc --- /dev/null +++ b/pkg/admin/v1/admin_grpc.pb.go @@ -0,0 +1,336 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v7.34.1 +// source: admin/v1/admin.proto + +package admin + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + AdminService_CreateRole_FullMethodName = "/admin.v1.AdminService/CreateRole" + AdminService_AddUsersToRole_FullMethodName = "/admin.v1.AdminService/AddUsersToRole" + AdminService_GetRoles_FullMethodName = "/admin.v1.AdminService/GetRoles" + AdminService_GetRole_FullMethodName = "/admin.v1.AdminService/GetRole" + AdminService_UpdateRole_FullMethodName = "/admin.v1.AdminService/UpdateRole" + AdminService_DeleteRole_FullMethodName = "/admin.v1.AdminService/DeleteRole" + AdminService_DeleteUsersFromRole_FullMethodName = "/admin.v1.AdminService/DeleteUsersFromRole" +) + +// AdminServiceClient is the client API for AdminService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AdminServiceClient interface { + CreateRole(ctx context.Context, in *CreateRoleRequest, opts ...grpc.CallOption) (*CreateRoleResponse, error) + AddUsersToRole(ctx context.Context, in *AddUsersToRoleRequest, opts ...grpc.CallOption) (*AddUsersToRoleResponse, error) + GetRoles(ctx context.Context, in *GetRolesRequest, opts ...grpc.CallOption) (*GetRolesResponse, error) + GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) + UpdateRole(ctx context.Context, in *UpdateRoleRequest, opts ...grpc.CallOption) (*UpdateRoleResponse, error) + DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) + DeleteUsersFromRole(ctx context.Context, in *DeleteUsersFromRoleRequest, opts ...grpc.CallOption) (*DeleteUsersFromRoleResponse, error) +} + +type adminServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAdminServiceClient(cc grpc.ClientConnInterface) AdminServiceClient { + return &adminServiceClient{cc} +} + +func (c *adminServiceClient) CreateRole(ctx context.Context, in *CreateRoleRequest, opts ...grpc.CallOption) (*CreateRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateRoleResponse) + err := c.cc.Invoke(ctx, AdminService_CreateRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) AddUsersToRole(ctx context.Context, in *AddUsersToRoleRequest, opts ...grpc.CallOption) (*AddUsersToRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AddUsersToRoleResponse) + err := c.cc.Invoke(ctx, AdminService_AddUsersToRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) GetRoles(ctx context.Context, in *GetRolesRequest, opts ...grpc.CallOption) (*GetRolesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRolesResponse) + err := c.cc.Invoke(ctx, AdminService_GetRoles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) GetRole(ctx context.Context, in *GetRoleRequest, opts ...grpc.CallOption) (*GetRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRoleResponse) + err := c.cc.Invoke(ctx, AdminService_GetRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) UpdateRole(ctx context.Context, in *UpdateRoleRequest, opts ...grpc.CallOption) (*UpdateRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateRoleResponse) + err := c.cc.Invoke(ctx, AdminService_UpdateRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) DeleteRole(ctx context.Context, in *DeleteRoleRequest, opts ...grpc.CallOption) (*DeleteRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteRoleResponse) + err := c.cc.Invoke(ctx, AdminService_DeleteRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *adminServiceClient) DeleteUsersFromRole(ctx context.Context, in *DeleteUsersFromRoleRequest, opts ...grpc.CallOption) (*DeleteUsersFromRoleResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteUsersFromRoleResponse) + err := c.cc.Invoke(ctx, AdminService_DeleteUsersFromRole_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AdminServiceServer is the server API for AdminService service. +// All implementations should embed UnimplementedAdminServiceServer +// for forward compatibility +type AdminServiceServer interface { + CreateRole(context.Context, *CreateRoleRequest) (*CreateRoleResponse, error) + AddUsersToRole(context.Context, *AddUsersToRoleRequest) (*AddUsersToRoleResponse, error) + GetRoles(context.Context, *GetRolesRequest) (*GetRolesResponse, error) + GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) + UpdateRole(context.Context, *UpdateRoleRequest) (*UpdateRoleResponse, error) + DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) + DeleteUsersFromRole(context.Context, *DeleteUsersFromRoleRequest) (*DeleteUsersFromRoleResponse, error) +} + +// UnimplementedAdminServiceServer should be embedded to have forward compatible implementations. +type UnimplementedAdminServiceServer struct { +} + +func (UnimplementedAdminServiceServer) CreateRole(context.Context, *CreateRoleRequest) (*CreateRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateRole not implemented") +} +func (UnimplementedAdminServiceServer) AddUsersToRole(context.Context, *AddUsersToRoleRequest) (*AddUsersToRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddUsersToRole not implemented") +} +func (UnimplementedAdminServiceServer) GetRoles(context.Context, *GetRolesRequest) (*GetRolesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRoles not implemented") +} +func (UnimplementedAdminServiceServer) GetRole(context.Context, *GetRoleRequest) (*GetRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRole not implemented") +} +func (UnimplementedAdminServiceServer) UpdateRole(context.Context, *UpdateRoleRequest) (*UpdateRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateRole not implemented") +} +func (UnimplementedAdminServiceServer) DeleteRole(context.Context, *DeleteRoleRequest) (*DeleteRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteRole not implemented") +} +func (UnimplementedAdminServiceServer) DeleteUsersFromRole(context.Context, *DeleteUsersFromRoleRequest) (*DeleteUsersFromRoleResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteUsersFromRole not implemented") +} + +// UnsafeAdminServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AdminServiceServer will +// result in compilation errors. +type UnsafeAdminServiceServer interface { + mustEmbedUnimplementedAdminServiceServer() +} + +func RegisterAdminServiceServer(s grpc.ServiceRegistrar, srv AdminServiceServer) { + s.RegisterService(&AdminService_ServiceDesc, srv) +} + +func _AdminService_CreateRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).CreateRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_CreateRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).CreateRole(ctx, req.(*CreateRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_AddUsersToRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddUsersToRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).AddUsersToRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_AddUsersToRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).AddUsersToRole(ctx, req.(*AddUsersToRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_GetRoles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRolesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).GetRoles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_GetRoles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).GetRoles(ctx, req.(*GetRolesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_GetRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).GetRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_GetRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).GetRole(ctx, req.(*GetRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_UpdateRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).UpdateRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_UpdateRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).UpdateRole(ctx, req.(*UpdateRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_DeleteRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).DeleteRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_DeleteRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).DeleteRole(ctx, req.(*DeleteRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AdminService_DeleteUsersFromRole_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUsersFromRoleRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AdminServiceServer).DeleteUsersFromRole(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AdminService_DeleteUsersFromRole_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AdminServiceServer).DeleteUsersFromRole(ctx, req.(*DeleteUsersFromRoleRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AdminService_ServiceDesc is the grpc.ServiceDesc for AdminService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AdminService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "admin.v1.AdminService", + HandlerType: (*AdminServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateRole", + Handler: _AdminService_CreateRole_Handler, + }, + { + MethodName: "AddUsersToRole", + Handler: _AdminService_AddUsersToRole_Handler, + }, + { + MethodName: "GetRoles", + Handler: _AdminService_GetRoles_Handler, + }, + { + MethodName: "GetRole", + Handler: _AdminService_GetRole_Handler, + }, + { + MethodName: "UpdateRole", + Handler: _AdminService_UpdateRole_Handler, + }, + { + MethodName: "DeleteRole", + Handler: _AdminService_DeleteRole_Handler, + }, + { + MethodName: "DeleteUsersFromRole", + Handler: _AdminService_DeleteUsersFromRole_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "admin/v1/admin.proto", +} diff --git a/pkg/userprofile/v1/userprofile.pb.go b/pkg/userprofile/v1/userprofile.pb.go index d8e85f0..aa5b672 100644 --- a/pkg/userprofile/v1/userprofile.pb.go +++ b/pkg/userprofile/v1/userprofile.pb.go @@ -113,6 +113,7 @@ type GetUserProfileResponse struct { Timezone string `protobuf:"bytes,1,opt,name=timezone,proto3" json:"timezone,omitempty"` OnboardingVersion string `protobuf:"bytes,2,opt,name=onboarding_version,json=onboardingVersion,proto3" json:"onboarding_version,omitempty"` LogColumns *LogColumns `protobuf:"bytes,3,opt,name=log_columns,json=logColumns,proto3" json:"log_columns,omitempty"` + RoleId *int32 `protobuf:"varint,4,opt,name=role_id,json=roleId,proto3,oneof" json:"role_id,omitempty"` } func (x *GetUserProfileResponse) Reset() { @@ -168,6 +169,13 @@ func (x *GetUserProfileResponse) GetLogColumns() *LogColumns { return nil } +func (x *GetUserProfileResponse) GetRoleId() int32 { + if x != nil && x.RoleId != nil { + return *x.RoleId + } + return 0 +} + type UpdateUserProfileRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1168,7 +1176,7 @@ var file_userprofile_v1_userprofile_proto_rawDesc = []byte{ 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x22, 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x01, 0x0a, 0x16, 0x47, + 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xca, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, @@ -1178,138 +1186,141 @@ var file_userprofile_v1_userprofile_proto_rawDesc = []byte{ 0x12, 0x3b, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, - 0x73, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x22, 0xe5, 0x01, - 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, - 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x08, 0x74, 0x69, - 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, - 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x88, 0x01, 0x01, 0x12, 0x32, 0x0a, 0x12, 0x6f, - 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, - 0x40, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, - 0x48, 0x02, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x88, 0x01, - 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x42, 0x15, - 0x0a, 0x13, 0x5f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x22, 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, - 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0xf6, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, - 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, - 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x30, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x52, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x8b, 0x01, 0x0a, 0x05, 0x51, - 0x75, 0x65, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, - 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, - 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, - 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x90, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, - 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, - 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, - 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x2d, 0x0a, 0x1b, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x1a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x61, - 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, - 0x9d, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x64, 0x61, 0x73, - 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x0a, - 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x1a, 0x33, 0x0a, 0x09, 0x44, 0x61, - 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, - 0x29, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x5d, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, - 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x40, 0x0a, 0x16, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x2d, 0x0a, 0x17, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x70, 0x0a, 0x16, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, - 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x19, 0x0a, 0x17, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, + 0x73, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x12, 0x1c, 0x0a, + 0x07, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, + 0x52, 0x06, 0x72, 0x6f, 0x6c, 0x65, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x22, 0xe5, 0x01, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, + 0x6e, 0x65, 0x88, 0x01, 0x01, 0x12, 0x32, 0x0a, 0x12, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x01, 0x52, 0x11, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x40, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, + 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x6f, 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x48, 0x02, 0x52, 0x0a, 0x6c, 0x6f, + 0x67, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x6f, 0x6e, 0x62, + 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, + 0x0e, 0x0a, 0x0c, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x22, + 0x1b, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x0a, 0x19, + 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf6, 0x01, 0x0a, 0x1a, 0x47, 0x65, + 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, + 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x61, + 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, 0x71, 0x75, 0x65, + 0x72, 0x69, 0x65, 0x73, 0x1a, 0x8b, 0x01, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x28, 0x0a, + 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, + 0x6f, 0x6d, 0x22, 0x90, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, + 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x28, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x66, 0x72, 0x6f, + 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x48, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x2d, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, + 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x02, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, + 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x1d, 0x0a, 0x1b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, + 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x16, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9d, 0x01, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x0a, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, + 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x0a, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x73, 0x1a, 0x33, 0x0a, 0x09, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x29, 0x0a, 0x13, 0x47, 0x65, 0x74, + 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x75, 0x75, 0x69, 0x64, 0x22, 0x5d, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x44, 0x61, 0x73, 0x68, 0x62, + 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x40, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, + 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x2d, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0xb6, 0x04, 0x0a, 0x12, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a, 0x0a, 0x11, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, - 0x28, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x75, 0x75, 0x69, 0x64, 0x22, 0x70, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, + 0x69, 0x64, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x74, + 0x61, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x2c, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, + 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, + 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x22, + 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xb6, 0x04, 0x0a, 0x12, 0x55, + 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x61, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, + 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x28, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, - 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, - 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, - 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x70, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, - 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2a, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x70, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2a, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x7a, 0x6f, 0x6e, 0x74, 0x65, 0x63, 0x68, - 0x2f, 0x73, 0x65, 0x71, 0x2d, 0x75, 0x69, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x70, - 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x6d, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x29, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, + 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, + 0x65, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x70, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, + 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, + 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, + 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x70, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, + 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x2a, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, + 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, + 0x72, 0x69, 0x74, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x6f, 0x7a, 0x6f, 0x6e, 0x74, 0x65, 0x63, 0x68, 0x2f, 0x73, 0x65, 0x71, 0x2d, 0x75, + 0x69, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, + 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1655,6 +1666,7 @@ func file_userprofile_v1_userprofile_proto_init() { } } } + file_userprofile_v1_userprofile_proto_msgTypes[2].OneofWrappers = []any{} file_userprofile_v1_userprofile_proto_msgTypes[3].OneofWrappers = []any{} file_userprofile_v1_userprofile_proto_msgTypes[7].OneofWrappers = []any{} file_userprofile_v1_userprofile_proto_msgTypes[17].OneofWrappers = []any{} diff --git a/swagger/swagger.json b/swagger/swagger.json index b35dd86..c8f6d93 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -12,6 +12,266 @@ "version": "1.0" }, "paths": { + "/admin/v1/roles": { + "get": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_get_roles", + "responses": { + "200": { + "description": "A successful response", + "schema": { + "$ref": "#/definitions/admin.v1.GetRolesResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + }, + "post": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_create_role", + "parameters": [ + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.v1.CreateRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response", + "schema": { + "$ref": "#/definitions/admin.v1.CreateRoleResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + } + }, + "/admin/v1/roles/{id}": { + "get": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_get_role", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "A successful response", + "schema": { + "$ref": "#/definitions/admin.v1.GetRoleResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + }, + "delete": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_delete_role", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/admin.v1.DeleteRoleResponse" + } + } + ], + "responses": { + "200": { + "description": "A successful response" + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + }, + "patch": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_update_role", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.v1.UpdateRoleResponse" + } + } + ], + "responses": { + "200": { + "description": "A successful response" + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + } + }, + "/admin/v1/roles/{id}/users": { + "post": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_add_users_to_role", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.v1.AddUsersToRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response" + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + }, + "delete": { + "security": [ + { + "bearer": [] + } + ], + "tags": [ + "admin_v1" + ], + "operationId": "admin_v1_delete_users_from_role", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/admin.v1.DeleteUsersFromRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response" + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/UnexpectedError" + } + } + } + } + }, "/dashboards/v1/": { "post": { "security": [ @@ -60,7 +320,7 @@ "tags": [ "dashboards_v1" ], - "operationId": "dashboards_v1_getAll", + "operationId": "dashboards_v1_get_all", "parameters": [ { "description": "Request body", @@ -98,7 +358,7 @@ "tags": [ "dashboards_v1" ], - "operationId": "dashboards_v1_getMy", + "operationId": "dashboards_v1_get_my", "parameters": [ { "description": "Request body", @@ -174,7 +434,7 @@ "tags": [ "dashboards_v1" ], - "operationId": "dashboards_v1_getByUUID", + "operationId": "dashboards_v1_get_by_uuid", "parameters": [ { "type": "string", @@ -688,7 +948,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getAggregation", + "operationId": "seqapi_v1_get_aggregation", "parameters": [ { "type": "string", @@ -738,7 +998,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getAggregationTs", + "operationId": "seqapi_v1_get_aggregation_ts", "parameters": [ { "type": "string", @@ -987,7 +1247,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getEvent", + "operationId": "seqapi_v1_get_event", "parameters": [ { "type": "string", @@ -1098,7 +1358,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getPinnedFields", + "operationId": "seqapi_v1_get_pinned_fields", "parameters": [ { "type": "string", @@ -1133,7 +1393,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getHistogram", + "operationId": "seqapi_v1_get_histogram", "parameters": [ { "type": "string", @@ -1172,7 +1432,7 @@ "tags": [ "seqapi_v1" ], - "operationId": "seqapi_v1_getLimits", + "operationId": "seqapi_v1_get_limits", "parameters": [ { "type": "string", @@ -1311,7 +1571,7 @@ "tags": [ "userprofile_v1" ], - "operationId": "userprofile_v1_getUserProfile", + "operationId": "userprofile_v1_get_user_profile", "responses": { "200": { "description": "A successful response", @@ -1336,7 +1596,7 @@ "tags": [ "userprofile_v1" ], - "operationId": "userprofile_v1_updateUserProfile", + "operationId": "userprofile_v1_update_user_profile", "parameters": [ { "description": "Request body", @@ -1371,7 +1631,7 @@ "tags": [ "userprofile_v1" ], - "operationId": "userprofile_v1_getFavoriteQueries", + "operationId": "userprofile_v1_get_favorite_queries", "responses": { "200": { "description": "A successful response", @@ -1396,7 +1656,7 @@ "tags": [ "userprofile_v1" ], - "operationId": "userprofile_v1_createFavoriteQuery", + "operationId": "userprofile_v1_create_favorite_query", "parameters": [ { "description": "Request body", @@ -1434,7 +1694,7 @@ "tags": [ "userprofile_v1" ], - "operationId": "userprofile_v1_deleteFavoriteQuery", + "operationId": "userprofile_v1_delete_favorite_query", "parameters": [ { "type": "string", @@ -1468,6 +1728,131 @@ } } }, + "admin.v1.AddUsersToRoleRequest": { + "type": "object", + "properties": { + "usernames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "admin.v1.CreateRoleRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "admin.v1.CreateRoleResponse": { + "type": "object", + "properties": { + "role_id": { + "type": "integer" + } + } + }, + "admin.v1.DeleteRoleResponse": { + "type": "object", + "properties": { + "replacement_role_id": { + "type": "integer" + } + } + }, + "admin.v1.DeleteUsersFromRoleRequest": { + "type": "object", + "properties": { + "usernames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "admin.v1.GetRoleResponse": { + "type": "object", + "properties": { + "usernames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "admin.v1.GetRolesResponse": { + "type": "object", + "properties": { + "available_permissions": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.v1.Permission" + } + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/admin.v1.Role" + } + } + } + }, + "admin.v1.Permission": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, + "admin.v1.Role": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "admin.v1.UpdateRoleResponse": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "dashboards.v1.CreateRequest": { "type": "object", "properties": { diff --git a/tracing/tracing_test.go b/tracing/tracing_test.go index 81f7b9a..90d67a2 100644 --- a/tracing/tracing_test.go +++ b/tracing/tracing_test.go @@ -72,7 +72,6 @@ func TestValidateTracingConfig(t *testing.T) { } for _, tCase := range tCases { - tCase := tCase t.Run(tCase.name, func(t *testing.T) { t.Parallel() err := validateTracingConfig(tCase.cfg)