Skip to content

Commit

Permalink
introduced profile APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
khanzadimahdi committed Apr 6, 2024
1 parent 9528ddd commit b5e1293
Show file tree
Hide file tree
Showing 16 changed files with 357 additions and 1 deletion.
2 changes: 1 addition & 1 deletion backend/application/auth/resetpassword/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (uc *UseCase) ResetPassword(request Request) (*Response, error) {
return nil, err
}

salt := make([]byte, 1024)
salt := make([]byte, 64)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
Expand Down
13 changes: 13 additions & 0 deletions backend/application/dashboard/profile/changepassword/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package changepassword

type validationErrors map[string]string

type Request struct {
UserUUID string `json:"-"`
CurrentPassword string `json:"current_password"`
NewPassword string `json:"new_password"`
}

func (r *Request) Validate() (bool, validationErrors) {
return true, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package changepassword

type ChangePasswordResponse struct {
ValidationErrors validationErrors `json:"errors,omitempty"`
}
61 changes: 61 additions & 0 deletions backend/application/dashboard/profile/changepassword/usecase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package changepassword

import (
"crypto/rand"

"github.com/khanzadimahdi/testproject/domain/password"
"github.com/khanzadimahdi/testproject/domain/user"
)

type UseCase struct {
userRepository user.Repository
hasher password.Hasher
}

func NewUseCase(userRepository user.Repository, hasher password.Hasher) *UseCase {
return &UseCase{
userRepository: userRepository,
hasher: hasher,
}
}

func (uc *UseCase) ChangePassword(request Request) (*ChangePasswordResponse, error) {
if ok, validation := request.Validate(); !ok {
return &ChangePasswordResponse{
ValidationErrors: validation,
}, nil
}

u, err := uc.userRepository.GetOne(request.UserUUID)
if err != nil {
return nil, err
}

if !uc.passwordIsValid(u, []byte(request.CurrentPassword)) {
return &ChangePasswordResponse{
ValidationErrors: validationErrors{
"current_password": "current password is not valid",
},
}, nil
}

salt := make([]byte, 64)
if _, err := rand.Read(salt); err != nil {
return nil, err
}

u.PasswordHash = password.Hash{
Value: uc.hasher.Hash([]byte(request.NewPassword), salt),
Salt: salt,
}

if err := uc.userRepository.Save(&u); err != nil {
return nil, err
}

return &ChangePasswordResponse{}, err
}

func (uc *UseCase) passwordIsValid(u user.User, password []byte) bool {
return uc.hasher.Equal(password, u.PasswordHash.Value, u.PasswordHash.Salt)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package changepassword
9 changes: 9 additions & 0 deletions backend/application/dashboard/profile/getprofile/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package getprofile

type GetProfileResponse struct {
UUID string `json:"uuid,omitempty"`
Name string `json:"name,omitempty"`
Avatar string `json:"avatar,omitempty"`
Email string `json:"email,omitempty"`
Username string `json:"username,omitempty"`
}
30 changes: 30 additions & 0 deletions backend/application/dashboard/profile/getprofile/usecase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package getprofile

import (
"github.com/khanzadimahdi/testproject/domain/user"
)

type UseCase struct {
userRepository user.Repository
}

func NewUseCase(userRepository user.Repository) *UseCase {
return &UseCase{
userRepository: userRepository,
}
}

func (uc *UseCase) Profile(UUID string) (*GetProfileResponse, error) {
u, err := uc.userRepository.GetOne(UUID)
if err != nil {
return nil, err
}

return &GetProfileResponse{
UUID: UUID,
Name: u.Name,
Avatar: u.Avatar,
Email: u.Email,
Username: u.Username,
}, err
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package getprofile
15 changes: 15 additions & 0 deletions backend/application/dashboard/profile/updateprofile/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package updateprofile

type validationErrors map[string]string

type Request struct {
UserUUID string `json:"-"`
Name string `json:"name"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Username string `json:"username"`
}

func (r *Request) Validate() (bool, validationErrors) {
return true, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package updateprofile

type UpdateProfileResponse struct {
ValidationErrors validationErrors `json:"errors,omitempty"`
}
76 changes: 76 additions & 0 deletions backend/application/dashboard/profile/updateprofile/usecase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package updateprofile

import (
"github.com/khanzadimahdi/testproject/domain"
"github.com/khanzadimahdi/testproject/domain/user"
)

type UseCase struct {
userRepository user.Repository
}

func NewUseCase(userRepository user.Repository) *UseCase {
return &UseCase{
userRepository: userRepository,
}
}

func (uc *UseCase) UpdateProfile(request Request) (*UpdateProfileResponse, error) {
if ok, validation := request.Validate(); !ok {
return &UpdateProfileResponse{
ValidationErrors: validation,
}, nil
}

// make sure email is unique
exists, err := uc.anotherUserExists(request.Email, request.UserUUID)
if err != nil {
return nil, err
} else if exists {
return &UpdateProfileResponse{
ValidationErrors: map[string]string{
"email": "another user with this email already exists",
},
}, nil
}

// make sure username is unique
exists, err = uc.anotherUserExists(request.Username, request.UserUUID)
if err != nil {
return nil, err
} else if exists {
return &UpdateProfileResponse{
ValidationErrors: map[string]string{
"username": "another user with this email already exists",
},
}, nil
}

user, err := uc.userRepository.GetOne(request.UserUUID)
if err != nil {
return nil, err
}

user.Name = request.Name
user.Avatar = request.Avatar
user.Email = request.Email
user.Username = request.Username

err = uc.userRepository.Save(&user)
if err != nil {
return nil, err
}

return &UpdateProfileResponse{}, err
}

func (uc *UseCase) anotherUserExists(identity string, currentUserUUID string) (bool, error) {
u, err := uc.userRepository.GetOneByIdentity(identity)
if err == domain.ErrNotExists {
return false, nil
} else if err != nil {
return false, err
}

return u.UUID != currentUserUUID, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package updateprofile
13 changes: 13 additions & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import (
dashboardGetFile "github.com/khanzadimahdi/testproject/application/dashboard/file/getFile"
dashboardGetFiles "github.com/khanzadimahdi/testproject/application/dashboard/file/getFiles"
dashboardUploadFile "github.com/khanzadimahdi/testproject/application/dashboard/file/uploadFile"
"github.com/khanzadimahdi/testproject/application/dashboard/profile/changepassword"
"github.com/khanzadimahdi/testproject/application/dashboard/profile/getprofile"
"github.com/khanzadimahdi/testproject/application/dashboard/profile/updateprofile"
getFile "github.com/khanzadimahdi/testproject/application/file/getFile"
"github.com/khanzadimahdi/testproject/application/home"
"github.com/khanzadimahdi/testproject/infrastructure/console"
Expand All @@ -52,6 +55,7 @@ import (
dashboardArticleAPI "github.com/khanzadimahdi/testproject/presentation/http/api/dashboard/article"
dashboardElementAPI "github.com/khanzadimahdi/testproject/presentation/http/api/dashboard/element"
dashboardFileAPI "github.com/khanzadimahdi/testproject/presentation/http/api/dashboard/file"
"github.com/khanzadimahdi/testproject/presentation/http/api/dashboard/profile"
fileAPI "github.com/khanzadimahdi/testproject/presentation/http/api/file"
hashtagAPI "github.com/khanzadimahdi/testproject/presentation/http/api/hashtag"
homeapi "github.com/khanzadimahdi/testproject/presentation/http/api/home"
Expand Down Expand Up @@ -163,6 +167,10 @@ func httpHandler() http.Handler {
router.Handler(http.MethodGet, "/files/:uuid", fileAPI.NewShowHandler(getFileUseCase))

// -------------------- dashboard -------------------- //
getProfile := getprofile.NewUseCase(userRepository)
updateProfile := updateprofile.NewUseCase(userRepository)
dashboardChangePassword := changepassword.NewUseCase(userRepository, hasher)

dashboardCreateArticleUsecase := dashboardCreateArticle.NewUseCase(articlesRepository)
dashboardDeleteArticleUsecase := dashboardDeleteArticle.NewUseCase(articlesRepository)
dashboardGetArticleUsecase := dashboardGetArticle.NewUseCase(articlesRepository)
Expand All @@ -179,6 +187,11 @@ func httpHandler() http.Handler {
dashboardGetElementsUsecase := dashboardGetElements.NewUseCase(elementsRepository)
dashboardUpdateElementUsecase := dashboardUpdateElement.NewUseCase(elementsRepository)

// profile
router.Handler(http.MethodGet, "/api/dashboard/profile", middleware.NewAuthoriseMiddleware(profile.NewGetProfileHandler(getProfile), j, userRepository))
router.Handler(http.MethodPut, "/api/dashboard/profile", middleware.NewAuthoriseMiddleware(profile.NewUpdateProfileHandler(updateProfile), j, userRepository))
router.Handler(http.MethodPut, "/api/dashboard/password", middleware.NewAuthoriseMiddleware(profile.NewChangePasswordHandler(dashboardChangePassword), j, userRepository))

// articles
router.Handler(http.MethodPost, "/api/dashboard/articles", middleware.NewAuthoriseMiddleware(dashboardArticleAPI.NewCreateHandler(dashboardCreateArticleUsecase), j, userRepository))
router.Handler(http.MethodDelete, "/api/dashboard/articles/:uuid", middleware.NewAuthoriseMiddleware(dashboardArticleAPI.NewDeleteHandler(dashboardDeleteArticleUsecase), j, userRepository))
Expand Down
44 changes: 44 additions & 0 deletions backend/presentation/http/api/dashboard/profile/changepassword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package profile

import (
"encoding/json"
"errors"
"net/http"

"github.com/khanzadimahdi/testproject/application/auth"
"github.com/khanzadimahdi/testproject/application/dashboard/profile/changepassword"
"github.com/khanzadimahdi/testproject/domain"
)

type changePasswordHandler struct {
userCase *changepassword.UseCase
}

func NewChangePasswordHandler(userCase *changepassword.UseCase) *changePasswordHandler {
return &changePasswordHandler{
userCase: userCase,
}
}

func (h *changePasswordHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
var request changepassword.Request
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
rw.WriteHeader(http.StatusBadRequest)
return
}
request.UserUUID = auth.FromContext(r.Context()).UUID

response, err := h.userCase.ChangePassword(request)

switch true {
case errors.Is(err, domain.ErrNotExists):
rw.WriteHeader(http.StatusNotFound)
case err != nil:
rw.WriteHeader(http.StatusInternalServerError)
case len(response.ValidationErrors) > 0:
rw.WriteHeader(http.StatusBadRequest)
json.NewEncoder(rw).Encode(response)
default:
rw.WriteHeader(http.StatusNoContent)
}
}
38 changes: 38 additions & 0 deletions backend/presentation/http/api/dashboard/profile/getprofile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package profile

import (
"encoding/json"
"errors"
"net/http"

"github.com/khanzadimahdi/testproject/application/auth"
"github.com/khanzadimahdi/testproject/application/dashboard/profile/getprofile"
"github.com/khanzadimahdi/testproject/domain"
)

type getProfileHandler struct {
useCase *getprofile.UseCase
}

func NewGetProfileHandler(useCase *getprofile.UseCase) *getProfileHandler {
return &getProfileHandler{
useCase: useCase,
}
}

func (h *getProfileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
userUUID := auth.FromContext(r.Context()).UUID

response, err := h.useCase.Profile(userUUID)

switch true {
case errors.Is(err, domain.ErrNotExists):
rw.WriteHeader(http.StatusNotFound)
case err != nil:
rw.WriteHeader(http.StatusInternalServerError)
default:
rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(response)
}
}
Loading

0 comments on commit b5e1293

Please sign in to comment.