From 7b8336dc102c979923f9feadd0b08686127fdd48 Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 30 Mar 2023 13:24:32 +0900 Subject: [PATCH 1/4] minor fix: reflect mapper --- pkg/domain/mapper.go | 29 +++++++++++++++++++ pkg/domain/mapper_test.go | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 pkg/domain/mapper.go create mode 100644 pkg/domain/mapper_test.go diff --git a/pkg/domain/mapper.go b/pkg/domain/mapper.go new file mode 100644 index 00000000..6e5ad776 --- /dev/null +++ b/pkg/domain/mapper.go @@ -0,0 +1,29 @@ +package domain + +import ( + "fmt" + "reflect" +) + +func Map(src interface{}, dst interface{}) error { + + srcVal := reflect.ValueOf(src) + srcType := srcVal.Type() + + dstVal := reflect.ValueOf(dst) + if dstVal.Kind() != reflect.Ptr || dstVal.IsNil() { + return fmt.Errorf("dst must be a non-nil pointer") + } + dstElem := dstVal.Elem() + + for i := 0; i < srcVal.NumField(); i++ { + fieldName := srcType.Field(i).Name + dstField := dstElem.FieldByName(fieldName) + if dstField.IsValid() && dstField.CanSet() { + dstField.Set(srcVal.Field(i)) + } + } + + return nil + +} diff --git a/pkg/domain/mapper_test.go b/pkg/domain/mapper_test.go new file mode 100644 index 00000000..42ae92f6 --- /dev/null +++ b/pkg/domain/mapper_test.go @@ -0,0 +1,60 @@ +package domain + +import ( + "fmt" + "testing" + "time" +) + +// test case +func TestConvert(t *testing.T) { + type args struct { + src interface{} + dst interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "test case 1", + args: args{ + src: CreateOrganizationRequest{ + Name: "test", + Description: "test", + Phone: "test", + }, + dst: &Organization{}, + }, + wantErr: false, + }, + { + name: "test case 2", + args: args{ + src: Organization{ + ID: "", + Name: "test", + Description: "test", + Phone: "test", + Status: "PENDING", + StatusDescription: "good", + Creator: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + dst: &GetOrganizationResponse{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Map(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr { + t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr) + } else { + fmt.Println(tt.args.dst) + } + }) + } +} From 1d6eb350888b70bd9a9e1216071b2aed6e78646e Mon Sep 17 00:00:00 2001 From: donggyu Date: Thu, 30 Mar 2023 14:09:20 +0900 Subject: [PATCH 2/4] minor fix: reflect mapper --- pkg/domain/mapper_test.go | 60 --------------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 pkg/domain/mapper_test.go diff --git a/pkg/domain/mapper_test.go b/pkg/domain/mapper_test.go deleted file mode 100644 index 42ae92f6..00000000 --- a/pkg/domain/mapper_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package domain - -import ( - "fmt" - "testing" - "time" -) - -// test case -func TestConvert(t *testing.T) { - type args struct { - src interface{} - dst interface{} - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "test case 1", - args: args{ - src: CreateOrganizationRequest{ - Name: "test", - Description: "test", - Phone: "test", - }, - dst: &Organization{}, - }, - wantErr: false, - }, - { - name: "test case 2", - args: args{ - src: Organization{ - ID: "", - Name: "test", - Description: "test", - Phone: "test", - Status: "PENDING", - StatusDescription: "good", - Creator: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - }, - dst: &GetOrganizationResponse{}, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Map(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr { - t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr) - } else { - fmt.Println(tt.args.dst) - } - }) - } -} From ae32ff3cdf01917162faab0091943e9891bc3922 Mon Sep 17 00:00:00 2001 From: donggyu Date: Mon, 3 Apr 2023 21:33:54 +0900 Subject: [PATCH 3/4] minor fix: reflect mapper --- internal/delivery/http/auth.go | 13 +-- internal/delivery/http/organization.go | 49 ++++----- internal/delivery/http/user.go | 146 ++++++++++++++++++------- internal/keycloak/keycloak.go | 20 +--- internal/repository/mapper.go | 72 ++++++++++++ internal/repository/mapper_test.go | 79 +++++++++++++ internal/repository/organization.go | 16 +-- internal/repository/user.go | 54 +++++---- internal/route/route.go | 8 -- internal/usecase/auth.go | 12 -- internal/usecase/organization.go | 18 ++- internal/usecase/user.go | 64 +++++------ pkg/domain/mapper.go | 55 +++++++++- pkg/domain/mapper_test.go | 97 ++++++++++++++++ pkg/domain/organization.go | 83 +++++++------- pkg/domain/user.go | 134 ++++++++++++++--------- 16 files changed, 642 insertions(+), 278 deletions(-) create mode 100644 internal/repository/mapper.go create mode 100644 internal/repository/mapper_test.go create mode 100644 pkg/domain/mapper_test.go diff --git a/internal/delivery/http/auth.go b/internal/delivery/http/auth.go index 67376136..3d523cd8 100644 --- a/internal/delivery/http/auth.go +++ b/internal/delivery/http/auth.go @@ -34,7 +34,7 @@ func NewAuthHandler(h usecase.IAuthUsecase) IAuthHandler { // @Accept json // @Produce json // @Param body body domain.LoginRequest true "account info" -// @Success 200 {object} domain.User "user detail" +// @Success 200 {object} domain.LoginResponse "user detail" // @Router /auth/login [post] func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { input := domain.LoginRequest{} @@ -52,13 +52,8 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { return } - var out struct { - User domain.User `json:"user"` - } - - out.User = user - - //_ = h.Repository.AddHistory(user.ID.String(), "", "login", fmt.Sprintf("[%s] 님이 로그인하였습니다.", input.AccountId)) + var out domain.LoginResponse + domain.Map(user, &out) ResponseJSON(w, http.StatusOK, out) @@ -80,10 +75,8 @@ func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) { func (h *AuthHandler) FindId(w http.ResponseWriter, r *http.Request) { //TODO implement me - panic("implement me") } func (h *AuthHandler) FindPassword(w http.ResponseWriter, r *http.Request) { //TODO implement me - panic("implement me") } diff --git a/internal/delivery/http/organization.go b/internal/delivery/http/organization.go index 518d48ce..bc97b3d6 100644 --- a/internal/delivery/http/organization.go +++ b/internal/delivery/http/organization.go @@ -46,16 +46,16 @@ func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http. } ctx := r.Context() - organization := input.ToOrganization() - organization.Creator = "" + var organization domain.Organization + err = domain.Map(input, &organization) - organizationId, err := h.usecase.Create(ctx, organization) + organizationId, err := h.usecase.Create(ctx, &organization) if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) ErrorJSON(w, err) return } - + organization.ID = organizationId // Admin user 생성 _, err = h.userUsecase.CreateAdmin(organizationId) if err != nil { @@ -64,13 +64,9 @@ func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http. return } - var out struct { - OrganizationId string `json:"organizationId"` - } - - out.OrganizationId = organizationId + var out domain.CreateOrganizationResponse + domain.Map(organization, &out) - //time.Sleep(time.Second * 5) // for test ResponseJSON(w, http.StatusOK, out) } @@ -80,11 +76,10 @@ func (h *OrganizationHandler) CreateOrganization(w http.ResponseWriter, r *http. // @Description Get organization list // @Accept json // @Produce json -// @Success 200 {object} []domain.Organization +// @Success 200 {object} []domain.ListOrganizationBody // @Router /organizations [get] // @Security JWT func (h *OrganizationHandler) GetOrganizations(w http.ResponseWriter, r *http.Request) { - log.Info("GetOrganization") organizations, err := h.usecase.Fetch() if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) @@ -93,12 +88,11 @@ func (h *OrganizationHandler) GetOrganizations(w http.ResponseWriter, r *http.Re return } - var out struct { - Organizations []domain.Organization `json:"organizations"` - } + var out domain.ListOrganizationResponse + out.Organizations = make([]domain.ListOrganizationBody, len(*organizations)) - for _, organization := range *organizations { - out.Organizations = append(out.Organizations, organization) + for i, organization := range *organizations { + domain.Map(organization, &out.Organizations[i]) } ResponseJSON(w, http.StatusOK, out) @@ -111,7 +105,7 @@ func (h *OrganizationHandler) GetOrganizations(w http.ResponseWriter, r *http.Re // @Accept json // @Produce json // @Param organizationId path string true "organizationId" -// @Success 200 {object} domain.Organization +// @Success 200 {object} domain.GetOrganizationResponse // @Router /organizations/{organizationId} [get] // @Security JWT func (h *OrganizationHandler) GetOrganization(w http.ResponseWriter, r *http.Request) { @@ -130,11 +124,8 @@ func (h *OrganizationHandler) GetOrganization(w http.ResponseWriter, r *http.Req return } - var out struct { - Organization domain.Organization `json:"organization"` - } - - out.Organization = organization + var out domain.GetOrganizationResponse + domain.Map(organization, &out.Organization) ResponseJSON(w, http.StatusOK, out) } @@ -190,7 +181,8 @@ func (h *OrganizationHandler) DeleteOrganization(w http.ResponseWriter, r *http. // @Accept json // @Produce json // @Param organizationId path string true "organizationId" -// @Success 200 {object} domain.Organization +// @Param body body domain.UpdateOrganizationRequest true "update organization request" +// @Success 200 {object} domain.UpdateOrganizationResponse // @Router /organizations/{organizationId} [put] // @Security JWT func (h *OrganizationHandler) UpdateOrganization(w http.ResponseWriter, r *http.Request) { @@ -208,7 +200,7 @@ func (h *OrganizationHandler) UpdateOrganization(w http.ResponseWriter, r *http. return } - err = h.usecase.Update(organizationId, input) + organization, err := h.usecase.Update(organizationId, input) if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) @@ -216,9 +208,10 @@ func (h *OrganizationHandler) UpdateOrganization(w http.ResponseWriter, r *http. return } - log.Info(input) + var out domain.UpdateOrganizationResponse + domain.Map(organization, &out) - ResponseJSON(w, http.StatusOK, nil) + ResponseJSON(w, http.StatusOK, out) } // UpdatePrimaryCluster godoc @@ -253,7 +246,5 @@ func (h *OrganizationHandler) UpdatePrimaryCluster(w http.ResponseWriter, r *htt return } - log.Info(input) - ResponseJSON(w, http.StatusOK, nil) } diff --git a/internal/delivery/http/user.go b/internal/delivery/http/user.go index 3587bfba..9b0850ed 100644 --- a/internal/delivery/http/user.go +++ b/internal/delivery/http/user.go @@ -26,6 +26,16 @@ type UserHandler struct { usecase usecase.IUserUsecase } +// Create godoc +// @Tags Users +// @Summary Create user +// @Description Create user +// @Accept json +// @Produce json +// @Param body body domain.CreateUserRequest true "create user request" +// @Success 200 {object} domain.CreateUserResponse "create user response" +// @Router /users [post] +// @Security JWT func (u UserHandler) Create(w http.ResponseWriter, r *http.Request) { // TODO implement validation input := domain.CreateUserRequest{} @@ -44,28 +54,40 @@ func (u UserHandler) Create(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - user := input.ToUser() + var user domain.User + domain.Map(input, &user) user.Organization = domain.Organization{ ID: userInfo.GetOrganizationId(), } - user, err = u.usecase.Create(ctx, user) + resUser, err := u.usecase.Create(ctx, &user) if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) + if _, status := httpErrors.ErrorResponse(err); status == http.StatusConflict { + ErrorJSON(w, httpErrors.NewConflictError(err)) + return + } ErrorJSON(w, err) return } - var out struct { - User domain.User - } - - out.User = *user + var out domain.CreateUserResponse + domain.Map(*resUser, &out.User) ResponseJSON(w, http.StatusCreated, out) } +// Get godoc +// @Tags Users +// @Summary Get user detail +// @Description Get user detail +// @Accept json +// @Produce json +// @Param userId path string true "userId" +// @Success 200 {object} domain.GetUserResponse +// @Router /users/{userId} [get] +// @Security JWT func (u UserHandler) Get(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userId, ok := vars["userId"] @@ -82,37 +104,53 @@ func (u UserHandler) Get(w http.ResponseWriter, r *http.Request) { return } - var out struct { - User domain.User - } - user.Password = "" - out.User = *user + var out domain.GetUserResponse + domain.Map(*user, &out.User) ResponseJSON(w, http.StatusOK, out) } +// List godoc +// @Tags Users +// @Summary Get user list +// @Description Get user list +// @Accept json +// @Produce json +// @Success 200 {object} []domain.ListUserBody +// @Router /users [get] +// @Security JWT func (u UserHandler) List(w http.ResponseWriter, r *http.Request) { users, err := u.usecase.List(r.Context()) if err != nil { - log.Errorf("error is :%s(%T)", err.Error(), err) + if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { + ResponseJSON(w, http.StatusNoContent, domain.ListUserResponse{}) + return + } + log.Errorf("error is :%s(%T)", err.Error(), err) ErrorJSON(w, err) - } - if users == nil { - users = &[]domain.User{} + return } - var out struct { - Users []domain.User - } - for _, user := range *users { - user.Password = "" - out.Users = append(out.Users, user) + var out domain.ListUserResponse + out.Users = make([]domain.ListUserBody, len(*users)) + for i, user := range *users { + domain.Map(user, &out.Users[i]) } ResponseJSON(w, http.StatusOK, out) } +// Delete godoc +// @Tags Users +// @Summary Delete user +// @Description Delete user +// @Accept json +// @Produce json +// @Param userId path string true "userId" +// @Success 200 {object} domain.User +// @Router /users/{userId} [delete] +// @Security JWT func (u UserHandler) Delete(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userId, ok := vars["userId"] @@ -123,6 +161,10 @@ func (u UserHandler) Delete(w http.ResponseWriter, r *http.Request) { err := u.usecase.DeleteByAccountId(r.Context(), userId) if err != nil { + if _, status := httpErrors.ErrorResponse(err); status == http.StatusNotFound { + ErrorJSON(w, httpErrors.NewNotFoundError(err)) + return + } log.Errorf("error is :%s(%T)", err.Error(), err) ErrorJSON(w, err) @@ -132,6 +174,17 @@ func (u UserHandler) Delete(w http.ResponseWriter, r *http.Request) { ResponseJSON(w, http.StatusOK, nil) } +// Update UpdateUser godoc +// @Tags Users +// @Summary Update user detail +// @Description Update user detail +// @Accept json +// @Produce json +// @Param userId path string true "userId" +// @Param body body domain.UpdateUserRequest true "update user request" +// @Success 200 {object} domain.UpdateUserResponse +// @Router /users/{userId} [put] +// @Security JWT func (u UserHandler) Update(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userId, ok := vars["userId"] @@ -156,12 +209,14 @@ func (u UserHandler) Update(w http.ResponseWriter, r *http.Request) { } ctx := r.Context() - user := input.ToUser() + var user domain.User + domain.Map(input, &user) user.Organization = domain.Organization{ ID: userInfo.GetOrganizationId(), } + user.AccountId = userId - user, err = u.usecase.UpdateByAccountId(ctx, userId, user) + resUser, err := u.usecase.UpdateByAccountId(ctx, userId, &user) if err != nil { log.Errorf("error is :%s(%T)", err.Error(), err) @@ -169,15 +224,23 @@ func (u UserHandler) Update(w http.ResponseWriter, r *http.Request) { return } - var out struct { - User domain.User - } - - out.User = *user + var out domain.UpdateUserResponse + domain.Map(*resUser, &out.User) ResponseJSON(w, http.StatusOK, out) } +// UpdatePassword godoc +// @Tags Users +// @Summary Update user password detail +// @Description Update user password detail +// @Accept json +// @Produce json +// @Param userId path string true "userId" +// @Param body body domain.UpdatePasswordRequest true "update user password request" +// @Success 200 {object} domain.UpdatePasswordResponse +// @Router /users/{userId}/password [put] +// @Security JWT func (u UserHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userId, ok := vars["userId"] @@ -206,6 +269,17 @@ func (u UserHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) { ResponseJSON(w, http.StatusOK, nil) } +// CheckId godoc +// @Tags Users +// @Summary Update user password detail +// @Description Update user password detail +// @Accept json +// @Produce json +// @Param userId path string true "userId" +// @Success 204 +// @Failure 409 +// @Router /users/{userId} [post] +// @Security JWT func (u UserHandler) CheckId(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) userId, ok := vars["userId"] @@ -214,19 +288,17 @@ func (u UserHandler) CheckId(w http.ResponseWriter, r *http.Request) { return } - user, err := u.usecase.GetByAccountId(r.Context(), userId) + _, err := u.usecase.GetByAccountId(r.Context(), userId) if err != nil { - log.Errorf("error is :%s(%T)", err.Error(), err) - + if _, code := httpErrors.ErrorResponse(err); code == http.StatusNotFound { + ResponseJSON(w, http.StatusNoContent, nil) + return + } ErrorJSON(w, err) return } - if user != nil { - ErrorJSON(w, httpErrors.NewConflictError(fmt.Errorf("user already exists"))) - } - - ResponseJSON(w, http.StatusNotFound, nil) + ResponseJSON(w, http.StatusConflict, nil) } func NewUserHandler(h usecase.IUserUsecase) IUserHandler { diff --git a/internal/keycloak/keycloak.go b/internal/keycloak/keycloak.go index b5d93c9d..e3eeeba0 100644 --- a/internal/keycloak/keycloak.go +++ b/internal/keycloak/keycloak.go @@ -64,44 +64,38 @@ func (c *Keycloak) InitializeKeycloak() error { if os.Getenv("LOG_LEVEL") == "DEBUG" { restyClient.SetDebug(true) } - //restyClient.SetDebug(true) restyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - log.Info("loginAdmin") token, err := c.loginAdmin(ctx) if err != nil { log.Fatal(err) return err } - log.Info("Add Group") group, err := c.ensureGroupByName(ctx, token, DefaultMasterRealm, "tks-admin@master") if err != nil { log.Fatal(err) return err } - log.Info("Add user") user, err := c.ensureUserByName(ctx, token, DefaultMasterRealm, c.config.AdminId, c.config.AdminPassword) if err != nil { log.Fatal(err) return err } - log.Info("Add user to group") if err := c.addUserToGroup(ctx, token, DefaultMasterRealm, *user.ID, *group.ID); err != nil { log.Fatal(err) return err } - log.Info("ensureClient") + keycloakClient, err := c.ensureClient(ctx, token, DefaultMasterRealm, DefaultClientID, DefaultClientSecret) if err != nil { log.Fatal(err) return err } - log.Info("ensureClientProtocolMappers") for _, defaultMapper := range defaultProtocolTksMapper { if err := c.ensureClientProtocolMappers(ctx, token, DefaultMasterRealm, *keycloakClient.ClientID, "openid", defaultMapper); err != nil { log.Fatal(err) @@ -109,7 +103,6 @@ func (c *Keycloak) InitializeKeycloak() error { } } - log.Info("LoginClient") if _, err := c.client.Login(ctx, DefaultClientID, DefaultClientSecret, DefaultMasterRealm, c.config.AdminId, c.config.AdminPassword); err != nil { log.Fatal(err) @@ -130,7 +123,6 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai } realmUUID, err := k.client.CreateRealm(ctx, accessToken, realmConfig) if err != nil { - log.Info("CreateRealm", "err", err) return realmUUID, err } // After Create Realm, accesstoken got changed so that old token doesn't work properly. @@ -140,8 +132,6 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai } accessToken = token.AccessToken - log.Info("CreateRealm", "realmUUID", realmUUID) - log.Info("CreateRealm", "organizationName", organizationName) time.Sleep(time.Second * 3) clientUUID, err := k.createDefaultClient(context.Background(), accessToken, organizationName, DefaultClientID, DefaultClientSecret) if err != nil { @@ -171,11 +161,9 @@ func (k *Keycloak) CreateRealm(organizationName string, organizationConfig domai } } adminGroupUuid, err := k.createGroup(ctx, accessToken, organizationName, "admin@"+organizationName) - log.Info("adminGroupUuid", adminGroupUuid) - g, _ := k.client.GetGroups(ctx, accessToken, organizationName, gocloak.GetGroupsParams{ - Search: gocloak.StringP("admin@" + organizationName), - }) - log.Info("GetGroups g[0].ID", g[0].ID) + if err != nil { + return realmUUID, err + } token, err = k.loginAdmin(ctx) if err != nil { diff --git a/internal/repository/mapper.go b/internal/repository/mapper.go new file mode 100644 index 00000000..128597d3 --- /dev/null +++ b/internal/repository/mapper.go @@ -0,0 +1,72 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/openinfradev/tks-api/pkg/domain" + "reflect" +) + +type ConverterMap map[compositeKey]func(interface{}) (interface{}, error) + +type compositeKey struct { + srcType reflect.Type + dstType reflect.Type +} + +func recursiveMap(src interface{}, dst interface{}, converterMap ConverterMap) error { + srcVal := reflect.ValueOf(src) + srcType := srcVal.Type() + + dstVal := reflect.ValueOf(dst) + if dstVal.Kind() != reflect.Ptr || dstVal.IsNil() { + return fmt.Errorf("dst must be a non-nil pointer") + } + dstElem := dstVal.Elem() + + for i := 0; i < srcVal.NumField(); i++ { + fieldName := srcType.Field(i).Name + srcField := srcVal.Field(i) + dstField := dstElem.FieldByName(fieldName) + + if dstField.IsValid() && dstField.CanSet() { + if dstField.Type() == srcField.Type() { + dstField.Set(srcField) + continue + } else if srcField.Type().Kind() == reflect.Struct && dstField.Type().Kind() == reflect.Struct { + if err := recursiveMap(srcField.Interface(), dstField.Addr().Interface(), converterMap); err != nil { + return err + } + } else { + if converter, ok := converterMap[compositeKey{srcType: srcField.Type(), dstType: dstField.Type()}]; ok { + if converted, err := converter(srcField.Interface()); err != nil { + return err + } else { + dstField.Set(reflect.ValueOf(converted)) + } + } else { + return fmt.Errorf("no converter found for %s -> %s", srcField.Type(), dstField.Type()) + } + } + } + } + + return nil +} +func Map(src interface{}, dst interface{}) error { + return recursiveMap(src, dst, ConverterMap{ + {srcType: reflect.TypeOf((*uuid.UUID)(nil)), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { + return i.(uuid.UUID).String(), nil + }, + {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*uuid.UUID)(nil))}: func(i interface{}) (interface{}, error) { + val, _ := uuid.Parse(i.(string)) + return val, nil + }, + {srcType: reflect.TypeOf((*domain.OrganizationStatus)(nil)), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { + return string(i.(domain.OrganizationStatus)), nil + }, + {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*domain.OrganizationStatus)(nil))}: func(i interface{}) (interface{}, error) { + return i.(domain.OrganizationStatus).String(), nil + }, + }) +} diff --git a/internal/repository/mapper_test.go b/internal/repository/mapper_test.go new file mode 100644 index 00000000..bf040134 --- /dev/null +++ b/internal/repository/mapper_test.go @@ -0,0 +1,79 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/openinfradev/tks-api/pkg/domain" + "testing" + "time" +) + +// test case +func TestConvert(t *testing.T) { + genUuid, _ := uuid.NewRandom() + uuidStr := genUuid.String() + + type args struct { + src interface{} + dst interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "test case 1", + args: args{ + src: User{ + ID: genUuid, + AccountId: "testAccount", + Name: "testName", + Password: "testPassword", + RoleId: uuid.UUID{}, + Role: Role{}, + OrganizationId: "testOrganizationId", + Organization: Organization{}, + Creator: uuid.UUID{}, + Email: "testEmail", + Department: "testDepartment", + Description: "testDescription", + }, + dst: &domain.User{}, + }, + wantErr: false, + }, + { + name: "test case 2", + args: args{ + src: domain.User{ + ID: uuidStr, + AccountId: "testAccount", + Password: "testPassword", + Name: "testName", + Token: "testToken", + Role: domain.Role{}, + Organization: domain.Organization{}, + Creator: "testCreator", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + Email: "test email", + Department: "testDepartment", + Description: "testDescription", + }, + dst: &User{}, + }, + wantErr: false, + }, + } + _ = tests + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Map(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr { + t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr) + } else { + fmt.Println(tt.args.dst) + } + }) + } +} diff --git a/internal/repository/organization.go b/internal/repository/organization.go index 1f4a3e05..3c53462f 100644 --- a/internal/repository/organization.go +++ b/internal/repository/organization.go @@ -61,7 +61,7 @@ func (r *OrganizationRepository) Create(organizationId string, name string, crea } res := r.db.Create(&organization) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.Organization{}, res.Error } @@ -74,7 +74,7 @@ func (r *OrganizationRepository) Fetch() (*[]domain.Organization, error) { res := r.db.Find(&organizations) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return nil, res.Error } for _, organization := range organizations { @@ -88,7 +88,7 @@ func (r *OrganizationRepository) Get(id string) (domain.Organization, error) { var organization Organization res := r.db.First(&organization, "id = ?", id) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.Organization{}, res.Error } @@ -105,12 +105,12 @@ func (r *OrganizationRepository) Update(organizationId string, in domain.UpdateO PrimaryClusterId: in.PrimaryClusterId, }) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.Organization{}, res.Error } res = r.db.Model(&Organization{}).Where("id = ?", organizationId).Find(&organization) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.Organization{}, res.Error } @@ -120,7 +120,7 @@ func (r *OrganizationRepository) Update(organizationId string, in domain.UpdateO func (r *OrganizationRepository) Delete(organizationId string) error { res := r.db.Delete(&Organization{}, "id = ?", organizationId) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } @@ -132,7 +132,7 @@ func (r *OrganizationRepository) InitWorkflow(organizationId string, workflowId Where("ID = ?", organizationId). Updates(map[string]interface{}{"Status": status, "WorkflowId": workflowId}) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } return nil @@ -145,7 +145,7 @@ func (r *OrganizationRepository) reflect(organization Organization) domain.Organ Description: organization.Description, Phone: organization.Phone, PrimaryClusterId: organization.PrimaryClusterId, - Status: organization.Status.String(), + Status: organization.Status, Creator: organization.Creator.String(), CreatedAt: organization.CreatedAt, UpdatedAt: organization.UpdatedAt, diff --git a/internal/repository/user.go b/internal/repository/user.go index eb036a03..da8e0902 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -36,7 +36,7 @@ type UserRepository struct { func (r *UserRepository) Flush(organizationId string) error { res := r.db.Where("organization_id = ?", organizationId).Delete(&User{}) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } return nil @@ -81,7 +81,7 @@ func (r *UserRepository) Create(accountId string, organizationId string, passwor } res := r.db.Create(&newUser) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.User{}, res.Error } @@ -106,8 +106,12 @@ func (r *UserRepository) CreateWithUuid(uuid uuid.UUID, accountId string, name s log.Error(res.Error.Error()) return domain.User{}, res.Error } + user, err := r.getUserByAccountId(accountId, organizationId) + if err != nil { + return domain.User{}, err + } - return r.reflect(newUser), nil + return r.reflect(user), nil } func (r *UserRepository) AccountIdFilter(accountId string) FilterFunc { return func(user *gorm.DB) *gorm.DB { @@ -138,7 +142,7 @@ func (r *UserRepository) List(filters ...FilterFunc) (*[]domain.User, error) { res = cFunc(r.db.Model(&User{}).Preload("Organization").Preload("Role")).Find(&users) } if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return nil, res.Error } if res.RowsAffected == 0 { @@ -165,7 +169,7 @@ func (r *UserRepository) GetByUuid(userId uuid.UUID) (respUser domain.User, err res := r.db.Model(&User{}).Preload("Organization").Preload("Role").Find(&user, "id = ?", userId) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.User{}, res.Error } if res.RowsAffected == 0 { @@ -184,14 +188,14 @@ func (r *UserRepository) UpdateWithUuid(uuid uuid.UUID, accountId string, name s Department: department, Description: description, }) - //if res.RowsAffected == 0 || res.Error != nil { - // return domain.User{}, fmt.Errorf("Not found user. %s", res.Error) - //} + if res.RowsAffected == 0 || res.Error != nil { + return domain.User{}, httpErrors.NewNotFoundError(httpErrors.NotFound) + } if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return domain.User{}, res.Error } - res = r.db.Model(&User{}).Where("id = ?", uuid).Find(&user) + res = r.db.Model(&User{}).Preload("Organization").Preload("Role").Where("id = ?", uuid).Find(&user) if res.Error != nil { return domain.User{}, res.Error } @@ -200,7 +204,7 @@ func (r *UserRepository) UpdateWithUuid(uuid uuid.UUID, accountId string, name s func (r *UserRepository) DeleteWithUuid(uuid uuid.UUID) error { res := r.db.Unscoped().Delete(&User{}, "id = ?", uuid) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } return nil @@ -263,7 +267,7 @@ func (r *UserRepository) AssignRoleWithUuid(uuid uuid.UUID, roleName string) err } res := r.db.Create(&newRole) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } @@ -287,7 +291,7 @@ func (r *UserRepository) AssignRole(accountId string, organizationId string, rol } res := r.db.Create(&newRole) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return res.Error } @@ -311,7 +315,7 @@ func (r *UserRepository) FetchRoles() (*[]domain.Role, error) { res := r.db.Find(&roles) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return nil, res.Error } @@ -334,7 +338,7 @@ func (r *UserRepository) getUserByAccountId(accountId string, organizationId str res := r.db.Model(&User{}).Preload("Organization").Preload("Role"). Find(&user, "account_id = ? AND organization_id = ?", accountId, organizationId) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return User{}, res.Error } if res.RowsAffected == 0 { @@ -348,7 +352,7 @@ func (r *UserRepository) getRoleByName(roleName string) (Role, error) { role := Role{} res := r.db.First(&role, "name = ?", roleName) if res.Error != nil { - log.Error("error is :%s(%T)", res.Error.Error(), res.Error) + log.Errorf("error is :%s(%T)", res.Error.Error(), res.Error) return Role{}, res.Error } if res.RowsAffected == 0 { @@ -384,12 +388,15 @@ func (r *UserRepository) reflect(user User) domain.User { //} organization := domain.Organization{ - ID: user.Organization.ID, - Name: user.Organization.Name, - Description: user.Organization.Description, - Creator: user.Organization.Creator.String(), - CreatedAt: user.Organization.CreatedAt, - UpdatedAt: user.Organization.UpdatedAt, + ID: user.Organization.ID, + Name: user.Organization.Name, + Description: user.Organization.Description, + Phone: user.Organization.Phone, + Status: user.Organization.Status, + StatusDescription: user.Organization.StatusDesc, + Creator: user.Organization.Creator.String(), + CreatedAt: user.Organization.CreatedAt, + UpdatedAt: user.Organization.UpdatedAt, } //for _, organization := range user.Organizations { // outOrganization := domain.Organization{ @@ -413,6 +420,9 @@ func (r *UserRepository) reflect(user User) domain.User { Creator: user.Creator.String(), CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, + Email: user.Email, + Department: user.Department, + Description: user.Description, } } diff --git a/internal/route/route.go b/internal/route/route.go index e8faffc1..a1c0edd0 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -231,14 +231,6 @@ func authMiddleware(next http.Handler, kc keycloak.IKeycloak) http.Handler { case "keycloak": default: - - // [TODO] implementaion keycloak process - //vars := mux.Vars(r) - //organization, ok := vars["organizationId"] - //if !ok { - // organization = "master" - //} - auth := strings.TrimSpace(r.Header.Get("Authorization")) if auth == "" { w.WriteHeader(http.StatusUnauthorized) diff --git a/internal/usecase/auth.go b/internal/usecase/auth.go index a1b54c23..3f8d8c6e 100644 --- a/internal/usecase/auth.go +++ b/internal/usecase/auth.go @@ -7,7 +7,6 @@ import ( "github.com/openinfradev/tks-api/internal/repository" "github.com/openinfradev/tks-api/pkg/domain" "github.com/openinfradev/tks-api/pkg/httpErrors" - "github.com/openinfradev/tks-api/pkg/log" ) type IAuthUsecase interface { @@ -35,8 +34,6 @@ func (r *AuthUsecase) Login(accountId string, password string, organizationId st return domain.User{}, err } if !helper.CheckPasswordHash(user.Password, password) { - log.Debug(user.Password) - log.Debug(password) return domain.User{}, httpErrors.NewUnauthorizedError(fmt.Errorf("password is not correct")) } @@ -50,15 +47,6 @@ func (r *AuthUsecase) Login(accountId string, password string, organizationId st // Insert token user.Token = accountToken.Token - // Remove password in user - user.Password = "" - - // Replaced with Keycloak - //user.Token, err = helper.CreateJWT(accountId, user.ID, organizationId) - //if err != nil { - // return domain.User{}, fmt.Errorf("failed to create token") - //} - return user, nil } diff --git a/internal/usecase/organization.go b/internal/usecase/organization.go index 29fc8fa8..b56d27da 100644 --- a/internal/usecase/organization.go +++ b/internal/usecase/organization.go @@ -3,7 +3,6 @@ package usecase import ( "context" "fmt" - "github.com/openinfradev/tks-api/internal/auth/request" "github.com/openinfradev/tks-api/internal/helper" "github.com/openinfradev/tks-api/internal/keycloak" @@ -21,7 +20,7 @@ type IOrganizationUsecase interface { Create(context.Context, *domain.Organization) (organizationId string, err error) Fetch() (*[]domain.Organization, error) Get(organizationId string) (domain.Organization, error) - Update(organizationId string, in domain.UpdateOrganizationRequest) (err error) + Update(organizationId string, in domain.UpdateOrganizationRequest) (domain.Organization, error) UpdatePrimaryClusterId(organizationId string, clusterId string) (err error) Delete(organizationId string, accessToken string) error } @@ -62,8 +61,6 @@ func (u *OrganizationUsecase) Create(ctx context.Context, in *domain.Organizatio if err != nil { return "", err } - log.Info("newly created Organization ID:", organizationId) - workflowId, err := u.argo.SumbitWorkflowFromWftpl( "tks-create-contract-repo", argowf.SubmitOptions{ @@ -119,17 +116,18 @@ func (u *OrganizationUsecase) Delete(organizationId string, accessToken string) return nil } -func (u *OrganizationUsecase) Update(organizationId string, in domain.UpdateOrganizationRequest) (err error) { - _, err = u.Get(organizationId) +func (u *OrganizationUsecase) Update(organizationId string, in domain.UpdateOrganizationRequest) (domain.Organization, error) { + _, err := u.Get(organizationId) if err != nil { - return httpErrors.NewNotFoundError(err) + return domain.Organization{}, httpErrors.NewNotFoundError(err) } - _, err = u.repo.Update(organizationId, in) + res, err := u.repo.Update(organizationId, in) if err != nil { - return err + return domain.Organization{}, err } - return nil + + return res, nil } func (u *OrganizationUsecase) UpdatePrimaryClusterId(organizationId string, clusterId string) (err error) { diff --git a/internal/usecase/user.go b/internal/usecase/user.go index 31531b84..a3f3de28 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -10,7 +10,10 @@ import ( "github.com/openinfradev/tks-api/internal/keycloak" "github.com/openinfradev/tks-api/internal/repository" "github.com/openinfradev/tks-api/pkg/domain" + "github.com/openinfradev/tks-api/pkg/httpErrors" + "github.com/openinfradev/tks-api/pkg/log" "github.com/pkg/errors" + "net/http" ) type IUserUsecase interface { @@ -32,26 +35,8 @@ type UserUsecase struct { func (u *UserUsecase) DeleteAll(ctx context.Context, organizationId string) error { // TODO: implement me as transaction - //users, err := u.repo.List(u.repo.OrganizationFilter(organizationId)) - //if err != nil { - // return err - //} - //token, ok := request.TokenFrom(ctx) - //if ok == false { - // return httpErrors.NewInternalServerError(fmt.Errorf("token in the context is empty")) - //} - //for _, user := range *users { - // // Delete user in keycloak - // - // err = u.kc.DeleteUser(organizationId, user.AccountId, token) - // if err != nil { - // if _, statusCode := httpErrors.ErrorResponse(err); statusCode == http.StatusNotFound { - // continue - // } - // return err - // } - //} - // + // TODO: clean users in keycloak + err := u.repo.Flush(organizationId) if err != nil { return err @@ -156,11 +141,6 @@ func (u *UserUsecase) CreateAdmin(orgainzationId string) (*domain.User, error) { return nil, err } - //err = u.repo.AssignRole(user.AccountId, user.Organization.ID, user.Role.Name) - //if err != nil { - // return nil, err - //} - return &resUser, nil } @@ -226,7 +206,7 @@ func (u *UserUsecase) List(ctx context.Context) (*[]domain.User, error) { users, err := u.repo.List(u.repo.OrganizationFilter(userInfo.GetOrganizationId())) if err != nil { - return nil, errors.Wrap(err, "getting users from repository failed") + return nil, err } return users, nil @@ -252,10 +232,17 @@ func (u *UserUsecase) UpdateByAccountId(ctx context.Context, accountId string, u if ok == false { return nil, fmt.Errorf("user in the context is empty") } + token, ok := request.TokenFrom(ctx) + if ok == false { + return nil, fmt.Errorf("token in the context is empty") + } users, err := u.repo.List(u.repo.OrganizationFilter(userInfo.GetOrganizationId()), u.repo.AccountIdFilter(accountId)) if err != nil { + if _, code := httpErrors.ErrorResponse(err); code == http.StatusNotFound { + return nil, httpErrors.NewNotFoundError(httpErrors.NotFound) + } return nil, errors.Wrap(err, "getting users from repository failed") } if len(*users) == 0 { @@ -264,14 +251,25 @@ func (u *UserUsecase) UpdateByAccountId(ctx context.Context, accountId string, u return nil, fmt.Errorf("multiple users found") } - uuid, err := uuid.Parse((*users)[0].ID) + if user.Role.Name != (*users)[0].Role.Name { + groups := []string{fmt.Sprintf("%s@%s", user.Role.Name, userInfo.GetOrganizationId())} + if err := u.kc.UpdateUser(userInfo.GetOrganizationId(), &gocloak.User{ + ID: &(*users)[0].ID, + Groups: &groups, + }, token); err != nil { + log.Errorf("updating user in keycloak failed: %v", err) + return nil, httpErrors.NewInternalServerError(err) + } + } + + userUuid, err := uuid.Parse((*users)[0].ID) if err != nil { return nil, err } originPassword := (*users)[0].Password - *user, err = u.repo.UpdateWithUuid(uuid, user.AccountId, user.Name, originPassword, user.Email, + *user, err = u.repo.UpdateWithUuid(userUuid, user.AccountId, user.Name, originPassword, user.Email, user.Department, user.Description) if err != nil { return nil, errors.Wrap(err, "updating user in repository failed") @@ -288,7 +286,7 @@ func (u *UserUsecase) DeleteByAccountId(ctx context.Context, accountId string) e user, err := u.repo.Get(accountId, userInfo.GetOrganizationId()) if err != nil { - return errors.Wrap(err, "getting users from repository failed") + return err } uuid, err := uuid.Parse(user.ID) @@ -297,7 +295,7 @@ func (u *UserUsecase) DeleteByAccountId(ctx context.Context, accountId string) e } err = u.repo.DeleteWithUuid(uuid) if err != nil { - return errors.Wrap(err, "deleting user in repository failed") + return err } // Delete user in keycloak @@ -307,7 +305,7 @@ func (u *UserUsecase) DeleteByAccountId(ctx context.Context, accountId string) e } err = u.kc.DeleteUser(userInfo.GetOrganizationId(), accountId, token) if err != nil { - return errors.Wrap(err, "deleting user in keycloak failed") + return err } return nil @@ -335,6 +333,10 @@ func (u *UserUsecase) Create(ctx context.Context, user *domain.User) (*domain.Us Groups: &groups, }, token) if err != nil { + if _, err := u.kc.GetUser(user.Organization.ID, user.AccountId, token); err == nil { + return nil, httpErrors.NewConflictError(errors.New("user already exists")) + } + return nil, errors.Wrap(err, "creating user in keycloak failed") } keycloakUser, err := u.kc.GetUser(user.Organization.ID, user.AccountId, token) diff --git a/pkg/domain/mapper.go b/pkg/domain/mapper.go index 6e5ad776..44945ac6 100644 --- a/pkg/domain/mapper.go +++ b/pkg/domain/mapper.go @@ -2,11 +2,18 @@ package domain import ( "fmt" + "github.com/google/uuid" "reflect" ) -func Map(src interface{}, dst interface{}) error { +type ConverterMap map[compositeKey]func(interface{}) (interface{}, error) + +type compositeKey struct { + srcType reflect.Type + dstType reflect.Type +} +func recursiveMap(src interface{}, dst interface{}, converterMap ConverterMap) error { srcVal := reflect.ValueOf(src) srcType := srcVal.Type() @@ -18,12 +25,54 @@ func Map(src interface{}, dst interface{}) error { for i := 0; i < srcVal.NumField(); i++ { fieldName := srcType.Field(i).Name + srcField := srcVal.Field(i) dstField := dstElem.FieldByName(fieldName) + if dstField.IsValid() && dstField.CanSet() { - dstField.Set(srcVal.Field(i)) + if dstField.Type() == srcField.Type() { + dstField.Set(srcField) + continue + } else if srcField.Type().Kind() == reflect.Struct && dstField.Type().Kind() == reflect.Struct { + if err := recursiveMap(srcField.Interface(), dstField.Addr().Interface(), converterMap); err != nil { + return err + } + } else { + converterKey := compositeKey{srcType: srcField.Type(), dstType: dstField.Type()} + if converter, ok := converterMap[converterKey]; ok { + if converted, err := converter(srcField.Interface()); err != nil { + return err + } else { + dstField.Set(reflect.ValueOf(converted)) + } + } else { + return fmt.Errorf("no converter found for %s -> %s", srcField.Type(), dstField.Type()) + } + } } } return nil - +} +func Map(src interface{}, dst interface{}) error { + return recursiveMap(src, dst, ConverterMap{ + {srcType: reflect.TypeOf((*uuid.UUID)(nil)).Elem(), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { + return i.(uuid.UUID).String(), nil + }, + {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*uuid.UUID)(nil)).Elem()}: func(i interface{}) (interface{}, error) { + val, _ := uuid.Parse(i.(string)) + return val, nil + }, + {srcType: reflect.TypeOf((*OrganizationStatus)(nil)).Elem(), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { + return string(i.(OrganizationStatus)), nil + }, + {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*OrganizationStatus)(nil)).Elem()}: func(i interface{}) (interface{}, error) { + return i.(OrganizationStatus).String(), nil + }, + {srcType: reflect.TypeOf((*Role)(nil)).Elem(), dstType: reflect.TypeOf("")}: func(i interface{}) (interface{}, error) { + return i.(Role).Name, nil + }, + {srcType: reflect.TypeOf(""), dstType: reflect.TypeOf((*Role)(nil)).Elem()}: func(i interface{}) (interface{}, error) { + return Role{Name: i.(string)}, nil + }, + }) } diff --git a/pkg/domain/mapper_test.go b/pkg/domain/mapper_test.go new file mode 100644 index 00000000..5b476e0b --- /dev/null +++ b/pkg/domain/mapper_test.go @@ -0,0 +1,97 @@ +package domain + +import ( + "fmt" + "testing" + "time" +) + +// test case +func TestConvert(t *testing.T) { + type args struct { + src interface{} + dst interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "test case 1", + args: args{ + src: CreateOrganizationRequest{ + Name: "test", + Description: "test", + Phone: "test", + }, + dst: &Organization{}, + }, + wantErr: false, + }, + { + name: "test case 2", + args: args{ + src: Organization{ + ID: "", + Name: "test", + Description: "test", + Phone: "test", + StatusDescription: "good", + Creator: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + dst: &CreateOrganizationResponse{}, + }, + wantErr: false, + }, + { + name: "test case 3", + args: args{ + src: CreateUserRequest{ + AccountId: "testAccount", + Password: "testPassword", + Name: "testName", + Email: "testEmail", + Department: "testDepartment", + Role: "testRole", + Description: "testDescription", + }, + dst: &User{}, + }, + wantErr: false, + }, + { + name: "test case 4", + args: args{ + src: User{ + ID: "", + AccountId: "testAccount", + Password: "testPassword", + Name: "testName", + Token: "testToken", + Role: Role{}, + Organization: Organization{}, + Creator: "", + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + Email: "", + Department: "", + Description: "", + }, + dst: &User{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Map(tt.args.src, tt.args.dst); (err != nil) != tt.wantErr { + t.Errorf("Map() error = %v, wantErr %v", err, tt.wantErr) + } else { + fmt.Println(tt.args.dst) + } + }) + } +} diff --git a/pkg/domain/organization.go b/pkg/domain/organization.go index ebb6b19a..23f8296d 100644 --- a/pkg/domain/organization.go +++ b/pkg/domain/organization.go @@ -40,57 +40,62 @@ func (m OrganizationStatus) FromString(s string) OrganizationStatus { } type Organization = struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Phone string `json:"phone"` - PrimaryClusterId string `json:"primaryClusterId"` - Status string `json:"status"` - StatusDescription string `json:"statusDescription"` - Creator string `json:"creator"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Phone string `json:"phone"` + PrimaryClusterId string `json:"primaryClusterId"` + Status OrganizationStatus `json:"status"` + StatusDescription string `json:"statusDescription"` + Creator string `json:"creator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type CreateOrganizationRequest struct { - Name string `json:"name"` - Description string `json:"description"` + Name string `json:"name" validate:"required,min=3,max=20"` + Description string `json:"description" validate:"omitempty,min=0,max=100"` Phone string `json:"phone"` } -func (r *CreateOrganizationRequest) ToOrganization() *Organization { - return &Organization{ - ID: "", - Name: r.Name, - Description: r.Description, - Phone: r.Phone, - Status: OrganizationStatus_CREATED.String(), - StatusDescription: "", - Creator: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } +type CreateOrganizationResponse struct { + ID string `json:"id"` +} + +type GetOrganizationResponse struct { + Organization struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Phone string `json:"phone"` + Status OrganizationStatus `json:"status"` + StatusDescription string `json:"statusDescription"` + Creator string `json:"creator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + } `json:"organization"` +} +type ListOrganizationResponse struct { + Organizations []ListOrganizationBody `json:"organizations"` +} +type ListOrganizationBody struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Phone string `json:"phone"` + Status OrganizationStatus `json:"status"` } type UpdateOrganizationRequest struct { - Description string `json:"description"` - Phone string `json:"phone"` PrimaryClusterId string `json:"primaryClusterId"` + Description string `json:"description" validate:"omitempty,min=0,max=100"` + Phone string `json:"phone"` } -func (r *UpdateOrganizationRequest) ToOrganization() *Organization { - return &Organization{ - ID: "", - Name: "", - Description: r.Description, - Phone: r.Phone, - PrimaryClusterId: r.PrimaryClusterId, - Status: "", - StatusDescription: "", - Creator: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - } +type UpdateOrganizationResponse struct { + ID string `json:"id"` + Description string `json:"description"` + Phone string `json:"phone"` } type UpdatePrimaryClusterRequest struct { diff --git a/pkg/domain/user.go b/pkg/domain/user.go index 73a31e8a..17fedd91 100644 --- a/pkg/domain/user.go +++ b/pkg/domain/user.go @@ -47,9 +47,14 @@ type Policy = struct { } type LoginRequest struct { - AccountId string `json:"accountId"` - Password string `json:"password"` - OrganizationId string `json:"organizationId"` + AccountId string `json:"accountId" validate:"required"` + Password string `json:"password" validate:"required"` + OrganizationId string `json:"organizationId" validate:"required"` +} + +type LoginResponse struct { + AccountId string `json:"accountId"` + Token string `json:"token"` } type LogoutRequest struct { @@ -66,69 +71,92 @@ type FindPasswordRequest struct { } type CreateUserRequest struct { - AccountId string `json:"accountId"` - Password string `json:"password"` - Name string `json:"name"` - Email string `json:"email"` - Department string `json:"department"` - Role string `json:"role"` - Description string `json:"description"` + AccountId string `json:"accountId" validate:"required"` + Password string `json:"password" validate:"required"` + Name string `json:"name" validate:"omitempty,min=0,max=20"` + Email string `json:"email" validate:"omitempty,email"` + Department string `json:"department" validate:"omitempty,min=0,max=20"` + Role string `json:"role" validate:"required,oneof=admin user"` + Description string `json:"description" validate:"omitempty,min=0,max=100"` } -func (r *CreateUserRequest) ToUser() *User { - return &User{ - ID: "", - AccountId: r.AccountId, - Password: r.Password, - Name: r.Name, - Token: "", - Role: Role{Name: r.Role}, - Organization: Organization{}, - Creator: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - Email: r.Email, - Department: r.Department, - Description: r.Description, - } +type CreateUserResponse struct { + User struct { + ID string `json:"id"` + AccountId string `json:"accountId"` + Name string `json:"name"` + Role Role `json:"role"` + Organization Organization `json:"organization"` + Email string `json:"email"` + Department string `json:"department"` + Description string `json:"description"` + } `json:"user"` +} + +type GetUserResponse struct { + User struct { + ID string `json:"id"` + AccountId string `json:"accountId"` + Name string `json:"name"` + Role Role `json:"role"` + Organization Organization `json:"organization"` + Email string `json:"email"` + Department string `json:"department"` + Description string `json:"description"` + Creator string `json:"creator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + } `json:"user"` +} + +type ListUserResponse struct { + Users []ListUserBody `json:"users"` +} +type ListUserBody struct { + ID string `json:"id"` + AccountId string `json:"accountId"` + Name string `json:"name"` + Role Role `json:"role"` + Organization Organization `json:"organization"` + Email string `json:"email"` + Department string `json:"department"` + Description string `json:"description"` + Creator string `json:"creator"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type UpdateUserRequest struct { - Name string `json:"name"` - Email string `json:"email"` - Department string `json:"department"` - Role string `json:"role"` - Description string `json:"description"` + Name string `json:"name" validate:"omitempty,min=0,max=20"` + Role string `json:"role" validate:"oneof=admin user"` + Email string `json:"email" validate:"omitempty,email"` + Department string `json:"department" validate:"omitempty,min=0,max=20"` + Description string `json:"description" validate:"omitempty,min=0,max=100"` } -func (r *UpdateUserRequest) ToUser() *User { - return &User{ - ID: "", - AccountId: "", - Password: "", - Name: r.Name, - Token: "", - Role: Role{Name: r.Role}, - Organization: Organization{}, - Creator: "", - CreatedAt: time.Time{}, - UpdatedAt: time.Time{}, - Email: r.Email, - Department: r.Department, - Description: r.Description, - } +type UpdateUserResponse struct { + User struct { + ID string `json:"id"` + AccountId string `json:"accountId"` + Name string `json:"name"` + Role Role `json:"role"` + Organization Organization `json:"organization"` + Email string `json:"email"` + Department string `json:"department"` + Description string `json:"description"` + } `json:"user"` } type UpdatePasswordRequest struct { - Password string `json:"password"` + Password string `json:"password" validate:"required"` } -func (u *UpdatePasswordRequest) ToUser() *User { - return &User{ - Password: u.Password, - } +type UpdatePasswordResponse struct { } type CheckDuplicatedIdRequest struct { - AccountId string `json:"accountId"` + AccountId string `json:"accountId" validate:"required"` +} + +type CheckDuplicatedIdResponse struct { } From b569f4552b7a8364ba98630b2d80679ff85430f6 Mon Sep 17 00:00:00 2001 From: donggyu Date: Mon, 3 Apr 2023 21:49:11 +0900 Subject: [PATCH 4/4] =?UTF-8?q?minor=20fix:=20swagger=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/swagger/docs.go | 658 ++++++++++++++++++++++++++++++++++++++- api/swagger/swagger.json | 655 +++++++++++++++++++++++++++++++++++++- api/swagger/swagger.yaml | 434 +++++++++++++++++++++++++- 3 files changed, 1726 insertions(+), 21 deletions(-) diff --git a/api/swagger/docs.go b/api/swagger/docs.go index 6322f51f..96bed978 100644 --- a/api/swagger/docs.go +++ b/api/swagger/docs.go @@ -1,5 +1,4 @@ -// Package swagger GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT package swagger import "github.com/swaggo/swag" @@ -469,7 +468,7 @@ const docTemplate = `{ "200": { "description": "user detail", "schema": { - "$ref": "#/definitions/domain.User" + "$ref": "#/definitions/domain.LoginResponse" } } } @@ -874,7 +873,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.ListOrganizationBody" } } } @@ -949,7 +948,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.GetOrganizationResponse" } } } @@ -978,13 +977,22 @@ const docTemplate = `{ "name": "organizationId", "in": "path", "required": true + }, + { + "description": "update organization request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateOrganizationRequest" + } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.UpdateOrganizationResponse" } } } @@ -1067,6 +1075,271 @@ const docTemplate = `{ } } } + }, + "/users": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get user list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user list", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.ListUserBody" + } + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Create user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Create user", + "parameters": [ + { + "description": "create user request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "create user response", + "schema": { + "$ref": "#/definitions/domain.CreateUserResponse" + } + } + } + } + }, + "/users/{userId}": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get user detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetUserResponse" + } + } + } + }, + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "update user request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpdateUserResponse" + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user password detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user password detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "409": { + "description": "Conflict" + } + } + }, + "delete": { + "security": [ + { + "JWT": [] + } + ], + "description": "Delete user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Delete user", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.User" + } + } + } + } + }, + "/users/{userId}/password": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user password detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user password detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "update user password request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdatePasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpdatePasswordResponse" + } + } + } + } } }, "definitions": { @@ -1393,18 +1666,100 @@ const docTemplate = `{ }, "domain.CreateOrganizationRequest": { "type": "object", + "required": [ + "name" + ], "properties": { "description": { - "type": "string" + "type": "string", + "maxLength": 100, + "minLength": 0 }, "name": { - "type": "string" + "type": "string", + "maxLength": 20, + "minLength": 3 }, "phone": { "type": "string" } } }, + "domain.CreateUserRequest": { + "type": "object", + "required": [ + "accountId", + "password", + "role" + ], + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "email": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "password": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "admin", + "user" + ] + } + } + }, + "domain.CreateUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + } + } + } + } + }, "domain.GetCloudSettingsResponse": { "type": "object", "properties": { @@ -1416,6 +1771,86 @@ const docTemplate = `{ } } }, + "domain.GetOrganizationResponse": { + "type": "object", + "properties": { + "organization": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/domain.OrganizationStatus" + }, + "statusDescription": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + } + } + }, + "domain.GetUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" + } + } + } + } + }, "domain.History": { "type": "object", "properties": { @@ -1439,8 +1874,71 @@ const docTemplate = `{ } } }, + "domain.ListOrganizationBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/domain.OrganizationStatus" + } + } + }, + "domain.ListUserBody": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" + } + } + }, "domain.LoginRequest": { "type": "object", + "required": [ + "accountId", + "organizationId", + "password" + ], "properties": { "accountId": { "type": "string" @@ -1453,6 +1951,17 @@ const docTemplate = `{ } } }, + "domain.LoginResponse": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "domain.Organization": { "type": "object", "properties": { @@ -1478,7 +1987,7 @@ const docTemplate = `{ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/domain.OrganizationStatus" }, "statusDescription": { "type": "string" @@ -1488,6 +1997,29 @@ const docTemplate = `{ } } }, + "domain.OrganizationStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "x-enum-varnames": [ + "OrganizationStatus_PENDING", + "OrganizationStatus_CREATE", + "OrganizationStatus_CREATING", + "OrganizationStatus_CREATED", + "OrganizationStatus_DELETE", + "OrganizationStatus_DELETING", + "OrganizationStatus_DELETED", + "OrganizationStatus_ERROR" + ] + }, "domain.Role": { "type": "object", "properties": { @@ -1542,6 +2074,50 @@ const docTemplate = `{ } } }, + "domain.UpdateOrganizationRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "phone": { + "type": "string" + }, + "primaryClusterId": { + "type": "string" + } + } + }, + "domain.UpdateOrganizationResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "domain.UpdatePasswordRequest": { + "type": "object", + "required": [ + "password" + ], + "properties": { + "password": { + "type": "string" + } + } + }, + "domain.UpdatePasswordResponse": { + "type": "object" + }, "domain.UpdatePrimaryClusterRequest": { "type": "object", "properties": { @@ -1550,6 +2126,70 @@ const docTemplate = `{ } } }, + "domain.UpdateUserRequest": { + "type": "object", + "properties": { + "department": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "email": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "role": { + "type": "string", + "enum": [ + "admin", + "user" + ] + } + } + }, + "domain.UpdateUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + } + } + } + } + }, "domain.User": { "type": "object", "properties": { diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json index 9c873b3e..6fb205ef 100644 --- a/api/swagger/swagger.json +++ b/api/swagger/swagger.json @@ -462,7 +462,7 @@ "200": { "description": "user detail", "schema": { - "$ref": "#/definitions/domain.User" + "$ref": "#/definitions/domain.LoginResponse" } } } @@ -867,7 +867,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.ListOrganizationBody" } } } @@ -942,7 +942,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.GetOrganizationResponse" } } } @@ -971,13 +971,22 @@ "name": "organizationId", "in": "path", "required": true + }, + { + "description": "update organization request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateOrganizationRequest" + } } ], "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/domain.Organization" + "$ref": "#/definitions/domain.UpdateOrganizationResponse" } } } @@ -1060,6 +1069,271 @@ } } } + }, + "/users": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get user list", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user list", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.ListUserBody" + } + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Create user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Create user", + "parameters": [ + { + "description": "create user request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.CreateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "create user response", + "schema": { + "$ref": "#/definitions/domain.CreateUserResponse" + } + } + } + } + }, + "/users/{userId}": { + "get": { + "security": [ + { + "JWT": [] + } + ], + "description": "Get user detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get user detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.GetUserResponse" + } + } + } + }, + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "update user request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdateUserRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpdateUserResponse" + } + } + } + }, + "post": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user password detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user password detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "409": { + "description": "Conflict" + } + } + }, + "delete": { + "security": [ + { + "JWT": [] + } + ], + "description": "Delete user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Delete user", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.User" + } + } + } + } + }, + "/users/{userId}/password": { + "put": { + "security": [ + { + "JWT": [] + } + ], + "description": "Update user password detail", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update user password detail", + "parameters": [ + { + "type": "string", + "description": "userId", + "name": "userId", + "in": "path", + "required": true + }, + { + "description": "update user password request", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/domain.UpdatePasswordRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.UpdatePasswordResponse" + } + } + } + } } }, "definitions": { @@ -1386,18 +1660,100 @@ }, "domain.CreateOrganizationRequest": { "type": "object", + "required": [ + "name" + ], "properties": { "description": { - "type": "string" + "type": "string", + "maxLength": 100, + "minLength": 0 }, "name": { - "type": "string" + "type": "string", + "maxLength": 20, + "minLength": 3 }, "phone": { "type": "string" } } }, + "domain.CreateUserRequest": { + "type": "object", + "required": [ + "accountId", + "password", + "role" + ], + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "email": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "password": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "admin", + "user" + ] + } + } + }, + "domain.CreateUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + } + } + } + } + }, "domain.GetCloudSettingsResponse": { "type": "object", "properties": { @@ -1409,6 +1765,86 @@ } } }, + "domain.GetOrganizationResponse": { + "type": "object", + "properties": { + "organization": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/domain.OrganizationStatus" + }, + "statusDescription": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + } + } + }, + "domain.GetUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" + } + } + } + } + }, "domain.History": { "type": "object", "properties": { @@ -1432,8 +1868,71 @@ } } }, + "domain.ListOrganizationBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/domain.OrganizationStatus" + } + } + }, + "domain.ListUserBody": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "creator": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + }, + "updatedAt": { + "type": "string" + } + } + }, "domain.LoginRequest": { "type": "object", + "required": [ + "accountId", + "organizationId", + "password" + ], "properties": { "accountId": { "type": "string" @@ -1446,6 +1945,17 @@ } } }, + "domain.LoginResponse": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, "domain.Organization": { "type": "object", "properties": { @@ -1471,7 +1981,7 @@ "type": "string" }, "status": { - "type": "string" + "$ref": "#/definitions/domain.OrganizationStatus" }, "statusDescription": { "type": "string" @@ -1481,6 +1991,29 @@ } } }, + "domain.OrganizationStatus": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "x-enum-varnames": [ + "OrganizationStatus_PENDING", + "OrganizationStatus_CREATE", + "OrganizationStatus_CREATING", + "OrganizationStatus_CREATED", + "OrganizationStatus_DELETE", + "OrganizationStatus_DELETING", + "OrganizationStatus_DELETED", + "OrganizationStatus_ERROR" + ] + }, "domain.Role": { "type": "object", "properties": { @@ -1535,6 +2068,50 @@ } } }, + "domain.UpdateOrganizationRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "phone": { + "type": "string" + }, + "primaryClusterId": { + "type": "string" + } + } + }, + "domain.UpdateOrganizationResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "domain.UpdatePasswordRequest": { + "type": "object", + "required": [ + "password" + ], + "properties": { + "password": { + "type": "string" + } + } + }, + "domain.UpdatePasswordResponse": { + "type": "object" + }, "domain.UpdatePrimaryClusterRequest": { "type": "object", "properties": { @@ -1543,6 +2120,70 @@ } } }, + "domain.UpdateUserRequest": { + "type": "object", + "properties": { + "department": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "description": { + "type": "string", + "maxLength": 100, + "minLength": 0 + }, + "email": { + "type": "string" + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 0 + }, + "role": { + "type": "string", + "enum": [ + "admin", + "user" + ] + } + } + }, + "domain.UpdateUserResponse": { + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "accountId": { + "type": "string" + }, + "department": { + "type": "string" + }, + "description": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "$ref": "#/definitions/domain.Organization" + }, + "role": { + "$ref": "#/definitions/domain.Role" + } + } + } + } + }, "domain.User": { "type": "object", "properties": { diff --git a/api/swagger/swagger.yaml b/api/swagger/swagger.yaml index 5a66c899..c120c5f0 100644 --- a/api/swagger/swagger.yaml +++ b/api/swagger/swagger.yaml @@ -218,11 +218,69 @@ definitions: domain.CreateOrganizationRequest: properties: description: + maxLength: 100 + minLength: 0 type: string name: + maxLength: 20 + minLength: 3 type: string phone: type: string + required: + - name + type: object + domain.CreateUserRequest: + properties: + accountId: + type: string + department: + maxLength: 20 + minLength: 0 + type: string + description: + maxLength: 100 + minLength: 0 + type: string + email: + type: string + name: + maxLength: 20 + minLength: 0 + type: string + password: + type: string + role: + enum: + - admin + - user + type: string + required: + - accountId + - password + - role + type: object + domain.CreateUserResponse: + properties: + user: + properties: + accountId: + type: string + department: + type: string + description: + type: string + email: + type: string + id: + type: string + name: + type: string + organization: + $ref: '#/definitions/domain.Organization' + role: + $ref: '#/definitions/domain.Role' + type: object type: object domain.GetCloudSettingsResponse: properties: @@ -231,6 +289,58 @@ definitions: $ref: '#/definitions/domain.CloudSettingResponse' type: array type: object + domain.GetOrganizationResponse: + properties: + organization: + properties: + createdAt: + type: string + creator: + type: string + description: + type: string + id: + type: string + name: + type: string + phone: + type: string + status: + $ref: '#/definitions/domain.OrganizationStatus' + statusDescription: + type: string + updatedAt: + type: string + type: object + type: object + domain.GetUserResponse: + properties: + user: + properties: + accountId: + type: string + createdAt: + type: string + creator: + type: string + department: + type: string + description: + type: string + email: + type: string + id: + type: string + name: + type: string + organization: + $ref: '#/definitions/domain.Organization' + role: + $ref: '#/definitions/domain.Role' + updatedAt: + type: string + type: object + type: object domain.History: properties: accountId: @@ -246,6 +356,44 @@ definitions: updatedAt: type: string type: object + domain.ListOrganizationBody: + properties: + description: + type: string + id: + type: string + name: + type: string + phone: + type: string + status: + $ref: '#/definitions/domain.OrganizationStatus' + type: object + domain.ListUserBody: + properties: + accountId: + type: string + createdAt: + type: string + creator: + type: string + department: + type: string + description: + type: string + email: + type: string + id: + type: string + name: + type: string + organization: + $ref: '#/definitions/domain.Organization' + role: + $ref: '#/definitions/domain.Role' + updatedAt: + type: string + type: object domain.LoginRequest: properties: accountId: @@ -254,6 +402,17 @@ definitions: type: string password: type: string + required: + - accountId + - organizationId + - password + type: object + domain.LoginResponse: + properties: + accountId: + type: string + token: + type: string type: object domain.Organization: properties: @@ -272,12 +431,32 @@ definitions: primaryClusterId: type: string status: - type: string + $ref: '#/definitions/domain.OrganizationStatus' statusDescription: type: string updatedAt: type: string type: object + domain.OrganizationStatus: + enum: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 7 + type: integer + x-enum-varnames: + - OrganizationStatus_PENDING + - OrganizationStatus_CREATE + - OrganizationStatus_CREATING + - OrganizationStatus_CREATED + - OrganizationStatus_DELETE + - OrganizationStatus_DELETING + - OrganizationStatus_DELETED + - OrganizationStatus_ERROR domain.Role: properties: createdAt: @@ -313,11 +492,84 @@ definitions: secretKeyId: type: string type: object + domain.UpdateOrganizationRequest: + properties: + description: + maxLength: 100 + minLength: 0 + type: string + phone: + type: string + primaryClusterId: + type: string + type: object + domain.UpdateOrganizationResponse: + properties: + description: + type: string + id: + type: string + phone: + type: string + type: object + domain.UpdatePasswordRequest: + properties: + password: + type: string + required: + - password + type: object + domain.UpdatePasswordResponse: + type: object domain.UpdatePrimaryClusterRequest: properties: primaryClusterId: type: string type: object + domain.UpdateUserRequest: + properties: + department: + maxLength: 20 + minLength: 0 + type: string + description: + maxLength: 100 + minLength: 0 + type: string + email: + type: string + name: + maxLength: 20 + minLength: 0 + type: string + role: + enum: + - admin + - user + type: string + type: object + domain.UpdateUserResponse: + properties: + user: + properties: + accountId: + type: string + department: + type: string + description: + type: string + email: + type: string + id: + type: string + name: + type: string + organization: + $ref: '#/definitions/domain.Organization' + role: + $ref: '#/definitions/domain.Role' + type: object + type: object domain.User: properties: accountId: @@ -637,7 +889,7 @@ paths: "200": description: user detail schema: - $ref: '#/definitions/domain.User' + $ref: '#/definitions/domain.LoginResponse' summary: login tags: - Auth @@ -886,7 +1138,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/domain.Organization' + $ref: '#/definitions/domain.ListOrganizationBody' type: array security: - JWT: [] @@ -955,7 +1207,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/domain.Organization' + $ref: '#/definitions/domain.GetOrganizationResponse' security: - JWT: [] summary: Get organization detail @@ -971,13 +1223,19 @@ paths: name: organizationId required: true type: string + - description: update organization request + in: body + name: body + required: true + schema: + $ref: '#/definitions/domain.UpdateOrganizationRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/domain.Organization' + $ref: '#/definitions/domain.UpdateOrganizationResponse' security: - JWT: [] summary: Update organization detail @@ -1010,6 +1268,172 @@ paths: summary: Update primary cluster tags: - Organizations + /users: + get: + consumes: + - application/json + description: Get user list + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.ListUserBody' + type: array + security: + - JWT: [] + summary: Get user list + tags: + - Users + post: + consumes: + - application/json + description: Create user + parameters: + - description: create user request + in: body + name: body + required: true + schema: + $ref: '#/definitions/domain.CreateUserRequest' + produces: + - application/json + responses: + "200": + description: create user response + schema: + $ref: '#/definitions/domain.CreateUserResponse' + security: + - JWT: [] + summary: Create user + tags: + - Users + /users/{userId}: + delete: + consumes: + - application/json + description: Delete user + parameters: + - description: userId + in: path + name: userId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.User' + security: + - JWT: [] + summary: Delete user + tags: + - Users + get: + consumes: + - application/json + description: Get user detail + parameters: + - description: userId + in: path + name: userId + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.GetUserResponse' + security: + - JWT: [] + summary: Get user detail + tags: + - Users + post: + consumes: + - application/json + description: Update user password detail + parameters: + - description: userId + in: path + name: userId + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "409": + description: Conflict + security: + - JWT: [] + summary: Update user password detail + tags: + - Users + put: + consumes: + - application/json + description: Update user detail + parameters: + - description: userId + in: path + name: userId + required: true + type: string + - description: update user request + in: body + name: body + required: true + schema: + $ref: '#/definitions/domain.UpdateUserRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.UpdateUserResponse' + security: + - JWT: [] + summary: Update user detail + tags: + - Users + /users/{userId}/password: + put: + consumes: + - application/json + description: Update user password detail + parameters: + - description: userId + in: path + name: userId + required: true + type: string + - description: update user password request + in: body + name: body + required: true + schema: + $ref: '#/definitions/domain.UpdatePasswordRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.UpdatePasswordResponse' + security: + - JWT: [] + summary: Update user password detail + tags: + - Users securityDefinitions: JWT: in: header