diff --git a/internal/api/user/controller.go b/internal/api/user/controller.go new file mode 100644 index 0000000..59b32b0 --- /dev/null +++ b/internal/api/user/controller.go @@ -0,0 +1,152 @@ +package user + +import ( + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "merch-parser-api/internal/interfaces" + "merch-parser-api/internal/shared" + "merch-parser-api/pkg/responses" + "net/http" +) + +type controller struct { + service *service + utils interfaces.Utils +} + +func newController(service *service, utils interfaces.Utils) *controller { + return &controller{ + service: service, + utils: utils, + } +} + +func (h *Handler) RegisterRoutes(r *gin.RouterGroup) { + userGroup := r.Group("/user") + + userGroup.POST("/", h.controller.register) + userGroup.GET("/", h.controller.get) + userGroup.PUT("/", h.controller.update) + userGroup.DELETE("/", h.controller.delete) +} + +func (h *Handler) ExcludeRoutes() []shared.ExcludeRoute { + return []shared.ExcludeRoute{ + {Route: "/user", Method: http.MethodPost}, + } +} + +// @Summary Регистрация нового пользователя +// @Description Регистрация нового пользователя +// @Tags Users +// @Security BearerAuth +// @Accept json +// @Param body body Register true "новый пользователь" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /user [post] +func (co *controller) register(c *gin.Context) { + var register Register + if err := c.ShouldBindJSON(®ister); err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error("User | Failed to bind JSON on register") + return + } + + if err := co.service.register(register); err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error("User | Failed to register user") + return + } + + c.Status(http.StatusOK) +} + +// @Summary Получить информацию о пользователе +// @Description Получает информацию о пользователе по его uuid из токена +// @Tags Users +// @Security BearerAuth +// @Success 200 {object} Info +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 401 {object} responses.ErrorResponse401 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /user [get] +func (co *controller) get(c *gin.Context) { + userUuid, err := co.utils.GetUserUuidFromContext(c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error("User | Failed to get user uuid from context on get") + return + } + + response, err := co.service.get(userUuid) + if err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error("User | Failed to get user info") + return + } + + c.JSON(http.StatusOK, response) +} + +// @Summary Обновить информацию о пользователе +// @Description Обновить информацию о пользователе по его uuid из токена +// @Tags Users +// @Security BearerAuth +// @Accept json +// @Param body body Update true "изменения" +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 401 {object} responses.ErrorResponse401 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /user [put] +func (co *controller) update(c *gin.Context) { + userUuid, err := co.utils.GetUserUuidFromContext(c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error("User | Failed to get user uuid from context on update") + return + } + + var update Update + if err = c.ShouldBindJSON(&update); err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error("User | Failed to bind JSON on update") + return + } + + if err = co.service.update(userUuid, update); err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error("User | Failed to update user info") + return + } + + c.Status(http.StatusOK) +} + +// @Summary Удалить пользователя +// @Description Помечает пользователя как удаленного по его uuid из токена +// @Tags Users +// @Security BearerAuth +// @Success 200 +// @Failure 400 {object} responses.ErrorResponse400 +// @Failure 401 {object} responses.ErrorResponse401 +// @Failure 500 {object} responses.ErrorResponse500 +// @Router /user [delete] +func (co *controller) delete(c *gin.Context) { + userUuid, err := co.utils.GetUserUuidFromContext(c) + if err != nil { + c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()}) + log.WithError(err).Error("User | Failed to get user uuid from context on delete") + return + } + + if err = co.service.delete(userUuid); err != nil { + c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()}) + log.WithError(err).Error("User | Failed to delete user info") + return + } + + c.Status(http.StatusOK) +} diff --git a/internal/api/user/dto.go b/internal/api/user/dto.go new file mode 100644 index 0000000..1f71ef1 --- /dev/null +++ b/internal/api/user/dto.go @@ -0,0 +1,18 @@ +package user + +type Register struct { + Username string `json:"username"` + Email string `json:"email"` + Password string `json:"password"` +} + +type Info struct { + Username string `json:"username"` + Email string `json:"email"` + CreatedAt string `json:"created_at"` +} + +type Update struct { + Username string `json:"username"` + Email string `json:"email"` +} diff --git a/internal/api/user/handler.go b/internal/api/user/handler.go new file mode 100644 index 0000000..295d8d3 --- /dev/null +++ b/internal/api/user/handler.go @@ -0,0 +1,29 @@ +package user + +import ( + "gorm.io/gorm" + "merch-parser-api/internal/interfaces" +) + +type Handler struct { + controller *controller + service *service + repo UserRepo +} + +type Deps struct { + DB *gorm.DB + Utils interfaces.Utils +} + +func NewHandler(deps Deps) *Handler { + r := newRepo(deps.DB) + s := newService(r) + c := newController(s, deps.Utils) + + return &Handler{ + controller: c, + service: s, + repo: r, + } +} diff --git a/internal/api/user/model.go b/internal/api/user/model.go new file mode 100644 index 0000000..61ed5ac --- /dev/null +++ b/internal/api/user/model.go @@ -0,0 +1,22 @@ +package user + +import ( + "database/sql" + "time" +) + +type User struct { + Id uint `gorm:"primary_key"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt sql.NullTime `gorm:"column:updated_at"` + DeletedAt sql.NullTime `gorm:"column:deleted_at"` + Uuid string `gorm:"column:uuid"` + Username string `gorm:"column:username"` + Password string `gorm:"column:password"` + Email string `gorm:"column:email"` + Verified int `gorm:"column:verified"` +} + +func (User) TableName() string { + return "users" +} diff --git a/internal/api/user/repository.go b/internal/api/user/repository.go new file mode 100644 index 0000000..d7f0808 --- /dev/null +++ b/internal/api/user/repository.go @@ -0,0 +1,38 @@ +package user + +import ( + "gorm.io/gorm" + "time" +) + +type repo struct { + db *gorm.DB +} + +func newRepo(db *gorm.DB) *repo { + return &repo{db: db} +} + +type UserRepo interface { + register(user User) error + getByUuid(userUuid string) (User, error) + update(user map[string]any) error + delete(userUuid string) error +} + +func (r *repo) register(user User) error { + return r.db.Create(&user).Error +} + +func (r *repo) getByUuid(userUuid string) (user User, err error) { + err = r.db.Where("uuid = ?", userUuid).First(&user).Error + return user, err +} + +func (r *repo) update(user map[string]any) error { + return r.db.Where("uuid = ?", user["uuid"]).Updates(&user).Error +} + +func (r *repo) delete(userUuid string) error { + return r.db.Model(&User{}).Where("uuid = ?", userUuid).Update("deleted_at", time.Now().UTC()).Error +} diff --git a/internal/api/user/service.go b/internal/api/user/service.go new file mode 100644 index 0000000..b217fed --- /dev/null +++ b/internal/api/user/service.go @@ -0,0 +1,66 @@ +package user + +import ( + "database/sql" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "time" +) + +type service struct { + repo UserRepo +} + +func newService(repo UserRepo) *service { + return &service{repo: repo} +} + +func (s *service) register(dto Register) error { + user := User{ + CreatedAt: time.Now().UTC(), + UpdatedAt: sql.NullTime{Valid: false}, + DeletedAt: sql.NullTime{Valid: false}, + Uuid: uuid.NewString(), + Username: dto.Username, + Password: dto.Password, + Email: dto.Email, + Verified: 0, + } + + return s.repo.register(user) +} + +func (s *service) get(userUuid string) (Info, error) { + user, err := s.repo.getByUuid(userUuid) + if err != nil { + log.WithError(err).Error("User | Failed to get user by uuid") + return Info{}, err + } + + return Info{ + Username: user.Username, + Email: user.Email, + CreatedAt: user.CreatedAt.String(), + }, err +} + +func (s *service) update(userUuid string, update Update) error { + user := make(map[string]any) + + if update.Username != "" { + user["username"] = update.Username + } + + if update.Email != "" { + user["email"] = update.Email + } + + user["updated_at"] = time.Now().UTC() + user["uuid"] = userUuid + + return s.repo.update(user) +} + +func (s *service) delete(userUuid string) error { + return s.repo.delete(userUuid) +}