From 8442288475664ab732f3db4ba5efed68b5eea0f5 Mon Sep 17 00:00:00 2001 From: alishakawaguchi <alisha@nucleuscloud.com> Date: Tue, 7 Nov 2023 09:54:40 -0800 Subject: [PATCH] add create team account functionality (#520) --- backend/gen/go/db/mock_Querier.go | 110 +++++++ backend/gen/go/db/querier.go | 2 + backend/gen/go/db/users.sql.go | 57 ++++ .../mock_UserAccountServiceClient.go | 55 ++++ .../user_account.connect.go | 27 ++ .../protos/mgmt/v1alpha1/user_account.pb.go | 308 +++++++++++++----- .../mgmt/v1alpha1/user_account.pb.validate.go | 208 ++++++++++++ backend/internal/nucleusdb/db.go | 2 - backend/internal/nucleusdb/mock_Tx.go | 69 ++++ backend/internal/nucleusdb/users.go | 40 +++ backend/internal/nucleusdb/users_test.go | 132 ++++++++ .../protos/mgmt/v1alpha1/user_account.proto | 8 + .../mgmt/v1alpha1/job-service/jobs.go | 1 - .../v1alpha1/user-account-service/users.go | 23 ++ .../user-account-service/users_test.go | 28 ++ backend/sql/postgresql/queries/users.sql | 14 + frontend/app/api/teams/route.ts | 10 + frontend/components/AccountSwitcher.tsx | 286 +++++++++++----- .../mgmt/v1alpha1/user_account_connect.ts | 11 +- .../mgmt/v1alpha1/user_account_pb.ts | 74 +++++ 20 files changed, 1287 insertions(+), 178 deletions(-) create mode 100644 backend/internal/nucleusdb/mock_Tx.go create mode 100644 backend/internal/nucleusdb/users_test.go create mode 100644 frontend/app/api/teams/route.ts diff --git a/backend/gen/go/db/mock_Querier.go b/backend/gen/go/db/mock_Querier.go index 0630ea9d75..a2f5cb7bb6 100644 --- a/backend/gen/go/db/mock_Querier.go +++ b/backend/gen/go/db/mock_Querier.go @@ -456,6 +456,60 @@ func (_c *MockQuerier_CreatePersonalAccount_Call) RunAndReturn(run func(context. return _c } +// CreateTeamAccount provides a mock function with given fields: ctx, db, accountSlug +func (_m *MockQuerier) CreateTeamAccount(ctx context.Context, db DBTX, accountSlug string) (NeosyncApiAccount, error) { + ret := _m.Called(ctx, db, accountSlug) + + var r0 NeosyncApiAccount + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, DBTX, string) (NeosyncApiAccount, error)); ok { + return rf(ctx, db, accountSlug) + } + if rf, ok := ret.Get(0).(func(context.Context, DBTX, string) NeosyncApiAccount); ok { + r0 = rf(ctx, db, accountSlug) + } else { + r0 = ret.Get(0).(NeosyncApiAccount) + } + + if rf, ok := ret.Get(1).(func(context.Context, DBTX, string) error); ok { + r1 = rf(ctx, db, accountSlug) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockQuerier_CreateTeamAccount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTeamAccount' +type MockQuerier_CreateTeamAccount_Call struct { + *mock.Call +} + +// CreateTeamAccount is a helper method to define mock.On call +// - ctx context.Context +// - db DBTX +// - accountSlug string +func (_e *MockQuerier_Expecter) CreateTeamAccount(ctx interface{}, db interface{}, accountSlug interface{}) *MockQuerier_CreateTeamAccount_Call { + return &MockQuerier_CreateTeamAccount_Call{Call: _e.mock.On("CreateTeamAccount", ctx, db, accountSlug)} +} + +func (_c *MockQuerier_CreateTeamAccount_Call) Run(run func(ctx context.Context, db DBTX, accountSlug string)) *MockQuerier_CreateTeamAccount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DBTX), args[2].(string)) + }) + return _c +} + +func (_c *MockQuerier_CreateTeamAccount_Call) Return(_a0 NeosyncApiAccount, _a1 error) *MockQuerier_CreateTeamAccount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockQuerier_CreateTeamAccount_Call) RunAndReturn(run func(context.Context, DBTX, string) (NeosyncApiAccount, error)) *MockQuerier_CreateTeamAccount_Call { + _c.Call.Return(run) + return _c +} + // CreateUser provides a mock function with given fields: ctx, db func (_m *MockQuerier) CreateUser(ctx context.Context, db DBTX) (NeosyncApiUser, error) { ret := _m.Called(ctx, db) @@ -1472,6 +1526,62 @@ func (_c *MockQuerier_GetPersonalAccountByUserId_Call) RunAndReturn(run func(con return _c } +// GetTeamAccountsByUserId provides a mock function with given fields: ctx, db, userid +func (_m *MockQuerier) GetTeamAccountsByUserId(ctx context.Context, db DBTX, userid pgtype.UUID) ([]NeosyncApiAccount, error) { + ret := _m.Called(ctx, db, userid) + + var r0 []NeosyncApiAccount + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, DBTX, pgtype.UUID) ([]NeosyncApiAccount, error)); ok { + return rf(ctx, db, userid) + } + if rf, ok := ret.Get(0).(func(context.Context, DBTX, pgtype.UUID) []NeosyncApiAccount); ok { + r0 = rf(ctx, db, userid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]NeosyncApiAccount) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, DBTX, pgtype.UUID) error); ok { + r1 = rf(ctx, db, userid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockQuerier_GetTeamAccountsByUserId_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTeamAccountsByUserId' +type MockQuerier_GetTeamAccountsByUserId_Call struct { + *mock.Call +} + +// GetTeamAccountsByUserId is a helper method to define mock.On call +// - ctx context.Context +// - db DBTX +// - userid pgtype.UUID +func (_e *MockQuerier_Expecter) GetTeamAccountsByUserId(ctx interface{}, db interface{}, userid interface{}) *MockQuerier_GetTeamAccountsByUserId_Call { + return &MockQuerier_GetTeamAccountsByUserId_Call{Call: _e.mock.On("GetTeamAccountsByUserId", ctx, db, userid)} +} + +func (_c *MockQuerier_GetTeamAccountsByUserId_Call) Run(run func(ctx context.Context, db DBTX, userid pgtype.UUID)) *MockQuerier_GetTeamAccountsByUserId_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(DBTX), args[2].(pgtype.UUID)) + }) + return _c +} + +func (_c *MockQuerier_GetTeamAccountsByUserId_Call) Return(_a0 []NeosyncApiAccount, _a1 error) *MockQuerier_GetTeamAccountsByUserId_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockQuerier_GetTeamAccountsByUserId_Call) RunAndReturn(run func(context.Context, DBTX, pgtype.UUID) ([]NeosyncApiAccount, error)) *MockQuerier_GetTeamAccountsByUserId_Call { + _c.Call.Return(run) + return _c +} + // GetTemporalConfigByAccount provides a mock function with given fields: ctx, db, id func (_m *MockQuerier) GetTemporalConfigByAccount(ctx context.Context, db DBTX, id pgtype.UUID) (*pg_models.TemporalConfig, error) { ret := _m.Called(ctx, db, id) diff --git a/backend/gen/go/db/querier.go b/backend/gen/go/db/querier.go index 83ad9ba685..23161f8c98 100644 --- a/backend/gen/go/db/querier.go +++ b/backend/gen/go/db/querier.go @@ -20,6 +20,7 @@ type Querier interface { CreateJobConnectionDestination(ctx context.Context, db DBTX, arg CreateJobConnectionDestinationParams) (NeosyncApiJobDestinationConnectionAssociation, error) CreateJobConnectionDestinations(ctx context.Context, db DBTX, arg []CreateJobConnectionDestinationsParams) (int64, error) CreatePersonalAccount(ctx context.Context, db DBTX, accountSlug string) (NeosyncApiAccount, error) + CreateTeamAccount(ctx context.Context, db DBTX, accountSlug string) (NeosyncApiAccount, error) CreateUser(ctx context.Context, db DBTX) (NeosyncApiUser, error) DeleteCustomTransformerById(ctx context.Context, db DBTX, id pgtype.UUID) error DeleteJob(ctx context.Context, db DBTX, id pgtype.UUID) error @@ -39,6 +40,7 @@ type Querier interface { GetJobConnectionDestinationsByJobIds(ctx context.Context, db DBTX, jobids []pgtype.UUID) ([]NeosyncApiJobDestinationConnectionAssociation, error) GetJobsByAccount(ctx context.Context, db DBTX, accountid pgtype.UUID) ([]NeosyncApiJob, error) GetPersonalAccountByUserId(ctx context.Context, db DBTX, userid pgtype.UUID) (NeosyncApiAccount, error) + GetTeamAccountsByUserId(ctx context.Context, db DBTX, userid pgtype.UUID) ([]NeosyncApiAccount, error) GetTemporalConfigByAccount(ctx context.Context, db DBTX, id pgtype.UUID) (*pg_models.TemporalConfig, error) GetTemporalConfigByUserAccount(ctx context.Context, db DBTX, arg GetTemporalConfigByUserAccountParams) (*pg_models.TemporalConfig, error) GetUser(ctx context.Context, db DBTX, id pgtype.UUID) (NeosyncApiUser, error) diff --git a/backend/gen/go/db/users.sql.go b/backend/gen/go/db/users.sql.go index 78a8378fe2..b4e5c5264c 100644 --- a/backend/gen/go/db/users.sql.go +++ b/backend/gen/go/db/users.sql.go @@ -89,6 +89,29 @@ func (q *Queries) CreatePersonalAccount(ctx context.Context, db DBTX, accountSlu return i, err } +const createTeamAccount = `-- name: CreateTeamAccount :one +INSERT INTO neosync_api.accounts ( + account_type, account_slug +) VALUES ( + 1, $1 +) +RETURNING id, created_at, updated_at, account_type, account_slug, temporal_config +` + +func (q *Queries) CreateTeamAccount(ctx context.Context, db DBTX, accountSlug string) (NeosyncApiAccount, error) { + row := db.QueryRow(ctx, createTeamAccount, accountSlug) + var i NeosyncApiAccount + err := row.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.AccountType, + &i.AccountSlug, + &i.TemporalConfig, + ) + return i, err +} + const createUser = `-- name: CreateUser :one INSERT INTO neosync_api.users ( id, created_at, updated_at @@ -216,6 +239,40 @@ func (q *Queries) GetPersonalAccountByUserId(ctx context.Context, db DBTX, useri return i, err } +const getTeamAccountsByUserId = `-- name: GetTeamAccountsByUserId :many +SELECT a.id, a.created_at, a.updated_at, a.account_type, a.account_slug, a.temporal_config from neosync_api.accounts a +INNER JOIN neosync_api.account_user_associations aua ON aua.account_id = a.id +INNER JOIN neosync_api.users u ON u.id = aua.user_id +WHERE u.id = $1 AND a.account_type = 1 +` + +func (q *Queries) GetTeamAccountsByUserId(ctx context.Context, db DBTX, userid pgtype.UUID) ([]NeosyncApiAccount, error) { + rows, err := db.Query(ctx, getTeamAccountsByUserId, userid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []NeosyncApiAccount + for rows.Next() { + var i NeosyncApiAccount + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.AccountType, + &i.AccountSlug, + &i.TemporalConfig, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getTemporalConfigByAccount = `-- name: GetTemporalConfigByAccount :one SELECT temporal_config FROM neosync_api.accounts diff --git a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go index ced24de172..0f8689042f 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/mock_UserAccountServiceClient.go @@ -79,6 +79,61 @@ func (_c *MockUserAccountServiceClient_ConvertPersonalToTeamAccount_Call) RunAnd return _c } +// CreateTeamAccount provides a mock function with given fields: _a0, _a1 +func (_m *MockUserAccountServiceClient) CreateTeamAccount(_a0 context.Context, _a1 *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]) (*connect.Response[mgmtv1alpha1.CreateTeamAccountResponse], error) { + ret := _m.Called(_a0, _a1) + + var r0 *connect.Response[mgmtv1alpha1.CreateTeamAccountResponse] + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]) (*connect.Response[mgmtv1alpha1.CreateTeamAccountResponse], error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]) *connect.Response[mgmtv1alpha1.CreateTeamAccountResponse]); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*connect.Response[mgmtv1alpha1.CreateTeamAccountResponse]) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUserAccountServiceClient_CreateTeamAccount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateTeamAccount' +type MockUserAccountServiceClient_CreateTeamAccount_Call struct { + *mock.Call +} + +// CreateTeamAccount is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest] +func (_e *MockUserAccountServiceClient_Expecter) CreateTeamAccount(_a0 interface{}, _a1 interface{}) *MockUserAccountServiceClient_CreateTeamAccount_Call { + return &MockUserAccountServiceClient_CreateTeamAccount_Call{Call: _e.mock.On("CreateTeamAccount", _a0, _a1)} +} + +func (_c *MockUserAccountServiceClient_CreateTeamAccount_Call) Run(run func(_a0 context.Context, _a1 *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest])) *MockUserAccountServiceClient_CreateTeamAccount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*connect.Request[mgmtv1alpha1.CreateTeamAccountRequest])) + }) + return _c +} + +func (_c *MockUserAccountServiceClient_CreateTeamAccount_Call) Return(_a0 *connect.Response[mgmtv1alpha1.CreateTeamAccountResponse], _a1 error) *MockUserAccountServiceClient_CreateTeamAccount_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUserAccountServiceClient_CreateTeamAccount_Call) RunAndReturn(run func(context.Context, *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]) (*connect.Response[mgmtv1alpha1.CreateTeamAccountResponse], error)) *MockUserAccountServiceClient_CreateTeamAccount_Call { + _c.Call.Return(run) + return _c +} + // GetAccountTemporalConfig provides a mock function with given fields: _a0, _a1 func (_m *MockUserAccountServiceClient) GetAccountTemporalConfig(_a0 context.Context, _a1 *connect.Request[mgmtv1alpha1.GetAccountTemporalConfigRequest]) (*connect.Response[mgmtv1alpha1.GetAccountTemporalConfigResponse], error) { ret := _m.Called(_a0, _a1) diff --git a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go index 2fadb8d960..41f73bc13a 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect/user_account.connect.go @@ -48,6 +48,9 @@ const ( // UserAccountServiceConvertPersonalToTeamAccountProcedure is the fully-qualified name of the // UserAccountService's ConvertPersonalToTeamAccount RPC. UserAccountServiceConvertPersonalToTeamAccountProcedure = "/mgmt.v1alpha1.UserAccountService/ConvertPersonalToTeamAccount" + // UserAccountServiceCreateTeamAccountProcedure is the fully-qualified name of the + // UserAccountService's CreateTeamAccount RPC. + UserAccountServiceCreateTeamAccountProcedure = "/mgmt.v1alpha1.UserAccountService/CreateTeamAccount" // UserAccountServiceIsUserInAccountProcedure is the fully-qualified name of the // UserAccountService's IsUserInAccount RPC. UserAccountServiceIsUserInAccountProcedure = "/mgmt.v1alpha1.UserAccountService/IsUserInAccount" @@ -66,6 +69,7 @@ type UserAccountServiceClient interface { GetUserAccounts(context.Context, *connect.Request[v1alpha1.GetUserAccountsRequest]) (*connect.Response[v1alpha1.GetUserAccountsResponse], error) SetPersonalAccount(context.Context, *connect.Request[v1alpha1.SetPersonalAccountRequest]) (*connect.Response[v1alpha1.SetPersonalAccountResponse], error) ConvertPersonalToTeamAccount(context.Context, *connect.Request[v1alpha1.ConvertPersonalToTeamAccountRequest]) (*connect.Response[v1alpha1.ConvertPersonalToTeamAccountResponse], error) + CreateTeamAccount(context.Context, *connect.Request[v1alpha1.CreateTeamAccountRequest]) (*connect.Response[v1alpha1.CreateTeamAccountResponse], error) IsUserInAccount(context.Context, *connect.Request[v1alpha1.IsUserInAccountRequest]) (*connect.Response[v1alpha1.IsUserInAccountResponse], error) GetAccountTemporalConfig(context.Context, *connect.Request[v1alpha1.GetAccountTemporalConfigRequest]) (*connect.Response[v1alpha1.GetAccountTemporalConfigResponse], error) SetAccountTemporalConfig(context.Context, *connect.Request[v1alpha1.SetAccountTemporalConfigRequest]) (*connect.Response[v1alpha1.SetAccountTemporalConfigResponse], error) @@ -106,6 +110,11 @@ func NewUserAccountServiceClient(httpClient connect.HTTPClient, baseURL string, baseURL+UserAccountServiceConvertPersonalToTeamAccountProcedure, opts..., ), + createTeamAccount: connect.NewClient[v1alpha1.CreateTeamAccountRequest, v1alpha1.CreateTeamAccountResponse]( + httpClient, + baseURL+UserAccountServiceCreateTeamAccountProcedure, + opts..., + ), isUserInAccount: connect.NewClient[v1alpha1.IsUserInAccountRequest, v1alpha1.IsUserInAccountResponse]( httpClient, baseURL+UserAccountServiceIsUserInAccountProcedure, @@ -131,6 +140,7 @@ type userAccountServiceClient struct { getUserAccounts *connect.Client[v1alpha1.GetUserAccountsRequest, v1alpha1.GetUserAccountsResponse] setPersonalAccount *connect.Client[v1alpha1.SetPersonalAccountRequest, v1alpha1.SetPersonalAccountResponse] convertPersonalToTeamAccount *connect.Client[v1alpha1.ConvertPersonalToTeamAccountRequest, v1alpha1.ConvertPersonalToTeamAccountResponse] + createTeamAccount *connect.Client[v1alpha1.CreateTeamAccountRequest, v1alpha1.CreateTeamAccountResponse] isUserInAccount *connect.Client[v1alpha1.IsUserInAccountRequest, v1alpha1.IsUserInAccountResponse] getAccountTemporalConfig *connect.Client[v1alpha1.GetAccountTemporalConfigRequest, v1alpha1.GetAccountTemporalConfigResponse] setAccountTemporalConfig *connect.Client[v1alpha1.SetAccountTemporalConfigRequest, v1alpha1.SetAccountTemporalConfigResponse] @@ -161,6 +171,11 @@ func (c *userAccountServiceClient) ConvertPersonalToTeamAccount(ctx context.Cont return c.convertPersonalToTeamAccount.CallUnary(ctx, req) } +// CreateTeamAccount calls mgmt.v1alpha1.UserAccountService.CreateTeamAccount. +func (c *userAccountServiceClient) CreateTeamAccount(ctx context.Context, req *connect.Request[v1alpha1.CreateTeamAccountRequest]) (*connect.Response[v1alpha1.CreateTeamAccountResponse], error) { + return c.createTeamAccount.CallUnary(ctx, req) +} + // IsUserInAccount calls mgmt.v1alpha1.UserAccountService.IsUserInAccount. func (c *userAccountServiceClient) IsUserInAccount(ctx context.Context, req *connect.Request[v1alpha1.IsUserInAccountRequest]) (*connect.Response[v1alpha1.IsUserInAccountResponse], error) { return c.isUserInAccount.CallUnary(ctx, req) @@ -183,6 +198,7 @@ type UserAccountServiceHandler interface { GetUserAccounts(context.Context, *connect.Request[v1alpha1.GetUserAccountsRequest]) (*connect.Response[v1alpha1.GetUserAccountsResponse], error) SetPersonalAccount(context.Context, *connect.Request[v1alpha1.SetPersonalAccountRequest]) (*connect.Response[v1alpha1.SetPersonalAccountResponse], error) ConvertPersonalToTeamAccount(context.Context, *connect.Request[v1alpha1.ConvertPersonalToTeamAccountRequest]) (*connect.Response[v1alpha1.ConvertPersonalToTeamAccountResponse], error) + CreateTeamAccount(context.Context, *connect.Request[v1alpha1.CreateTeamAccountRequest]) (*connect.Response[v1alpha1.CreateTeamAccountResponse], error) IsUserInAccount(context.Context, *connect.Request[v1alpha1.IsUserInAccountRequest]) (*connect.Response[v1alpha1.IsUserInAccountResponse], error) GetAccountTemporalConfig(context.Context, *connect.Request[v1alpha1.GetAccountTemporalConfigRequest]) (*connect.Response[v1alpha1.GetAccountTemporalConfigResponse], error) SetAccountTemporalConfig(context.Context, *connect.Request[v1alpha1.SetAccountTemporalConfigRequest]) (*connect.Response[v1alpha1.SetAccountTemporalConfigResponse], error) @@ -219,6 +235,11 @@ func NewUserAccountServiceHandler(svc UserAccountServiceHandler, opts ...connect svc.ConvertPersonalToTeamAccount, opts..., ) + userAccountServiceCreateTeamAccountHandler := connect.NewUnaryHandler( + UserAccountServiceCreateTeamAccountProcedure, + svc.CreateTeamAccount, + opts..., + ) userAccountServiceIsUserInAccountHandler := connect.NewUnaryHandler( UserAccountServiceIsUserInAccountProcedure, svc.IsUserInAccount, @@ -246,6 +267,8 @@ func NewUserAccountServiceHandler(svc UserAccountServiceHandler, opts ...connect userAccountServiceSetPersonalAccountHandler.ServeHTTP(w, r) case UserAccountServiceConvertPersonalToTeamAccountProcedure: userAccountServiceConvertPersonalToTeamAccountHandler.ServeHTTP(w, r) + case UserAccountServiceCreateTeamAccountProcedure: + userAccountServiceCreateTeamAccountHandler.ServeHTTP(w, r) case UserAccountServiceIsUserInAccountProcedure: userAccountServiceIsUserInAccountHandler.ServeHTTP(w, r) case UserAccountServiceGetAccountTemporalConfigProcedure: @@ -281,6 +304,10 @@ func (UnimplementedUserAccountServiceHandler) ConvertPersonalToTeamAccount(conte return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount is not implemented")) } +func (UnimplementedUserAccountServiceHandler) CreateTeamAccount(context.Context, *connect.Request[v1alpha1.CreateTeamAccountRequest]) (*connect.Response[v1alpha1.CreateTeamAccountResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgmt.v1alpha1.UserAccountService.CreateTeamAccount is not implemented")) +} + func (UnimplementedUserAccountServiceHandler) IsUserInAccount(context.Context, *connect.Request[v1alpha1.IsUserInAccountRequest]) (*connect.Response[v1alpha1.IsUserInAccountResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mgmt.v1alpha1.UserAccountService.IsUserInAccount is not implemented")) } diff --git a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go index f5fdada13d..6fa735e196 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go @@ -902,6 +902,100 @@ func (x *AccountTemporalConfig) GetSyncJobQueueName() string { return "" } +type CreateTeamAccountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *CreateTeamAccountRequest) Reset() { + *x = CreateTeamAccountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTeamAccountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTeamAccountRequest) ProtoMessage() {} + +func (x *CreateTeamAccountRequest) ProtoReflect() protoreflect.Message { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[18] + 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 CreateTeamAccountRequest.ProtoReflect.Descriptor instead. +func (*CreateTeamAccountRequest) Descriptor() ([]byte, []int) { + return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{18} +} + +func (x *CreateTeamAccountRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type CreateTeamAccountResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccountId string `protobuf:"bytes,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` +} + +func (x *CreateTeamAccountResponse) Reset() { + *x = CreateTeamAccountResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTeamAccountResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTeamAccountResponse) ProtoMessage() {} + +func (x *CreateTeamAccountResponse) ProtoReflect() protoreflect.Message { + mi := &file_mgmt_v1alpha1_user_account_proto_msgTypes[19] + 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 CreateTeamAccountResponse.ProtoReflect.Descriptor instead. +func (*CreateTeamAccountResponse) Descriptor() ([]byte, []int) { + return file_mgmt_v1alpha1_user_account_proto_rawDescGZIP(), []int{19} +} + +func (x *CreateTeamAccountResponse) GetAccountId() string { + if x != nil { + return x.AccountId + } + return "" +} + var File_mgmt_v1alpha1_user_account_proto protoreflect.FileDescriptor var file_mgmt_v1alpha1_user_account_proto_rawDesc = []byte{ @@ -983,82 +1077,96 @@ var file_mgmt_v1alpha1_user_account_proto_rawDesc = []byte{ 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x6a, 0x6f, 0x62, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x10, 0x73, 0x79, 0x6e, 0x63, 0x4a, 0x6f, 0x62, 0x51, 0x75, 0x65, 0x75, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x2a, 0x70, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, 0x55, 0x53, 0x45, 0x52, 0x5f, - 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x50, 0x45, 0x52, 0x53, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x54, 0x45, 0x41, 0x4d, 0x10, 0x02, 0x32, 0xeb, 0x06, 0x0a, 0x12, 0x55, 0x73, 0x65, 0x72, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, - 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x07, 0x53, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, - 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x12, 0x53, 0x65, 0x74, - 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, - 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x6d, 0x67, 0x6d, 0x74, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, - 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x1c, 0x43, 0x6f, 0x6e, 0x76, 0x65, - 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, - 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, - 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x6d, 0x67, - 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x76, - 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, 0x61, - 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, - 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, - 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, - 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, + 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x37, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, + 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1b, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, + 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x0a, + 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x2a, 0x70, 0x0a, 0x0f, 0x55, 0x73, 0x65, + 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x1d, + 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x1e, 0x0a, 0x1a, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x50, 0x45, 0x52, 0x53, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, + 0x1a, 0x0a, 0x16, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x54, 0x45, 0x41, 0x4d, 0x10, 0x02, 0x32, 0xd5, 0x07, 0x0a, 0x12, + 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, + 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, + 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, + 0x0a, 0x07, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x25, 0x2e, + 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6b, + 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, + 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x89, 0x01, 0x0a, 0x1c, + 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, + 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x2e, 0x6d, + 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x54, 0x6f, 0x54, 0x65, + 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x33, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, + 0x54, 0x6f, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x68, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x6d, + 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x61, 0x6d, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x62, 0x0a, 0x0f, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x6d, 0x67, + 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x49, 0x73, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x6e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0xcc, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x67, 0x6d, - 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x10, 0x55, 0x73, 0x65, 0x72, - 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x50, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x75, 0x63, 0x6c, 0x65, - 0x75, 0x73, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x73, 0x79, 0x6e, 0x63, 0x2f, - 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6d, 0x67, 0x6d, 0x74, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x3b, 0x6d, 0x67, 0x6d, 0x74, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0xa2, 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x2e, 0x56, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x19, 0x4d, 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x67, 0x6d, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, - 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x7d, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x2e, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, + 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x2f, 0x2e, 0x6d, 0x67, 0x6d, 0x74, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x2e, 0x53, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x54, 0x65, 0x6d, 0x70, 0x6f, + 0x72, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x42, 0xcc, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x67, 0x6d, 0x74, + 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x10, 0x55, 0x73, 0x65, 0x72, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x50, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x75, 0x63, 0x6c, 0x65, 0x75, + 0x73, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x73, 0x79, 0x6e, 0x63, 0x2f, 0x62, + 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x6d, 0x67, 0x6d, 0x74, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x3b, 0x6d, 0x67, 0x6d, 0x74, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, + 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x2e, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x0d, 0x4d, 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xe2, 0x02, 0x19, 0x4d, 0x67, 0x6d, 0x74, 0x5c, 0x56, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0xea, 0x02, 0x0e, 0x4d, 0x67, 0x6d, 0x74, 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1074,7 +1182,7 @@ func file_mgmt_v1alpha1_user_account_proto_rawDescGZIP() []byte { } var file_mgmt_v1alpha1_user_account_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_mgmt_v1alpha1_user_account_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_mgmt_v1alpha1_user_account_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_mgmt_v1alpha1_user_account_proto_goTypes = []interface{}{ (UserAccountType)(0), // 0: mgmt.v1alpha1.UserAccountType (*GetUserRequest)(nil), // 1: mgmt.v1alpha1.GetUserRequest @@ -1095,6 +1203,8 @@ var file_mgmt_v1alpha1_user_account_proto_goTypes = []interface{}{ (*SetAccountTemporalConfigRequest)(nil), // 16: mgmt.v1alpha1.SetAccountTemporalConfigRequest (*SetAccountTemporalConfigResponse)(nil), // 17: mgmt.v1alpha1.SetAccountTemporalConfigResponse (*AccountTemporalConfig)(nil), // 18: mgmt.v1alpha1.AccountTemporalConfig + (*CreateTeamAccountRequest)(nil), // 19: mgmt.v1alpha1.CreateTeamAccountRequest + (*CreateTeamAccountResponse)(nil), // 20: mgmt.v1alpha1.CreateTeamAccountResponse } var file_mgmt_v1alpha1_user_account_proto_depIdxs = []int32{ 7, // 0: mgmt.v1alpha1.GetUserAccountsResponse.accounts:type_name -> mgmt.v1alpha1.UserAccount @@ -1107,19 +1217,21 @@ var file_mgmt_v1alpha1_user_account_proto_depIdxs = []int32{ 5, // 7: mgmt.v1alpha1.UserAccountService.GetUserAccounts:input_type -> mgmt.v1alpha1.GetUserAccountsRequest 10, // 8: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:input_type -> mgmt.v1alpha1.SetPersonalAccountRequest 8, // 9: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:input_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountRequest - 12, // 10: mgmt.v1alpha1.UserAccountService.IsUserInAccount:input_type -> mgmt.v1alpha1.IsUserInAccountRequest - 14, // 11: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:input_type -> mgmt.v1alpha1.GetAccountTemporalConfigRequest - 16, // 12: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:input_type -> mgmt.v1alpha1.SetAccountTemporalConfigRequest - 2, // 13: mgmt.v1alpha1.UserAccountService.GetUser:output_type -> mgmt.v1alpha1.GetUserResponse - 4, // 14: mgmt.v1alpha1.UserAccountService.SetUser:output_type -> mgmt.v1alpha1.SetUserResponse - 6, // 15: mgmt.v1alpha1.UserAccountService.GetUserAccounts:output_type -> mgmt.v1alpha1.GetUserAccountsResponse - 11, // 16: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:output_type -> mgmt.v1alpha1.SetPersonalAccountResponse - 9, // 17: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:output_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse - 13, // 18: mgmt.v1alpha1.UserAccountService.IsUserInAccount:output_type -> mgmt.v1alpha1.IsUserInAccountResponse - 15, // 19: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:output_type -> mgmt.v1alpha1.GetAccountTemporalConfigResponse - 17, // 20: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:output_type -> mgmt.v1alpha1.SetAccountTemporalConfigResponse - 13, // [13:21] is the sub-list for method output_type - 5, // [5:13] is the sub-list for method input_type + 19, // 10: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:input_type -> mgmt.v1alpha1.CreateTeamAccountRequest + 12, // 11: mgmt.v1alpha1.UserAccountService.IsUserInAccount:input_type -> mgmt.v1alpha1.IsUserInAccountRequest + 14, // 12: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:input_type -> mgmt.v1alpha1.GetAccountTemporalConfigRequest + 16, // 13: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:input_type -> mgmt.v1alpha1.SetAccountTemporalConfigRequest + 2, // 14: mgmt.v1alpha1.UserAccountService.GetUser:output_type -> mgmt.v1alpha1.GetUserResponse + 4, // 15: mgmt.v1alpha1.UserAccountService.SetUser:output_type -> mgmt.v1alpha1.SetUserResponse + 6, // 16: mgmt.v1alpha1.UserAccountService.GetUserAccounts:output_type -> mgmt.v1alpha1.GetUserAccountsResponse + 11, // 17: mgmt.v1alpha1.UserAccountService.SetPersonalAccount:output_type -> mgmt.v1alpha1.SetPersonalAccountResponse + 9, // 18: mgmt.v1alpha1.UserAccountService.ConvertPersonalToTeamAccount:output_type -> mgmt.v1alpha1.ConvertPersonalToTeamAccountResponse + 20, // 19: mgmt.v1alpha1.UserAccountService.CreateTeamAccount:output_type -> mgmt.v1alpha1.CreateTeamAccountResponse + 13, // 20: mgmt.v1alpha1.UserAccountService.IsUserInAccount:output_type -> mgmt.v1alpha1.IsUserInAccountResponse + 15, // 21: mgmt.v1alpha1.UserAccountService.GetAccountTemporalConfig:output_type -> mgmt.v1alpha1.GetAccountTemporalConfigResponse + 17, // 22: mgmt.v1alpha1.UserAccountService.SetAccountTemporalConfig:output_type -> mgmt.v1alpha1.SetAccountTemporalConfigResponse + 14, // [14:23] is the sub-list for method output_type + 5, // [5:14] is the sub-list for method input_type 5, // [5:5] is the sub-list for extension type_name 5, // [5:5] is the sub-list for extension extendee 0, // [0:5] is the sub-list for field type_name @@ -1347,6 +1459,30 @@ func file_mgmt_v1alpha1_user_account_proto_init() { return nil } } + file_mgmt_v1alpha1_user_account_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTeamAccountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_mgmt_v1alpha1_user_account_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTeamAccountResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1354,7 +1490,7 @@ func file_mgmt_v1alpha1_user_account_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_mgmt_v1alpha1_user_account_proto_rawDesc, NumEnums: 1, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.validate.go b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.validate.go index 8d04f9d217..42d0978fc8 100644 --- a/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.validate.go +++ b/backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.validate.go @@ -2020,3 +2020,211 @@ var _ interface { Cause() error ErrorName() string } = AccountTemporalConfigValidationError{} + +// Validate checks the field values on CreateTeamAccountRequest with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CreateTeamAccountRequest) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CreateTeamAccountRequest with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// CreateTeamAccountRequestMultiError, or nil if none found. +func (m *CreateTeamAccountRequest) ValidateAll() error { + return m.validate(true) +} + +func (m *CreateTeamAccountRequest) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Name + + if len(errors) > 0 { + return CreateTeamAccountRequestMultiError(errors) + } + + return nil +} + +// CreateTeamAccountRequestMultiError is an error wrapping multiple validation +// errors returned by CreateTeamAccountRequest.ValidateAll() if the designated +// constraints aren't met. +type CreateTeamAccountRequestMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CreateTeamAccountRequestMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CreateTeamAccountRequestMultiError) AllErrors() []error { return m } + +// CreateTeamAccountRequestValidationError is the validation error returned by +// CreateTeamAccountRequest.Validate if the designated constraints aren't met. +type CreateTeamAccountRequestValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CreateTeamAccountRequestValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CreateTeamAccountRequestValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CreateTeamAccountRequestValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CreateTeamAccountRequestValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CreateTeamAccountRequestValidationError) ErrorName() string { + return "CreateTeamAccountRequestValidationError" +} + +// Error satisfies the builtin error interface +func (e CreateTeamAccountRequestValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCreateTeamAccountRequest.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = CreateTeamAccountRequestValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CreateTeamAccountRequestValidationError{} + +// Validate checks the field values on CreateTeamAccountResponse with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *CreateTeamAccountResponse) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on CreateTeamAccountResponse with the +// rules defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// CreateTeamAccountResponseMultiError, or nil if none found. +func (m *CreateTeamAccountResponse) ValidateAll() error { + return m.validate(true) +} + +func (m *CreateTeamAccountResponse) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for AccountId + + if len(errors) > 0 { + return CreateTeamAccountResponseMultiError(errors) + } + + return nil +} + +// CreateTeamAccountResponseMultiError is an error wrapping multiple validation +// errors returned by CreateTeamAccountResponse.ValidateAll() if the +// designated constraints aren't met. +type CreateTeamAccountResponseMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m CreateTeamAccountResponseMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m CreateTeamAccountResponseMultiError) AllErrors() []error { return m } + +// CreateTeamAccountResponseValidationError is the validation error returned by +// CreateTeamAccountResponse.Validate if the designated constraints aren't met. +type CreateTeamAccountResponseValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e CreateTeamAccountResponseValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e CreateTeamAccountResponseValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e CreateTeamAccountResponseValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e CreateTeamAccountResponseValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e CreateTeamAccountResponseValidationError) ErrorName() string { + return "CreateTeamAccountResponseValidationError" +} + +// Error satisfies the builtin error interface +func (e CreateTeamAccountResponseValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sCreateTeamAccountResponse.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = CreateTeamAccountResponseValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = CreateTeamAccountResponseValidationError{} diff --git a/backend/internal/nucleusdb/db.go b/backend/internal/nucleusdb/db.go index f470022293..873914e8b3 100644 --- a/backend/internal/nucleusdb/db.go +++ b/backend/internal/nucleusdb/db.go @@ -2,7 +2,6 @@ package nucleusdb import ( "context" - "fmt" "log/slog" "github.com/jackc/pgx/v5" @@ -107,7 +106,6 @@ func HandleSqlRollback( tx SqlRollbackInterface, logger *slog.Logger, ) { - fmt.Println("HERE") if err := tx.Rollback(); err != nil && !isTxDone(err) { logger.Error(err.Error()) } diff --git a/backend/internal/nucleusdb/mock_Tx.go b/backend/internal/nucleusdb/mock_Tx.go new file mode 100644 index 0000000000..1edee167ed --- /dev/null +++ b/backend/internal/nucleusdb/mock_Tx.go @@ -0,0 +1,69 @@ +package nucleusdb + +import ( + "context" + + "github.com/jackc/pgx/v5" + pgconn "github.com/jackc/pgx/v5/pgconn" + mock "github.com/stretchr/testify/mock" +) + +// MockTx is a mock type for the Tx interface +type MockTx struct { + mock.Mock +} + +func (m *MockTx) Begin(ctx context.Context) (pgx.Tx, error) { + args := m.Called(ctx) + return args.Get(0).(pgx.Tx), args.Error(1) +} + +func (m *MockTx) Commit(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockTx) Rollback(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockTx) CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) { + args := m.Called(ctx, tableName, columnNames, rowSrc) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockTx) SendBatch(ctx context.Context, b *pgx.Batch) pgx.BatchResults { + args := m.Called(ctx, b) + return args.Get(0).(pgx.BatchResults) +} + +func (m *MockTx) LargeObjects() pgx.LargeObjects { + args := m.Called() + return args.Get(0).(pgx.LargeObjects) +} + +func (m *MockTx) Prepare(ctx context.Context, name, sql string) (*pgconn.StatementDescription, error) { + args := m.Called(ctx, name, sql) + return args.Get(0).(*pgconn.StatementDescription), args.Error(1) +} + +func (m *MockTx) Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error) { + args := m.Called(ctx, sql, arguments) + return args.Get(0).(pgconn.CommandTag), args.Error(1) +} + +func (m *MockTx) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) { + callArgs := m.Called(ctx, sql, args) + return callArgs.Get(0).(pgx.Rows), callArgs.Error(1) +} + +func (m *MockTx) QueryRow(ctx context.Context, sql string, args ...any) pgx.Row { + callArgs := m.Called(ctx, sql, args) + return callArgs.Get(0).(pgx.Row) +} + +func (m *MockTx) Conn() *pgx.Conn { + args := m.Called() + return args.Get(0).(*pgx.Conn) +} diff --git a/backend/internal/nucleusdb/users.go b/backend/internal/nucleusdb/users.go index 4ea7c719e3..0b5fb4ff95 100644 --- a/backend/internal/nucleusdb/users.go +++ b/backend/internal/nucleusdb/users.go @@ -2,9 +2,12 @@ package nucleusdb import ( "context" + "fmt" + "strings" "github.com/jackc/pgx/v5/pgtype" db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" + nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors" ) func (d *NucleusDb) SetUserByAuth0Id( @@ -103,3 +106,40 @@ func (d *NucleusDb) SetPersonalAccount( } return personalAccount, nil } + +func (d *NucleusDb) CreateTeamAccount( + ctx context.Context, + userId pgtype.UUID, + teamName string, +) (*db_queries.NeosyncApiAccount, error) { + var teamAccount *db_queries.NeosyncApiAccount + if err := d.WithTx(ctx, nil, func(dbtx BaseDBTX) error { + accounts, err := d.Q.GetAccountsByUser(ctx, dbtx, userId) + if err != nil && !IsNoRows(err) { + return err + } else if err != nil && IsNoRows(err) { + accounts = []db_queries.NeosyncApiAccount{} + } + for _, account := range accounts { + if strings.EqualFold(account.AccountSlug, teamName) { + return nucleuserrors.NewAlreadyExists(fmt.Sprintf("team account with the name %s already exists", teamName)) + } + } + account, err := d.Q.CreateTeamAccount(ctx, dbtx, teamName) + if err != nil { + return err + } + teamAccount = &account + _, err = d.Q.CreateAccountUserAssociation(ctx, dbtx, db_queries.CreateAccountUserAssociationParams{ + AccountID: account.ID, + UserID: userId, + }) + if err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + return teamAccount, nil +} diff --git a/backend/internal/nucleusdb/users_test.go b/backend/internal/nucleusdb/users_test.go new file mode 100644 index 0000000000..a62fa32d02 --- /dev/null +++ b/backend/internal/nucleusdb/users_test.go @@ -0,0 +1,132 @@ +package nucleusdb + +import ( + "context" + "database/sql" + "errors" + "testing" + + db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" + "github.com/stretchr/testify/mock" + "github.com/zeebo/assert" +) + +const ( + anonymousUserId = "00000000-0000-0000-0000-000000000000" + mockUserId = "d5e29f1f-b920-458c-8b86-f3a180e06d98" + mockAccountId = "5629813e-1a35-4874-922c-9827d85f0378" + mockTeamName = "team-name" +) + +// CreateTeamAccount +func Test_CreateTeamAccount(t *testing.T) { + dbtxMock := NewMockDBTX(t) + querierMock := db_queries.NewMockQuerier(t) + mockTx := new(MockTx) + + userUuid, _ := ToUuid(mockUserId) + accountUuid, _ := ToUuid(mockAccountId) + ctx := context.Background() + + service := New(dbtxMock, querierMock) + + dbtxMock.On("Begin", ctx).Return(mockTx, nil) + querierMock.On("GetAccountsByUser", ctx, mockTx, userUuid).Return([]db_queries.NeosyncApiAccount{{AccountSlug: "other"}}, nil) + querierMock.On("CreateTeamAccount", ctx, mockTx, mockTeamName).Return(db_queries.NeosyncApiAccount{ID: accountUuid, AccountSlug: mockTeamName}, nil) + querierMock.On("CreateAccountUserAssociation", ctx, mockTx, db_queries.CreateAccountUserAssociationParams{ + AccountID: accountUuid, + UserID: userUuid, + }).Return(db_queries.NeosyncApiAccountUserAssociation{}, nil) + mockTx.On("Commit", ctx).Return(nil) + mockTx.On("Rollback", ctx).Return(nil) + + resp, err := service.CreateTeamAccount(context.Background(), userUuid, mockTeamName) + + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, accountUuid, resp.ID) + assert.Equal(t, mockTeamName, resp.AccountSlug) +} + +func Test_CreateTeamAccount_AlreadyExists(t *testing.T) { + dbtxMock := NewMockDBTX(t) + querierMock := db_queries.NewMockQuerier(t) + mockTx := new(MockTx) + + userUuid, _ := ToUuid(mockUserId) + ctx := context.Background() + + service := New(dbtxMock, querierMock) + + dbtxMock.On("Begin", ctx).Return(mockTx, nil) + querierMock.On("GetAccountsByUser", ctx, mockTx, userUuid).Return([]db_queries.NeosyncApiAccount{{AccountSlug: mockTeamName}}, nil) + mockTx.On("Rollback", ctx).Return(nil) + + resp, err := service.CreateTeamAccount(context.Background(), userUuid, mockTeamName) + + querierMock.AssertNotCalled(t, "CreateTeamAccount", mock.Anything, mock.Anything, mock.Anything) + querierMock.AssertNotCalled(t, "CreateAccountUserAssociation", mock.Anything, mock.Anything, mock.Anything) + mockTx.AssertNotCalled(t, "Commit", mock.Anything) + + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_CreateTeamAccount_NoRows(t *testing.T) { + dbtxMock := NewMockDBTX(t) + querierMock := db_queries.NewMockQuerier(t) + mockTx := new(MockTx) + + userUuid, _ := ToUuid(mockUserId) + accountUuid, _ := ToUuid(mockAccountId) + var nilAccounts []db_queries.NeosyncApiAccount + ctx := context.Background() + + service := New(dbtxMock, querierMock) + + dbtxMock.On("Begin", ctx).Return(mockTx, nil) + querierMock.On("GetAccountsByUser", ctx, mockTx, userUuid).Return(nilAccounts, sql.ErrNoRows) + querierMock.On("CreateTeamAccount", ctx, mockTx, mockTeamName).Return(db_queries.NeosyncApiAccount{ID: accountUuid, AccountSlug: mockTeamName}, nil) + querierMock.On("CreateAccountUserAssociation", ctx, mockTx, db_queries.CreateAccountUserAssociationParams{ + AccountID: accountUuid, + UserID: userUuid, + }).Return(db_queries.NeosyncApiAccountUserAssociation{}, nil) + mockTx.On("Commit", ctx).Return(nil) + mockTx.On("Rollback", ctx).Return(nil) + + resp, err := service.CreateTeamAccount(context.Background(), userUuid, mockTeamName) + + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, accountUuid, resp.ID) + assert.Equal(t, mockTeamName, resp.AccountSlug) +} + +func Test_CreateTeamAccount_Rollback(t *testing.T) { + dbtxMock := NewMockDBTX(t) + querierMock := db_queries.NewMockQuerier(t) + mockTx := new(MockTx) + + userUuid, _ := ToUuid(mockUserId) + accountUuid, _ := ToUuid(mockAccountId) + ctx := context.Background() + var nilAssociation db_queries.NeosyncApiAccountUserAssociation + + service := New(dbtxMock, querierMock) + + dbtxMock.On("Begin", ctx).Return(mockTx, nil) + querierMock.On("GetAccountsByUser", ctx, mockTx, userUuid).Return([]db_queries.NeosyncApiAccount{{AccountSlug: "other"}}, nil) + querierMock.On("CreateTeamAccount", ctx, mockTx, mockTeamName).Return(db_queries.NeosyncApiAccount{ID: accountUuid, AccountSlug: mockTeamName}, nil) + querierMock.On("CreateAccountUserAssociation", ctx, mockTx, db_queries.CreateAccountUserAssociationParams{ + AccountID: accountUuid, + UserID: userUuid, + }).Return(nilAssociation, errors.New("sad")) + mockTx.On("Rollback", ctx).Return(nil) + + resp, err := service.CreateTeamAccount(context.Background(), userUuid, mockTeamName) + + mockTx.AssertCalled(t, "Rollback", ctx) + mockTx.AssertNotCalled(t, "Commit", mock.Anything) + assert.Error(t, err) + assert.Nil(t, resp) +} diff --git a/backend/protos/mgmt/v1alpha1/user_account.proto b/backend/protos/mgmt/v1alpha1/user_account.proto index 526cadfa6e..4b2e3cfaf7 100644 --- a/backend/protos/mgmt/v1alpha1/user_account.proto +++ b/backend/protos/mgmt/v1alpha1/user_account.proto @@ -67,6 +67,13 @@ message AccountTemporalConfig { string sync_job_queue_name = 3 [(buf.validate.field).string.min_len = 1]; } +message CreateTeamAccountRequest { + string name = 1 [(buf.validate.field).string.min_len = 1]; +} +message CreateTeamAccountResponse { + string account_id = 1; +} + service UserAccountService { rpc GetUser(GetUserRequest) returns (GetUserResponse) {} rpc SetUser(SetUserRequest) returns (SetUserResponse) {} @@ -76,6 +83,7 @@ service UserAccountService { rpc SetPersonalAccount(SetPersonalAccountRequest) returns (SetPersonalAccountResponse) {} rpc ConvertPersonalToTeamAccount(ConvertPersonalToTeamAccountRequest) returns (ConvertPersonalToTeamAccountResponse) {} + rpc CreateTeamAccount(CreateTeamAccountRequest) returns (CreateTeamAccountResponse) {} rpc IsUserInAccount(IsUserInAccountRequest) returns (IsUserInAccountResponse) {} diff --git a/backend/services/mgmt/v1alpha1/job-service/jobs.go b/backend/services/mgmt/v1alpha1/job-service/jobs.go index 868265e65a..9e163c87b5 100644 --- a/backend/services/mgmt/v1alpha1/job-service/jobs.go +++ b/backend/services/mgmt/v1alpha1/job-service/jobs.go @@ -410,7 +410,6 @@ func (s *Service) CreateJob( hasNs, err := s.doesAccountHaveTemporalNamespace(ctx, *accountUuid, logger) if err != nil { - fmt.Println("account doesn't have namespace") return nil, err } if !hasNs { diff --git a/backend/services/mgmt/v1alpha1/user-account-service/users.go b/backend/services/mgmt/v1alpha1/user-account-service/users.go index e02c261f15..4dfabc0aa9 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/users.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/users.go @@ -168,3 +168,26 @@ func (s *Service) IsUserInAccount( Ok: count > 0, }), nil } + +func (s *Service) CreateTeamAccount( + ctx context.Context, + req *connect.Request[mgmtv1alpha1.CreateTeamAccountRequest], +) (*connect.Response[mgmtv1alpha1.CreateTeamAccountResponse], error) { + user, err := s.GetUser(ctx, connect.NewRequest(&mgmtv1alpha1.GetUserRequest{})) + if err != nil { + return nil, err + } + userId, err := nucleusdb.ToUuid(user.Msg.UserId) + if err != nil { + return nil, err + } + + account, err := s.db.CreateTeamAccount(ctx, userId, req.Msg.Name) + if err != nil { + return nil, err + } + + return connect.NewResponse(&mgmtv1alpha1.CreateTeamAccountResponse{ + AccountId: nucleusdb.UUIDString(account.ID), + }), nil +} diff --git a/backend/services/mgmt/v1alpha1/user-account-service/users_test.go b/backend/services/mgmt/v1alpha1/user-account-service/users_test.go index 4d86ad8eb2..53987c7040 100644 --- a/backend/services/mgmt/v1alpha1/user-account-service/users_test.go +++ b/backend/services/mgmt/v1alpha1/user-account-service/users_test.go @@ -241,6 +241,34 @@ func Test_IsUserInAccount_False(t *testing.T) { assert.Equal(t, false, resp.Msg.Ok) } +func Test_CreateTeamAccount(t *testing.T) { + m := createServiceMock(t, &Config{IsAuthEnabled: true}) + mockTx := new(nucleusdb.MockTx) + + mockTeamName := "team-name" + ctx := getAuthenticatedCtxMock(mockAuthProvider) + userAssociation := getUserIdentityProviderAssociationMock(mockUserId, mockAuthProvider) + accountUuid, _ := nucleusdb.ToUuid(mockAccountId) + userUuid, _ := nucleusdb.ToUuid(mockUserId) + m.QuerierMock.On("GetUserAssociationByAuth0Id", ctx, mock.Anything, mockAuthProvider).Return(userAssociation, nil) + m.DbtxMock.On("Begin", ctx).Return(mockTx, nil) + m.QuerierMock.On("GetAccountsByUser", ctx, mockTx, userUuid).Return([]db_queries.NeosyncApiAccount{{AccountSlug: "other"}}, nil) + m.QuerierMock.On("CreateTeamAccount", ctx, mockTx, mockTeamName).Return(db_queries.NeosyncApiAccount{ID: accountUuid, AccountSlug: mockTeamName}, nil) + m.QuerierMock.On("CreateAccountUserAssociation", ctx, mockTx, db_queries.CreateAccountUserAssociationParams{ + AccountID: accountUuid, + UserID: userUuid, + }).Return(db_queries.NeosyncApiAccountUserAssociation{}, nil) + mockTx.On("Commit", ctx).Return(nil) + mockTx.On("Rollback", ctx).Return(nil) + + resp, err := m.Service.CreateTeamAccount(ctx, &connect.Request[mgmtv1alpha1.CreateTeamAccountRequest]{Msg: &mgmtv1alpha1.CreateTeamAccountRequest{Name: mockTeamName}}) + + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, mockAccountId, resp.Msg.AccountId) + +} + type serviceMocks struct { Service *Service DbtxMock *nucleusdb.MockDBTX diff --git a/backend/sql/postgresql/queries/users.sql b/backend/sql/postgresql/queries/users.sql index 78ffb88fdb..627aa51ef5 100644 --- a/backend/sql/postgresql/queries/users.sql +++ b/backend/sql/postgresql/queries/users.sql @@ -37,6 +37,12 @@ INNER JOIN neosync_api.account_user_associations aua ON aua.account_id = a.id INNER JOIN neosync_api.users u ON u.id = aua.user_id WHERE u.id = sqlc.arg('userId') AND a.account_type = 0; +-- name: GetTeamAccountsByUserId :many +SELECT a.* from neosync_api.accounts a +INNER JOIN neosync_api.account_user_associations aua ON aua.account_id = a.id +INNER JOIN neosync_api.users u ON u.id = aua.user_id +WHERE u.id = sqlc.arg('userId') AND a.account_type = 1; + -- name: CreatePersonalAccount :one INSERT INTO neosync_api.accounts ( account_type, account_slug @@ -45,6 +51,14 @@ INSERT INTO neosync_api.accounts ( ) RETURNING *; +-- name: CreateTeamAccount :one +INSERT INTO neosync_api.accounts ( + account_type, account_slug +) VALUES ( + 1, $1 +) +RETURNING *; + -- name: GetAccountsByUser :many SELECT a.* from neosync_api.accounts a INNER JOIN neosync_api.account_user_associations aua ON aua.account_id = a.id diff --git a/frontend/app/api/teams/route.ts b/frontend/app/api/teams/route.ts new file mode 100644 index 0000000000..aa5aa75153 --- /dev/null +++ b/frontend/app/api/teams/route.ts @@ -0,0 +1,10 @@ +import { withNeosyncContext } from '@/api-only/neosync-context'; +import { CreateTeamAccountRequest } from '@/neosync-api-client/mgmt/v1alpha1/user_account_pb'; +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(req: NextRequest): Promise<NextResponse> { + return withNeosyncContext(async (ctx) => { + const body = CreateTeamAccountRequest.fromJson(await req.json()); + return ctx.userClient.createTeamAccount(body); + })(req); +} diff --git a/frontend/components/AccountSwitcher.tsx b/frontend/components/AccountSwitcher.tsx index 4172902f20..1adb119c12 100644 --- a/frontend/components/AccountSwitcher.tsx +++ b/frontend/components/AccountSwitcher.tsx @@ -1,13 +1,20 @@ 'use client'; - -import { ReactElement } from 'react'; - -import { CaretSortIcon, CheckIcon } from '@radix-ui/react-icons'; +import { + CaretSortIcon, + CheckIcon, + PlusCircledIcon, +} from '@radix-ui/react-icons'; import * as React from 'react'; +import { ReactElement } from 'react'; import { useGetUserAccounts } from '@/libs/hooks/useUserAccounts'; import { cn } from '@/libs/utils'; -import { UserAccountType } from '@/neosync-api-client/mgmt/v1alpha1/user_account_pb'; +import { + CreateTeamAccountRequest, + CreateTeamAccountResponse, + UserAccountType, +} from '@/neosync-api-client/mgmt/v1alpha1/user_account_pb'; +import { getErrorMessage } from '@/util/util'; import { useAccount } from './providers/account-provider'; import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; import { Button } from './ui/button'; @@ -18,105 +25,208 @@ import { CommandInput, CommandItem, CommandList, + CommandSeparator, } from './ui/command'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from './ui/dialog'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'; +import { Skeleton } from './ui/skeleton'; +import { toast } from './ui/use-toast'; interface Props {} export default function AccountSwitcher(_: Props): ReactElement { const { account, setAccount } = useAccount(); - const { data } = useGetUserAccounts(); + const { data, mutate, isLoading } = useGetUserAccounts(); const [open, setOpen] = React.useState(false); + const [showNewTeamDialog, setShowNewTeamDialog] = React.useState(false); + const [teamName, setTeamName] = React.useState(''); const personalAccounts = data?.accounts.filter((a) => a.type == UserAccountType.PERSONAL) || []; const teamAccounts = data?.accounts.filter((a) => a.type == UserAccountType.TEAM) || []; + async function onSubmit(teamName: string): Promise<void> { + try { + await createTeamAccount(teamName); + setShowNewTeamDialog(false); + mutate(); + toast({ + title: 'Successfully created team!', + variant: 'success', + }); + } catch (err) { + console.error(err); + toast({ + title: 'Unable to create team', + description: getErrorMessage(err), + variant: 'destructive', + }); + } + } + if (isLoading) { + return <Skeleton className=" h-full w-[200px]" />; + } + return ( - <Popover open={open} onOpenChange={setOpen}> - <PopoverTrigger asChild> - <Button - variant="outline" - role="combobox" - aria-expanded={open} - aria-label="Select a team" - className="w-[200px] justify-between" - > - <Avatar className="mr-2 h-5 w-5"> - <AvatarImage - src={`https://avatar.vercel.sh/${account?.id}.png`} - alt={account?.name} - /> - <AvatarFallback>SC</AvatarFallback> - </Avatar> - {account?.name} - <CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" /> - </Button> - </PopoverTrigger> - <PopoverContent className="w-[200px] p-0"> - <Command> - <CommandList> - <CommandInput placeholder="Search account..." /> - <CommandEmpty>No Account found.</CommandEmpty> - <CommandGroup key="personal" heading="Personal"> - {personalAccounts.map((a) => ( - <CommandItem - key={a.id} - onSelect={() => { - setAccount(a); - setOpen(false); - }} - className="text-sm" - > - <Avatar className="mr-2 h-5 w-5"> - <AvatarImage - src={`https://avatar.vercel.sh/${a.name}.png`} - alt={a.name} - className="grayscale" + <Dialog open={showNewTeamDialog} onOpenChange={setShowNewTeamDialog}> + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + variant="outline" + role="combobox" + aria-expanded={open} + aria-label="Select a team" + className="w-[200px] justify-between" + > + <Avatar className="mr-2 h-5 w-5"> + <AvatarImage + src={`https://avatar.vercel.sh/${account?.id}.png`} + alt={account?.name} + /> + <AvatarFallback>SC</AvatarFallback> + </Avatar> + {account?.name} + <CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + <PopoverContent className="w-[200px] p-0"> + <Command> + <CommandList> + <CommandInput placeholder="Search account..." /> + <CommandEmpty>No Account found.</CommandEmpty> + <CommandGroup key="personal" heading="Personal"> + {personalAccounts.map((a) => ( + <CommandItem + key={a.id} + onSelect={() => { + setAccount(a); + setOpen(false); + }} + className="text-sm" + > + <Avatar className="mr-2 h-5 w-5"> + <AvatarImage + src={`https://avatar.vercel.sh/${a.name}.png`} + alt={a.name} + className="grayscale" + /> + <AvatarFallback>SC</AvatarFallback> + </Avatar> + {a.name} + <CheckIcon + className={cn( + 'ml-auto h-4 w-4', + account?.id === a.id ? 'opacity-100' : 'opacity-0' + )} /> - <AvatarFallback>SC</AvatarFallback> - </Avatar> - {a.name} - <CheckIcon - className={cn( - 'ml-auto h-4 w-4', - account?.id === a.id ? 'opacity-100' : 'opacity-0' - )} - /> - </CommandItem> - ))} - </CommandGroup> - <CommandGroup key="team" heading="Team"> - {teamAccounts.map((a) => ( - <CommandItem - key={a.id} - onSelect={() => { - setAccount(a); - setOpen(false); - }} - className="text-sm" - > - <Avatar className="mr-2 h-5 w-5"> - <AvatarImage - src={`https://avatar.vercel.sh/${a.name}.png`} - alt={a.name} - className="grayscale" + </CommandItem> + ))} + </CommandGroup> + <CommandGroup key="team" heading="Team"> + {teamAccounts.map((a) => ( + <CommandItem + key={a.id} + onSelect={() => { + setAccount(a); + setOpen(false); + }} + className="text-sm" + > + <Avatar className="mr-2 h-5 w-5"> + <AvatarImage + src={`https://avatar.vercel.sh/${a.name}.png`} + alt={a.name} + className="grayscale" + /> + </Avatar> + {a.name} + <CheckIcon + className={cn( + 'ml-auto h-4 w-4', + account?.id === a.id ? 'opacity-100' : 'opacity-0' + )} /> - </Avatar> - {a.name} - <CheckIcon - className={cn( - 'ml-auto h-4 w-4', - account?.id === a.id ? 'opacity-100' : 'opacity-0' - )} - /> - </CommandItem> - ))} - </CommandGroup> - </CommandList> - </Command> - </PopoverContent> - </Popover> + </CommandItem> + ))} + </CommandGroup> + </CommandList> + <CommandSeparator /> + <CommandList> + <CommandGroup> + <DialogTrigger asChild> + <CommandItem + onSelect={() => { + setOpen(false); + setShowNewTeamDialog(true); + }} + > + <PlusCircledIcon className="mr-2 h-5 w-5" /> + Create Team + </CommandItem> + </DialogTrigger> + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + <DialogContent> + <DialogHeader> + <DialogTitle>Create team</DialogTitle> + <DialogDescription>Add a new team to manage jobs.</DialogDescription> + </DialogHeader> + <div> + <div className="space-y-4 py-2 pb-4"> + <div className="space-y-2"> + <Label htmlFor="name">Team name</Label> + <Input + id="name" + placeholder="Acme Inc." + onChange={(event) => setTeamName(event.target.value)} + /> + </div> + </div> + </div> + <DialogFooter> + <Button variant="outline" onClick={() => setShowNewTeamDialog(false)}> + Cancel + </Button> + <Button type="submit" onClick={() => onSubmit(teamName)}> + Continue + </Button> + </DialogFooter> + </DialogContent> + </Dialog> ); } + +async function createTeamAccount( + teamName: string +): Promise<CreateTeamAccountResponse | undefined> { + const res = await fetch(`/api/teams`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify( + new CreateTeamAccountRequest({ + name: teamName, + }) + ), + }); + if (!res.ok) { + const body = await res.json(); + throw new Error(body.message); + } + return CreateTeamAccountResponse.fromJson(await res.json()); +} diff --git a/frontend/neosync-api-client/mgmt/v1alpha1/user_account_connect.ts b/frontend/neosync-api-client/mgmt/v1alpha1/user_account_connect.ts index 34eb165250..a8cd930449 100644 --- a/frontend/neosync-api-client/mgmt/v1alpha1/user_account_connect.ts +++ b/frontend/neosync-api-client/mgmt/v1alpha1/user_account_connect.ts @@ -3,7 +3,7 @@ /* eslint-disable */ // @ts-nocheck -import { ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, IsUserInAccountRequest, IsUserInAccountResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse } from "./user_account_pb"; +import { ConvertPersonalToTeamAccountRequest, ConvertPersonalToTeamAccountResponse, CreateTeamAccountRequest, CreateTeamAccountResponse, GetAccountTemporalConfigRequest, GetAccountTemporalConfigResponse, GetUserAccountsRequest, GetUserAccountsResponse, GetUserRequest, GetUserResponse, IsUserInAccountRequest, IsUserInAccountResponse, SetAccountTemporalConfigRequest, SetAccountTemporalConfigResponse, SetPersonalAccountRequest, SetPersonalAccountResponse, SetUserRequest, SetUserResponse } from "./user_account_pb"; import { MethodKind } from "@bufbuild/protobuf"; /** @@ -57,6 +57,15 @@ export const UserAccountService = { O: ConvertPersonalToTeamAccountResponse, kind: MethodKind.Unary, }, + /** + * @generated from rpc mgmt.v1alpha1.UserAccountService.CreateTeamAccount + */ + createTeamAccount: { + name: "CreateTeamAccount", + I: CreateTeamAccountRequest, + O: CreateTeamAccountResponse, + kind: MethodKind.Unary, + }, /** * @generated from rpc mgmt.v1alpha1.UserAccountService.IsUserInAccount */ diff --git a/frontend/neosync-api-client/mgmt/v1alpha1/user_account_pb.ts b/frontend/neosync-api-client/mgmt/v1alpha1/user_account_pb.ts index 7840418cdd..80fc108e47 100644 --- a/frontend/neosync-api-client/mgmt/v1alpha1/user_account_pb.ts +++ b/frontend/neosync-api-client/mgmt/v1alpha1/user_account_pb.ts @@ -692,3 +692,77 @@ export class AccountTemporalConfig extends Message<AccountTemporalConfig> { } } +/** + * @generated from message mgmt.v1alpha1.CreateTeamAccountRequest + */ +export class CreateTeamAccountRequest extends Message<CreateTeamAccountRequest> { + /** + * @generated from field: string name = 1; + */ + name = ""; + + constructor(data?: PartialMessage<CreateTeamAccountRequest>) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "mgmt.v1alpha1.CreateTeamAccountRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateTeamAccountRequest { + return new CreateTeamAccountRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateTeamAccountRequest { + return new CreateTeamAccountRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateTeamAccountRequest { + return new CreateTeamAccountRequest().fromJsonString(jsonString, options); + } + + static equals(a: CreateTeamAccountRequest | PlainMessage<CreateTeamAccountRequest> | undefined, b: CreateTeamAccountRequest | PlainMessage<CreateTeamAccountRequest> | undefined): boolean { + return proto3.util.equals(CreateTeamAccountRequest, a, b); + } +} + +/** + * @generated from message mgmt.v1alpha1.CreateTeamAccountResponse + */ +export class CreateTeamAccountResponse extends Message<CreateTeamAccountResponse> { + /** + * @generated from field: string account_id = 1; + */ + accountId = ""; + + constructor(data?: PartialMessage<CreateTeamAccountResponse>) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "mgmt.v1alpha1.CreateTeamAccountResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "account_id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): CreateTeamAccountResponse { + return new CreateTeamAccountResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): CreateTeamAccountResponse { + return new CreateTeamAccountResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): CreateTeamAccountResponse { + return new CreateTeamAccountResponse().fromJsonString(jsonString, options); + } + + static equals(a: CreateTeamAccountResponse | PlainMessage<CreateTeamAccountResponse> | undefined, b: CreateTeamAccountResponse | PlainMessage<CreateTeamAccountResponse> | undefined): boolean { + return proto3.util.equals(CreateTeamAccountResponse, a, b); + } +} +