diff --git a/Makefile b/Makefile index 0e0564e..62661fb 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ restart: ## Stops ran containers, destroys them and restart docker compose -f compose-dev-backend.yml build ;\ docker compose -f compose-dev-backend.yml up -d -release_latest: ## Creates and pushes imaged to docker hub using the last git tag +release_latest: ## Creates and pushes images to docker hub using the last git tag docker buildx create --use ;\ docker buildx build --push --platform linux/amd64,linux/arm64 \ -t macyan/webdict:${GITTAG} -t macyan/webdict:latest . \ No newline at end of file diff --git a/backend/pkg/app/command/update_profile.go b/backend/pkg/app/command/update_profile.go index 1c2381f..b2a7076 100644 --- a/backend/pkg/app/command/update_profile.go +++ b/backend/pkg/app/command/update_profile.go @@ -14,6 +14,7 @@ type UpdateProfile struct { CurrentPassword string NewPassword string DefaultLangID string + ListOptions user.ListOptions } type UpdateProfileHandler struct { @@ -41,7 +42,7 @@ func (h UpdateProfileHandler) Handle(cmd UpdateProfile) error { return err } - if err = usr.ApplyChanges(cmd.Name, cmd.Email, passwd, usr.Role(), cmd.DefaultLangID); err != nil { + if err = usr.ApplyChanges(cmd.Name, cmd.Email, passwd, usr.Role(), cmd.DefaultLangID, cmd.ListOptions); err != nil { return err } diff --git a/backend/pkg/app/command/update_profile_test.go b/backend/pkg/app/command/update_profile_test.go index f67e014..ea3a07a 100644 --- a/backend/pkg/app/command/update_profile_test.go +++ b/backend/pkg/app/command/update_profile_test.go @@ -305,6 +305,7 @@ func TestUpdateProfileHandler_Handle_PositiveCases(t *testing.T) { CurrentPassword: currentPasswd, NewPassword: newPasswd, DefaultLangID: langID, + ListOptions: user.NewListOptions(true), } handler := NewUpdateProfileHandler(&usrRepo, &cipher, &langRepo) @@ -312,9 +313,11 @@ func TestUpdateProfileHandler_Handle_PositiveCases(t *testing.T) { updatedUsr := usrRepo.Calls[1].Arguments[0].(*user.User) data := updatedUsr.ToMap() + listData := updatedUsr.ListOptions() assert.Equal(t, cmd.Name, data["name"]) assert.Equal(t, cmd.Email, data["email"]) assert.Equal(t, newHash, data["password"]) assert.Equal(t, langID, data["defaultLangID"]) + assert.Equal(t, true, listData.ToMap()["showTranscription"]) } diff --git a/backend/pkg/app/command/update_user.go b/backend/pkg/app/command/update_user.go index 177df2f..c7b2e42 100644 --- a/backend/pkg/app/command/update_user.go +++ b/backend/pkg/app/command/update_user.go @@ -32,7 +32,7 @@ func (h UpdateUserHandler) Handle(cmd UpdateUser) error { return err } - if err = usr.ApplyChanges(cmd.Name, cmd.Email, passwd, cmd.Role, usr.DefaultLangID()); err != nil { + if err = usr.ApplyChanges(cmd.Name, cmd.Email, passwd, cmd.Role, usr.DefaultLangID(), usr.ListOptions()); err != nil { return err } diff --git a/backend/pkg/app/domain/user/user.go b/backend/pkg/app/domain/user/user.go index af28d44..2ce7151 100644 --- a/backend/pkg/app/domain/user/user.go +++ b/backend/pkg/app/domain/user/user.go @@ -8,13 +8,18 @@ import ( "unicode/utf8" ) -type User struct { - id string - name string - email string - password string - role Role - defaultLangID string +type ListOptions struct { + showTranscription bool +} + +func NewListOptions(showTranscription bool) ListOptions { + return ListOptions{showTranscription: showTranscription} +} + +func (l *ListOptions) ToMap() map[string]interface{} { + return map[string]interface{}{ + "showTranscription": l.showTranscription, + } } type Role int @@ -28,6 +33,16 @@ func (r Role) valid() bool { return r >= Admin && r <= Author } +type User struct { + id string + name string + email string + password string + role Role + defaultLangID string + listOptions ListOptions +} + func NewUser(name, email, password string, role Role) (*User, error) { u := User{ id: uuid.New().String(), @@ -35,6 +50,9 @@ func NewUser(name, email, password string, role Role) (*User, error) { email: email, password: password, role: role, + listOptions: ListOptions{ + showTranscription: false, + }, } if err := u.validate(); err != nil { @@ -64,24 +82,29 @@ func (u *User) DefaultLangID() string { return u.defaultLangID } -func (u *User) ApplyChanges(name, email, passwd string, role Role, defaultLangID string) error { +func (u *User) ListOptions() ListOptions { + return u.listOptions +} + +func (u *User) ApplyChanges(name, email, passwd string, role Role, defaultLangID string, listOptions ListOptions) error { updated := *u - updated.applyChanges(name, email, passwd, role, defaultLangID) + updated.applyChanges(name, email, passwd, role, defaultLangID, listOptions) if err := updated.validate(); err != nil { return err } - u.applyChanges(name, email, passwd, role, defaultLangID) + u.applyChanges(name, email, passwd, role, defaultLangID, listOptions) return nil } -func (u *User) applyChanges(name, email, passwd string, role Role, defaultLangID string) { +func (u *User) applyChanges(name, email, passwd string, role Role, defaultLangID string, listOptions ListOptions) { u.name = name u.email = email u.role = role u.defaultLangID = defaultLangID u.password = passwd + u.listOptions = listOptions } func (u *User) ToMap() map[string]interface{} { @@ -92,24 +115,7 @@ func (u *User) ToMap() map[string]interface{} { "password": u.password, "role": int(u.role), "defaultLangID": u.defaultLangID, - } -} - -func UnmarshalFromDB( - id string, - name string, - email string, - password string, - role int, - defaultLangID string, -) *User { - return &User{ - id: id, - name: name, - email: email, - password: password, - role: Role(role), - defaultLangID: defaultLangID, + "listOptions": u.listOptions.ToMap(), } } @@ -140,3 +146,23 @@ func (u *User) validate() error { return err } + +func UnmarshalFromDB( + id string, + name string, + email string, + password string, + role Role, + defaultLangID string, + listOptions ListOptions, +) *User { + return &User{ + id: id, + name: name, + email: email, + password: password, + role: role, + defaultLangID: defaultLangID, + listOptions: listOptions, + } +} diff --git a/backend/pkg/app/domain/user/user_test.go b/backend/pkg/app/domain/user/user_test.go index 9f49a7e..0063300 100644 --- a/backend/pkg/app/domain/user/user_test.go +++ b/backend/pkg/app/domain/user/user_test.go @@ -122,6 +122,7 @@ func TestNewUser(t *testing.T) { assert.Equal(t, got.password, tt.args.password) assert.Equal(t, got.email, tt.args.email) assert.Equal(t, got.role, tt.args.role) + assert.Equal(t, false, got.listOptions.showTranscription) }) } } @@ -132,11 +133,12 @@ func TestUnmarshalFromDB(t *testing.T) { name: "testName", email: "testEmail", password: "testPassword", - role: 0, + role: Role(0), defaultLangID: "testLang", + listOptions: ListOptions{showTranscription: true}, } - assert.Equal(t, &user, UnmarshalFromDB(user.id, user.name, user.email, user.password, int(user.role), user.defaultLangID)) + assert.Equal(t, &user, UnmarshalFromDB(user.id, user.name, user.email, user.password, user.role, user.defaultLangID, user.listOptions)) } func TestRole_valid(t *testing.T) { @@ -170,10 +172,11 @@ func TestRole_valid(t *testing.T) { func TestUser_ApplyChanges(t *testing.T) { type fields struct { - name string - email string - password string - role Role + name string + email string + password string + role Role + listOptions ListOptions } type args struct { name string @@ -181,6 +184,7 @@ func TestUser_ApplyChanges(t *testing.T) { passwd string role Role defaultLangID string + listOptions ListOptions } tests := []struct { name string @@ -191,16 +195,18 @@ func TestUser_ApplyChanges(t *testing.T) { { "Error on validation, changes should not be applied", fields{ - name: "testName", - email: "test@mail.com", - password: "testPasswd", - role: Admin, + name: "testName", + email: "test@mail.com", + password: "testPasswd", + role: Admin, + listOptions: ListOptions{showTranscription: true}, }, args{ - name: "name", - email: "invalidEmail", - passwd: "testPasswd", - role: Author, + name: "name", + email: "invalidEmail", + passwd: "testPasswd", + role: Author, + listOptions: ListOptions{showTranscription: false}, }, func(t assert.TestingT, err error, usr *User, details string) { assert.True(t, strings.Contains(err.Error(), "email is not valid"), details) @@ -213,10 +219,11 @@ func TestUser_ApplyChanges(t *testing.T) { { "Applied changes", fields{ - name: "testName", - email: "test@mail.com", - password: "testPasswd", - role: Admin, + name: "testName", + email: "test@mail.com", + password: "testPasswd", + role: Admin, + listOptions: ListOptions{showTranscription: true}, }, args{ name: "name", @@ -224,6 +231,7 @@ func TestUser_ApplyChanges(t *testing.T) { passwd: "updatedPasswd", role: Author, defaultLangID: "langID", + listOptions: ListOptions{showTranscription: false}, }, func(t assert.TestingT, err error, usr *User, details string) { assert.Nil(t, err, details) @@ -232,18 +240,20 @@ func TestUser_ApplyChanges(t *testing.T) { assert.Equal(t, "updatedPasswd", usr.password) assert.Equal(t, Author, usr.role) assert.Equal(t, "langID", usr.defaultLangID) + assert.Equal(t, false, usr.listOptions.showTranscription) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { u := &User{ - name: tt.fields.name, - email: tt.fields.email, - password: tt.fields.password, - role: tt.fields.role, + name: tt.fields.name, + email: tt.fields.email, + password: tt.fields.password, + role: tt.fields.role, + listOptions: tt.fields.listOptions, } - tt.wantFn(t, u.ApplyChanges(tt.args.name, tt.args.email, tt.args.passwd, tt.args.role, tt.args.defaultLangID), u, fmt.Sprintf("ApplyChanges(%v, %v, %v, %v, %v)", tt.args.name, tt.args.email, tt.args.passwd, tt.args.role, tt.args.defaultLangID)) + tt.wantFn(t, u.ApplyChanges(tt.args.name, tt.args.email, tt.args.passwd, tt.args.role, tt.args.defaultLangID, tt.args.listOptions), u, fmt.Sprintf("ApplyChanges(%v, %v, %v, %v, %v)", tt.args.name, tt.args.email, tt.args.passwd, tt.args.role, tt.args.defaultLangID)) }) } } diff --git a/backend/pkg/app/query/types.go b/backend/pkg/app/query/types.go index 265ab3d..dc2a660 100644 --- a/backend/pkg/app/query/types.go +++ b/backend/pkg/app/query/types.go @@ -90,6 +90,11 @@ type UserView struct { Email string Role RoleView DefaultLang LangView + ListOptions UserListOptionsView +} + +type UserListOptionsView struct { + ShowTranscription bool } func (v *UserView) sanitize(sanitizer *strictSanitizer) { diff --git a/backend/pkg/server/profile_handlers.go b/backend/pkg/server/profile_handlers.go index 1e2ab40..77eb0ad 100644 --- a/backend/pkg/server/profile_handlers.go +++ b/backend/pkg/server/profile_handlers.go @@ -1,6 +1,7 @@ package server import ( + "errors" "fmt" "github.com/gin-gonic/gin" "github.com/macyan13/webdict/backend/pkg/app/command" @@ -52,8 +53,9 @@ func (s *HTTPServer) UpdateProfile() gin.HandlerFunc { CurrentPassword: request.CurrentPassword, NewPassword: request.NewPassword, DefaultLangID: request.DefaultLangID, + ListOptions: user.NewListOptions(request.ListOptions.ShowTranscription), }); err != nil { - if err == user.ErrEmailAlreadyExists { + if errors.Is(err, user.ErrEmailAlreadyExists) { s.badRequest(c, fmt.Errorf("user with email %s already exists", request.Email)) return } diff --git a/backend/pkg/server/profile_handlers_test.go b/backend/pkg/server/profile_handlers_test.go index 27b7d7a..38e2e11 100644 --- a/backend/pkg/server/profile_handlers_test.go +++ b/backend/pkg/server/profile_handlers_test.go @@ -24,6 +24,7 @@ func TestHTTPServer_GetProfile(t *testing.T) { assert.Equal(t, name, profile.Name) assert.Equal(t, email, profile.Email) assert.Equal(t, int(user.Author), profile.Role.ID) + assert.Equal(t, false, profile.ListOptions.ShowTranscription) } func TestHTTPServer_GetProfile_Unauthorized(t *testing.T) { @@ -51,6 +52,7 @@ func TestHTTPServer_TestHTTPServer_UpdateProfile(t *testing.T) { Email: updatedEmail, CurrentPassword: currentPasswd, NewPassword: newPasswd, + ListOptions: profileListOptions{ShowTranscription: true}, } jsonValue, _ := json.Marshal(updRequest) @@ -63,6 +65,7 @@ func TestHTTPServer_TestHTTPServer_UpdateProfile(t *testing.T) { profile := getProfile(t, s, updatedEmail, newPasswd) assert.Equal(t, updatedName, profile.Name) assert.Equal(t, updatedEmail, profile.Email) + assert.Equal(t, true, profile.ListOptions.ShowTranscription) } func TestHTTPServer_TestHTTPServerUnauthorized(t *testing.T) { diff --git a/backend/pkg/server/types.go b/backend/pkg/server/types.go index 8a390e6..01b9945 100644 --- a/backend/pkg/server/types.go +++ b/backend/pkg/server/types.go @@ -23,11 +23,16 @@ type userRequest struct { } type updateProfileRequest struct { - Name string `json:"name"` - Email string `json:"email"` - CurrentPassword string `json:"current_password"` - NewPassword string `json:"new_password"` - DefaultLangID string `json:"default_lang_id"` + Name string `json:"name"` + Email string `json:"email"` + CurrentPassword string `json:"current_password"` + NewPassword string `json:"new_password"` + DefaultLangID string `json:"default_lang_id"` + ListOptions profileListOptions `json:"list_options"` +} + +type profileListOptions struct { + ShowTranscription bool `json:"show_transcription"` } type langRequest struct { @@ -70,11 +75,12 @@ type langResponse struct { } type userResponse struct { - ID string `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Role roleResponse `json:"role"` - DefaultLang langResponse `json:"default_lang"` + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role roleResponse `json:"role"` + DefaultLang langResponse `json:"default_lang"` + ListOptions profileListOptions `json:"list_options"` } type roleResponse struct { diff --git a/backend/pkg/server/user_handlers.go b/backend/pkg/server/user_handlers.go index 59ef56c..8d2ab87 100644 --- a/backend/pkg/server/user_handlers.go +++ b/backend/pkg/server/user_handlers.go @@ -129,10 +129,11 @@ func (s *HTTPServer) userViewsToResponses(users []query.UserView) []userResponse func (s *HTTPServer) userViewToResponse(usr query.UserView) userResponse { response := userResponse{ - ID: usr.ID, - Name: usr.Name, - Email: usr.Email, - Role: s.roleViewToResponse(usr.Role), + ID: usr.ID, + Name: usr.Name, + Email: usr.Email, + Role: s.roleViewToResponse(usr.Role), + ListOptions: profileListOptions{ShowTranscription: usr.ListOptions.ShowTranscription}, } if usr.DefaultLang.ID != "" { diff --git a/backend/pkg/store/inmemory/user.go b/backend/pkg/store/inmemory/user.go index dfd82d8..8349416 100644 --- a/backend/pkg/store/inmemory/user.go +++ b/backend/pkg/store/inmemory/user.go @@ -90,11 +90,14 @@ func (u *UserRepo) GetView(id string) (query.UserView, error) { return query.UserView{}, err } + listOptions := userData["listOptions"].(map[string]interface{}) + return query.UserView{ - ID: userData["id"].(string), - Name: userData["name"].(string), - Email: userData["email"].(string), - Role: role, + ID: userData["id"].(string), + Name: userData["name"].(string), + Email: userData["email"].(string), + Role: role, + ListOptions: query.UserListOptionsView{ShowTranscription: listOptions["showTranscription"].(bool)}, }, nil } diff --git a/backend/pkg/store/mongo/user.go b/backend/pkg/store/mongo/user.go index 561d422..1dd485e 100644 --- a/backend/pkg/store/mongo/user.go +++ b/backend/pkg/store/mongo/user.go @@ -21,12 +21,18 @@ type UserRepo struct { // UserModel represents mongo user document type UserModel struct { - ID string `bson:"_id"` - Name string `bson:"name"` - Email string `bson:"email"` - Password string `bson:"password"` - Role int `bson:"role"` - DefaultLangID string `bson:"default_lang_id"` + ID string `bson:"_id"` + Name string `bson:"name"` + Email string `bson:"email"` + Password string `bson:"password"` + Role int `bson:"role"` + DefaultLangID string `bson:"default_lang_id"` + ListOptions ListOptionsModel `bson:"list_options"` +} + +// ListOptionsModel represents the nested list options in the mongo user document +type ListOptionsModel struct { + ShowTranscription bool `bson:"show_transcription"` } // NewUserRepo creates new UserRepo @@ -213,6 +219,9 @@ func (r *UserRepo) fromModelToView(model UserModel) (query.UserView, error) { Email: model.Email, Role: role, DefaultLang: query.LangView{}, + ListOptions: query.UserListOptionsView{ + ShowTranscription: model.ListOptions.ShowTranscription, + }, } if model.DefaultLangID != "" { @@ -234,7 +243,8 @@ func (r *UserRepo) fromModelToDomain(model UserModel) *user.User { model.Name, model.Email, model.Password, - model.Role, + user.Role(model.Role), model.DefaultLangID, + user.NewListOptions(model.ListOptions.ShowTranscription), ) } diff --git a/backend/pkg/store/mongo/user_test.go b/backend/pkg/store/mongo/user_test.go index 8b5567f..2a3827f 100644 --- a/backend/pkg/store/mongo/user_test.go +++ b/backend/pkg/store/mongo/user_test.go @@ -14,8 +14,11 @@ func TestUserRepo_fromDomainToModel(t *testing.T) { password := "12345678" role := user.Admin usr, err := user.NewUser(name, email, password, role) + assert.Nil(t, err) + err = usr.ApplyChanges(name, email, password, role, usr.DefaultLangID(), user.NewListOptions(true)) assert.Nil(t, err) + repo := UserRepo{} model, err := repo.fromDomainToModel(usr) @@ -25,6 +28,7 @@ func TestUserRepo_fromDomainToModel(t *testing.T) { assert.Equal(t, email, model.Email) assert.Equal(t, password, model.Password) assert.Equal(t, int(role), model.Role) + assert.Equal(t, true, model.ListOptions.ShowTranscription) } func TestUserRepo_fromModelToView(t *testing.T) { @@ -82,12 +86,12 @@ func TestUserRepo_fromModelToView(t *testing.T) { langRepo.On("GetView", "langID", "authorID").Return(query.LangView{Name: "test"}, nil) return fields{langRepo: &langRepo, roleConverter: query.NewRoleMapper()} }, - args{model: UserModel{ID: "authorID", DefaultLangID: "langID", Role: 1}}, + args{model: UserModel{ID: "authorID", DefaultLangID: "langID", Role: 1, ListOptions: ListOptionsModel{ShowTranscription: true}}}, query.UserView{ID: "authorID", DefaultLang: query.LangView{Name: "test"}, Role: query.RoleView{ ID: 1, Name: "Admin", IsAdmin: true, - }}, + }, ListOptions: query.UserListOptionsView{ShowTranscription: true}}, assert.NoError, }, } @@ -106,3 +110,24 @@ func TestUserRepo_fromModelToView(t *testing.T) { }) } } + +func TestUserRepo_fromModelToDomain(t *testing.T) { + model := UserModel{ + ID: "authorID", + Name: "John", + Email: "John@do.com", + Password: "testPassword", + Role: 1, + } + + repo := UserRepo{} + usr := repo.fromModelToDomain(model) + listOptions := usr.ListOptions() + + assert.Equal(t, model.ID, usr.ID()) + assert.Equal(t, model.Email, usr.Email()) + assert.Equal(t, model.Password, usr.Password()) + assert.Equal(t, user.Role(model.Role), usr.Role()) + assert.Equal(t, model.DefaultLangID, usr.DefaultLangID()) + assert.Equal(t, false, listOptions.ToMap()["showTranscription"]) +} diff --git a/backend/webdict.rest b/backend/webdict.rest index 8705ca8..b42678f 100644 --- a/backend/webdict.rest +++ b/backend/webdict.rest @@ -151,7 +151,10 @@ Authorization: {{user_auth_type}} {{user_auth_token}} { "name": "John Dorry", "email": "{{userEmail}}", - "default_lang_id": "{{lang_id}}" + "default_lang_id": "{{lang_id}}", + "list_options": { + "show_transcription": true + } } > {% @@ -173,6 +176,7 @@ Authorization: {{user_auth_type}} {{user_auth_token}} client.assert(response.body.hasOwnProperty("id"), "User ID is not presented") client.assert(response.body.default_lang.name === "DE", "lang name from get profile is not correct") client.assert(response.body.name === "John Dorry", "name from get profile is not correct") + client.assert(response.body.list_options.show_transcription === true, "List options - show transcription from get profile is not correct") }) %} diff --git a/compose-dev-backend.yml b/compose-dev-backend.yml index 7964061..3923c07 100644 --- a/compose-dev-backend.yml +++ b/compose-dev-backend.yml @@ -1,5 +1,3 @@ -version: "2" - services: # webdict: # build: