From 25378f83c3f4123b635c6614bd4796d3c8e9efa8 Mon Sep 17 00:00:00 2001 From: NoBody313 Date: Sat, 18 Jan 2025 15:16:34 +0700 Subject: [PATCH 1/2] Nambah Logic Member (Blm Untuk Main Auth) --- .env | 4 +- app/controllers/memberController.go | 158 +++++++++++++++++++++++++ app/models/members.go | 49 ++++++++ app/repositories/members_repository.go | 32 +++++ app/services/member_service.go | 26 ++++ config/automigrate.go | 1 + routes/init_routes.go | 1 + routes/members.go | 23 ++++ 8 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 app/controllers/memberController.go create mode 100644 app/models/members.go create mode 100644 app/repositories/members_repository.go create mode 100644 app/services/member_service.go create mode 100644 routes/members.go diff --git a/.env b/.env index 402872a..752dbbe 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ DB_USERNAME= root DB_PASSWORD= -DB_DATABASE= ieee +DB_DATABASE= ieeee DB_HOST= localhost -PORT = 3306 +PORT = 3308 diff --git a/app/controllers/memberController.go b/app/controllers/memberController.go new file mode 100644 index 0000000..ffb944a --- /dev/null +++ b/app/controllers/memberController.go @@ -0,0 +1,158 @@ +package controllers + +import ( + "backend/app/models" + "backend/app/services" + "backend/utils" + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" +) + +type MemberController struct { + DB *gorm.DB + Service *services.MemberService +} + +type MembersResponse struct { + ID uint `gorm:"primaryKey"` + Name string `json:"name"` + Email string `json:"email" gorm:"unique"` + Password string `json:"password" gorm:"-"` + Role Role `json:"role" gorm:"type:enum('admin', 'member', 'it'); default:'member'"` +} + +type Role string + +func (mc *MemberController) GetMembers(ctx *gin.Context) { + members, err := mc.Service.GetMembers() + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get members"}) + return + } + + var response []MembersResponse + for _, member := range members { + response = append(response, MembersResponse{ + ID: member.ID, + Name: member.Name, + Email: member.Email, + Role: Role(member.Role), + }) + } + ctx.JSON(http.StatusOK, gin.H{"data": response}) +} + +func (mc *MemberController) GetMemberById(ctx *gin.Context) { + memberId := ctx.Param("id") + member, err := mc.Service.GetMemberById(memberId) + if err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "Failed to get member"}) + return + } + ctx.JSON(http.StatusOK, gin.H{"data": member}) +} + +func (mc *MemberController) CreateMember(ctx *gin.Context) { + // Struktur untuk input JSON + var input struct { + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + Role Role `json:"role" gorm:"type:enum('admin', 'member', 'it'); default:'member'"` + } + + if err := ctx.ShouldBindJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) + return + } + + //validRoles := map[Role]bool{"admin": true, "member": true, "it": true} + //if !validRoles[input.Role] { + // ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) + // return + //} + + member := models.Members{ + Name: input.Name, + Email: input.Email, + Password: input.Password, + Role: models.Role(input.Role), + } + + validationErrors := utils.ValidateStruct(member) + if len(validationErrors) > 0 { + ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) + return + } + + if err := mc.Service.CreateMember(&member); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save member to database", "details": err.Error()}) + return + } + + ctx.JSON(http.StatusCreated, gin.H{"message": "Member created successfully"}) +} + +func (mc *MemberController) UpdateMember(ctx *gin.Context) { + memberId := ctx.Param("id") + member, err := mc.Service.GetMemberById(memberId) + if err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "Member not found"}) + return + } + + var input struct { + Name *string `json:"name" binding:"omitempty"` + Email *string `json:"email" binding:"omitempty,email"` + Password *string `json:"password" binding:"omitempty"` + Role *Role `json:"role" binding:"omitempty"` + } + + if err := ctx.ShouldBindJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) + return + } + + if input.Name != nil { + member.Name = *input.Name + } + if input.Email != nil { + member.Email = *input.Email + } + if input.Password != nil { + member.Password = *input.Password + } + + if input.Role != nil { + validRoles := map[Role]bool{"admin": true, "member": true, "it": true} + if !validRoles[*input.Role] { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) + return + } + member.Role = models.Role(*input.Role) + } + + validationErrors := utils.ValidateStruct(member) + if len(validationErrors) > 0 { + ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) + return + } + + if err := mc.Service.UpdateMember(member); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to update member to database", + "details": err.Error(), + }) + return + } + + response := MembersResponse{ + ID: member.ID, + Name: member.Name, + Email: member.Email, + Role: Role(member.Role), + } + ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Member %s updated successfully", memberId), "data": response}) +} diff --git a/app/models/members.go b/app/models/members.go new file mode 100644 index 0000000..07ce174 --- /dev/null +++ b/app/models/members.go @@ -0,0 +1,49 @@ +package models + +import ( + "errors" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type Members struct { + ID uint `json:"id" gorm:"primaryKey;autoIncrement:true"` + Name string `json:"name"` + Email string `json:"email" gorm:"unique"` + Password string `json:"-"` + Role Role `json:"role" gorm:"type:enum('superAdmin' ,'admin', 'member', 'it'); default:'member'"` +} +type Role string + +const ( + RoleAdmin Role = "admin" + RoleMember Role = "member" + RoleIT Role = "it" +) + +func (r Role) IsValid() bool { + switch r { + case RoleAdmin, RoleMember, RoleIT: + return true + } + return true +} + +func (m *Members) BeforeSave(*gorm.DB) (err error) { + if m.Name == "" || m.Email == "" || m.Password == "" { + return errors.New("name, email, and password are required") + } + + if !m.Role.IsValid() { + m.Role = RoleMember + } + + if m.Password != "" { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(m.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + m.Password = string(hashedPassword) + } + return nil +} diff --git a/app/repositories/members_repository.go b/app/repositories/members_repository.go new file mode 100644 index 0000000..3dc57e7 --- /dev/null +++ b/app/repositories/members_repository.go @@ -0,0 +1,32 @@ +package repositories + +import ( + "backend/app/models" + "gorm.io/gorm" +) + +type MembersRepository struct { + DB *gorm.DB +} + +func (mr *MembersRepository) GetMembers() ([]models.Members, error) { + var members []models.Members + if err := mr.DB.Find(&members).Error; err != nil { + return nil, err + } + return members, nil +} + +func (mr *MembersRepository) GetMembersById(id string) (*models.Members, error) { + var member models.Members + err := mr.DB.Where("id = ?", id).First(&member).Error + return &member, err +} + +func (mr *MembersRepository) CreateMembers(members *models.Members) error { + return mr.DB.Create(members).Error +} + +func (mr *MembersRepository) UpdateMembers(members *models.Members) error { + return mr.DB.Save(members).Error +} diff --git a/app/services/member_service.go b/app/services/member_service.go new file mode 100644 index 0000000..55497bd --- /dev/null +++ b/app/services/member_service.go @@ -0,0 +1,26 @@ +package services + +import ( + "backend/app/models" + "backend/app/repositories" +) + +type MemberService struct { + Repo *repositories.MembersRepository +} + +func (ms *MemberService) GetMembers() ([]models.Members, error) { + return ms.Repo.GetMembers() +} + +func (ms *MemberService) GetMemberById(id string) (*models.Members, error) { + return ms.Repo.GetMembersById(id) +} + +func (ms *MemberService) CreateMember(member *models.Members) error { + return ms.Repo.CreateMembers(member) +} + +func (ms *MemberService) UpdateMember(member *models.Members) error { + return ms.Repo.UpdateMembers(member) +} diff --git a/config/automigrate.go b/config/automigrate.go index f199114..f8c7ff9 100644 --- a/config/automigrate.go +++ b/config/automigrate.go @@ -14,6 +14,7 @@ func AutoMigrate(db *gorm.DB) { &models.Activities{}, &models.News{}, &models.User{}, + &models.Members{}, ) if err != nil { log.Fatal("Failed to auto migrate: ", err) diff --git a/routes/init_routes.go b/routes/init_routes.go index 8a866d7..67847ba 100644 --- a/routes/init_routes.go +++ b/routes/init_routes.go @@ -29,6 +29,7 @@ func InitRouter(db *gorm.DB) *gin.Engine { RegisterActivityRoutes(r, db) RegisterNewsRoutes(r, db) RegisterAchievementRoutes(r, db) + RegisterMemberRoutes(r, db) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("/swagger/doc.json"))) r.Static("/uploads", "./uploads") diff --git a/routes/members.go b/routes/members.go new file mode 100644 index 0000000..9d51ca4 --- /dev/null +++ b/routes/members.go @@ -0,0 +1,23 @@ +package routes + +import ( + "backend/app/controllers" + "backend/app/repositories" + "backend/app/services" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func RegisterMemberRoutes(r *gin.Engine, db *gorm.DB) { + memberRepo := &repositories.MembersRepository{DB: db} + memberService := &services.MemberService{Repo: memberRepo} + memberController := &controllers.MemberController{Service: memberService} + + r.GET("/members", memberController.GetMembers) + r.GET("member/:id", memberController.GetMemberById) + + authGroup := AuthGroup(r) + authGroup.POST("/members", memberController.CreateMember) + authGroup.PATCH("/member/:id", memberController.UpdateMember) + authGroup.DELETE("/member/:id") +} From 6e40ab3715820840a48fe157c09219092feec3b5 Mon Sep 17 00:00:00 2001 From: NoBody313 Date: Sun, 19 Jan 2025 02:43:23 +0700 Subject: [PATCH 2/2] Perubahan pada Member menjadi User, Pembuatan CRUD User, Login & Register, Register dan Create User dengan Base Role 'member'. --- app/controllers/authAdmin.go | 47 ----- .../authentication/authController.go | 165 +++++++++++++++ app/controllers/memberController.go | 158 --------------- app/controllers/userController.go | 189 ++++++++++++++++++ app/models/members.go | 49 ----- app/models/user.go | 49 ++--- app/repositories/members_repository.go | 30 +-- app/services/auth_service.go | 30 --- app/services/member_service.go | 26 --- app/services/user_service.go | 30 +++ config/automigrate.go | 2 +- routes/auth_routes.go | 17 +- routes/init_routes.go | 4 +- routes/members.go | 23 --- routes/users.go | 23 +++ utils/hash.go | 15 ++ 16 files changed, 474 insertions(+), 383 deletions(-) delete mode 100644 app/controllers/authAdmin.go create mode 100644 app/controllers/authentication/authController.go delete mode 100644 app/controllers/memberController.go create mode 100644 app/controllers/userController.go delete mode 100644 app/models/members.go delete mode 100644 app/services/auth_service.go delete mode 100644 app/services/member_service.go create mode 100644 app/services/user_service.go delete mode 100644 routes/members.go create mode 100644 routes/users.go create mode 100644 utils/hash.go diff --git a/app/controllers/authAdmin.go b/app/controllers/authAdmin.go deleted file mode 100644 index 539d75f..0000000 --- a/app/controllers/authAdmin.go +++ /dev/null @@ -1,47 +0,0 @@ -// app/controllers/authAdmin.go -package controllers - -import ( - "backend/app/services" - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -type KodeInput struct { - Kode string `json:"kode" binding:"required"` -} - -// LoginAdmin godoc -// @Summary Login as as admin. -// @Description Logging in to get jwt token to access admin or user api by roles. -// @Tags Auth -// @Param Body body KodeInput true "the body to login a admin" -// @Produce json -// @Success 200 {object} map[string]interface{} -// @Router /login-admin [post] -func LoginAdmin(c *gin.Context) { - db := c.MustGet("db").(*gorm.DB) - var input KodeInput - - if err := c.ShouldBindJSON(&input); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - token, err := services.LoginCheckAdmin(db, input.Kode) - - if err != nil { - fmt.Println(err) - c.JSON(http.StatusBadRequest, gin.H{"error": "code is incorrect."}) - return - } - - admin := map[string]string{ - "kode": input.Kode, - } - - c.JSON(http.StatusOK, gin.H{"message": "login success", "user": admin, "token": token}) -} diff --git a/app/controllers/authentication/authController.go b/app/controllers/authentication/authController.go new file mode 100644 index 0000000..3ebeedd --- /dev/null +++ b/app/controllers/authentication/authController.go @@ -0,0 +1,165 @@ +package authentication + +import ( + "backend/app/models" + "backend/utils" + token2 "backend/utils/token" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" + "strings" +) + +type AuthController struct { + DB *gorm.DB +} + +type UserLoginResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` +} + +type DataResponse struct { + Token string `json:"token"` + User UserLoginResponse `json:"user"` +} + +type Response struct { + Status string `json:"status"` + Data DataResponse `json:"data"` +} + +// Login handles user login +func (ac *AuthController) Login(c *gin.Context) { + var input struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + } + + // Input Validation + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Find User by Email + var user models.User + if err := ac.DB.Where("email = ?", strings.ToLower(input.Email)).First(&user).Error; err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"}) + return + } + + // Validate Password + if err := utils.CheckPassword(user.Password, input.Password); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials password"}) + return + } + + // Generate Token (placeholder function, implement as needed) + token, err := token2.GenerateToken(user.ID, string(user.Role)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"}) + return + } + + // Respond with token and user data + c.JSON(http.StatusOK, Response{ + Status: "success", + Data: DataResponse{ + Token: token, + User: UserLoginResponse{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: string(user.Role), + }, + }, + }) + +} + +// Register handles user registration +func (ac *AuthController) Register(c *gin.Context) { + var input struct { + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + Role string `json:"role"` + } + + // Input Validation + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Validate Role + role := models.Role(strings.ToLower(input.Role)) + if input.Role != "" && !role.IsValid() { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role"}) + return + } + + // Hash Password + hashedPassword, err := utils.HashPassword(input.Password) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to hash password"}) + return + } + + // Create User + user := models.User{ + Name: input.Name, + Email: strings.ToLower(input.Email), + Password: hashedPassword, // Hash disimpan langsung di sini + Role: role, + } + + // Simpan ke database + if err := ac.DB.Create(&user).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "user created successfully"}) +} + +//OLD + +//type KodeInput struct { +// Kode string `json:"kode" binding:"required"` +//} +// +//// LoginAdmin godoc +//// @Summary Login as as admin. +//// @Description Logging in to get jwt token to access admin or user api by roles. +//// @Tags Auth +//// @Param Body body KodeInput true "the body to login a admin" +//// @Produce json +//// @Success 200 {object} map[string]interface{} +//// @Router /login-admin [post] +//func LoginAdmin(c *gin.Context) { +// db := c.MustGet("db").(*gorm.DB) +// var input KodeInput +// +// if err := c.ShouldBindJSON(&input); err != nil { +// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) +// return +// } +// +// token, err := services.LoginCheckAdmin(db, input.Kode) +// +// if err != nil { +// fmt.Println(err) +// c.JSON(http.StatusBadRequest, gin.H{"error": "code is incorrect."}) +// return +// } +// +// admin := map[string]string{ +// "kode": input.Kode, +// } +// +// c.JSON(http.StatusOK, gin.H{"message": "login success", "user": admin, "token": token}) +//} diff --git a/app/controllers/memberController.go b/app/controllers/memberController.go deleted file mode 100644 index ffb944a..0000000 --- a/app/controllers/memberController.go +++ /dev/null @@ -1,158 +0,0 @@ -package controllers - -import ( - "backend/app/models" - "backend/app/services" - "backend/utils" - "fmt" - "github.com/gin-gonic/gin" - "gorm.io/gorm" - "net/http" -) - -type MemberController struct { - DB *gorm.DB - Service *services.MemberService -} - -type MembersResponse struct { - ID uint `gorm:"primaryKey"` - Name string `json:"name"` - Email string `json:"email" gorm:"unique"` - Password string `json:"password" gorm:"-"` - Role Role `json:"role" gorm:"type:enum('admin', 'member', 'it'); default:'member'"` -} - -type Role string - -func (mc *MemberController) GetMembers(ctx *gin.Context) { - members, err := mc.Service.GetMembers() - if err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get members"}) - return - } - - var response []MembersResponse - for _, member := range members { - response = append(response, MembersResponse{ - ID: member.ID, - Name: member.Name, - Email: member.Email, - Role: Role(member.Role), - }) - } - ctx.JSON(http.StatusOK, gin.H{"data": response}) -} - -func (mc *MemberController) GetMemberById(ctx *gin.Context) { - memberId := ctx.Param("id") - member, err := mc.Service.GetMemberById(memberId) - if err != nil { - ctx.JSON(http.StatusNotFound, gin.H{"error": "Failed to get member"}) - return - } - ctx.JSON(http.StatusOK, gin.H{"data": member}) -} - -func (mc *MemberController) CreateMember(ctx *gin.Context) { - // Struktur untuk input JSON - var input struct { - Name string `json:"name" binding:"required"` - Email string `json:"email" binding:"required,email"` - Password string `json:"password" binding:"required"` - Role Role `json:"role" gorm:"type:enum('admin', 'member', 'it'); default:'member'"` - } - - if err := ctx.ShouldBindJSON(&input); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) - return - } - - //validRoles := map[Role]bool{"admin": true, "member": true, "it": true} - //if !validRoles[input.Role] { - // ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) - // return - //} - - member := models.Members{ - Name: input.Name, - Email: input.Email, - Password: input.Password, - Role: models.Role(input.Role), - } - - validationErrors := utils.ValidateStruct(member) - if len(validationErrors) > 0 { - ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) - return - } - - if err := mc.Service.CreateMember(&member); err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save member to database", "details": err.Error()}) - return - } - - ctx.JSON(http.StatusCreated, gin.H{"message": "Member created successfully"}) -} - -func (mc *MemberController) UpdateMember(ctx *gin.Context) { - memberId := ctx.Param("id") - member, err := mc.Service.GetMemberById(memberId) - if err != nil { - ctx.JSON(http.StatusNotFound, gin.H{"error": "Member not found"}) - return - } - - var input struct { - Name *string `json:"name" binding:"omitempty"` - Email *string `json:"email" binding:"omitempty,email"` - Password *string `json:"password" binding:"omitempty"` - Role *Role `json:"role" binding:"omitempty"` - } - - if err := ctx.ShouldBindJSON(&input); err != nil { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) - return - } - - if input.Name != nil { - member.Name = *input.Name - } - if input.Email != nil { - member.Email = *input.Email - } - if input.Password != nil { - member.Password = *input.Password - } - - if input.Role != nil { - validRoles := map[Role]bool{"admin": true, "member": true, "it": true} - if !validRoles[*input.Role] { - ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) - return - } - member.Role = models.Role(*input.Role) - } - - validationErrors := utils.ValidateStruct(member) - if len(validationErrors) > 0 { - ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) - return - } - - if err := mc.Service.UpdateMember(member); err != nil { - ctx.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to update member to database", - "details": err.Error(), - }) - return - } - - response := MembersResponse{ - ID: member.ID, - Name: member.Name, - Email: member.Email, - Role: Role(member.Role), - } - ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Member %s updated successfully", memberId), "data": response}) -} diff --git a/app/controllers/userController.go b/app/controllers/userController.go new file mode 100644 index 0000000..9395e04 --- /dev/null +++ b/app/controllers/userController.go @@ -0,0 +1,189 @@ +package controllers + +import ( + "backend/app/models" + "backend/app/services" + "backend/utils" + "fmt" + "github.com/gin-gonic/gin" + "gorm.io/gorm" + "net/http" + "strings" +) + +type UserController struct { + DB *gorm.DB + Service *services.UserService +} + +type UserResponse struct { + ID uint `gorm:"primaryKey"` + Name string `json:"name"` + Email string `json:"email" gorm:"unique"` + Password string `json:"password" gorm:"-"` + Role Role `json:"role" gorm:"type:enum('admin', 'user', 'it'); default:'user'"` +} + +type Role string + +func (mc *UserController) GetUsers(ctx *gin.Context) { + users, err := mc.Service.GetUsers() + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get users"}) + return + } + + var response []UserResponse + for _, user := range users { + response = append(response, UserResponse{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: Role(user.Role), + }) + } + ctx.JSON(http.StatusOK, gin.H{"data": response}) +} + +func (mc *UserController) GetUserById(ctx *gin.Context) { + userId := ctx.Param("id") + user, err := mc.Service.GetUserById(userId) + if err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "Failed to get user"}) + return + } + ctx.JSON(http.StatusOK, gin.H{"data": user}) +} + +func (mc *UserController) CreateUser(ctx *gin.Context) { + // Struktur untuk input JSON + var input struct { + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + Role string `json:"role"` + } + + if err := ctx.ShouldBindJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) + return + } + + validRoles := map[string]bool{"admin": true, "member": true, "tim_it": true} + if input.Role != "" && !validRoles[input.Role] { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) + return + } + + hashedPassword, err := utils.HashPassword(input.Password) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password", "details": err.Error()}) + return + } + + user := models.User{ + Name: input.Name, + Email: strings.ToLower(input.Email), + Password: hashedPassword, + Role: models.Role(input.Role), + } + + validationErrors := utils.ValidateStruct(user) + if len(validationErrors) > 0 { + ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) + return + } + + if err := mc.Service.CreateUser(&user); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save user to database", "details": err.Error()}) + return + } + + ctx.JSON(http.StatusCreated, gin.H{ + "message": "User created successfully", + "user": gin.H{ + "id": user.ID, + "name": user.Name, + "email": user.Email, + "role": user.Role, + }, + }) +} + +func (mc *UserController) UpdateUser(ctx *gin.Context) { + userId := ctx.Param("id") + user, err := mc.Service.GetUserById(userId) + if err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + var input struct { + Name *string `json:"name" binding:"omitempty"` + Email *string `json:"email" binding:"omitempty,email"` + Password *string `json:"password" binding:"omitempty"` + Role *Role `json:"role" binding:"omitempty"` + } + + if err := ctx.ShouldBindJSON(&input); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input", "details": err.Error()}) + return + } + + if input.Name != nil { + user.Name = *input.Name + } + if input.Email != nil { + user.Email = *input.Email + } + if input.Password != nil { + user.Password = *input.Password + } + + if input.Role != nil { + validRoles := map[Role]bool{"admin": true, "user": true, "it": true} + if !validRoles[*input.Role] { + ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) + return + } + user.Role = models.Role(*input.Role) + } + + validationErrors := utils.ValidateStruct(user) + if len(validationErrors) > 0 { + ctx.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) + return + } + + if err := mc.Service.UpdateUser(user); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to update user to database", + "details": err.Error(), + }) + return + } + + response := UserResponse{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: Role(user.Role), + } + ctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("User ID: %s updated successfully", userId), "data": response}) +} + +func (mc *UserController) DeleteUser(ctx *gin.Context) { + userId := ctx.Param("id") + + _, err := mc.Service.GetUserById(userId) + if err != nil { + ctx.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + if err := mc.Service.DeleteUser(userId); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete user", "details": err.Error()}) + return + } + ctx.JSON(http.StatusOK, gin.H{"message": "User deleted successfully", "data": nil}) +} diff --git a/app/models/members.go b/app/models/members.go deleted file mode 100644 index 07ce174..0000000 --- a/app/models/members.go +++ /dev/null @@ -1,49 +0,0 @@ -package models - -import ( - "errors" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -type Members struct { - ID uint `json:"id" gorm:"primaryKey;autoIncrement:true"` - Name string `json:"name"` - Email string `json:"email" gorm:"unique"` - Password string `json:"-"` - Role Role `json:"role" gorm:"type:enum('superAdmin' ,'admin', 'member', 'it'); default:'member'"` -} -type Role string - -const ( - RoleAdmin Role = "admin" - RoleMember Role = "member" - RoleIT Role = "it" -) - -func (r Role) IsValid() bool { - switch r { - case RoleAdmin, RoleMember, RoleIT: - return true - } - return true -} - -func (m *Members) BeforeSave(*gorm.DB) (err error) { - if m.Name == "" || m.Email == "" || m.Password == "" { - return errors.New("name, email, and password are required") - } - - if !m.Role.IsValid() { - m.Role = RoleMember - } - - if m.Password != "" { - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(m.Password), bcrypt.DefaultCost) - if err != nil { - return err - } - m.Password = string(hashedPassword) - } - return nil -} diff --git a/app/models/user.go b/app/models/user.go index 7f9a9df..da722dd 100644 --- a/app/models/user.go +++ b/app/models/user.go @@ -1,40 +1,27 @@ package models -import ( - "backend/utils/token" - "errors" - - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -// User - model untuk user type User struct { - ID uint `gorm:"primaryKey;autoIncrement:true"` - Role string `json:"role"` - Code string `json:"code"` + ID uint `json:"id" gorm:"primaryKey;autoIncrement:true"` + Name string `json:"name" binding:"required"` + Email string `json:"email" binding:"required,email" gorm:"unique"` + Password string `json:"-" binding:"required"` + Role Role `json:"role" gorm:"type:enum('super_admin' ,'admin', 'member', 'tim_it');default:'member'"` } -// GenerateJWT - generate token JWT untuk user -func (u *User) GenerateJWT() (string, error) { - if u.ID == 0 || u.Role == "" { - return "", errors.New("invalid user data for token generation") - } - return token.GenerateToken(u.ID, u.Role) -} +type Role string -// LoginCheckAdmin - verifikasi kode admin -func (u *User) LoginCheckAdmin(db *gorm.DB) (string, error) { - var admin User - if err := db.Where("code = ?", u.Code).First(&admin).Error; err != nil { - return "", errors.New("admin not found") - } +const ( + RoleSuperAdmin Role = "super_admin" + RoleAdmin Role = "admin" + RoleMember Role = "member" + RoleIT Role = "tim_it" +) - // Validasi kode dengan bcrypt - if err := bcrypt.CompareHashAndPassword([]byte(admin.Code), []byte(u.Code)); err != nil { - return "", errors.New("invalid code") +func (r Role) IsValid() bool { + switch r { + case RoleSuperAdmin, RoleAdmin, RoleMember, RoleIT: + return true + default: + return false } - - // Generate token JWT - return admin.GenerateJWT() } diff --git a/app/repositories/members_repository.go b/app/repositories/members_repository.go index 3dc57e7..f80caa4 100644 --- a/app/repositories/members_repository.go +++ b/app/repositories/members_repository.go @@ -5,28 +5,32 @@ import ( "gorm.io/gorm" ) -type MembersRepository struct { +type UsersRepository struct { DB *gorm.DB } -func (mr *MembersRepository) GetMembers() ([]models.Members, error) { - var members []models.Members - if err := mr.DB.Find(&members).Error; err != nil { +func (mr *UsersRepository) GetUsers() ([]models.User, error) { + var Users []models.User + if err := mr.DB.Find(&Users).Error; err != nil { return nil, err } - return members, nil + return Users, nil } -func (mr *MembersRepository) GetMembersById(id string) (*models.Members, error) { - var member models.Members - err := mr.DB.Where("id = ?", id).First(&member).Error - return &member, err +func (mr *UsersRepository) GetUsersById(id string) (*models.User, error) { + var user models.User + err := mr.DB.Where("id = ?", id).First(&user).Error + return &user, err } -func (mr *MembersRepository) CreateMembers(members *models.Members) error { - return mr.DB.Create(members).Error +func (mr *UsersRepository) CreateUsers(Users *models.User) error { + return mr.DB.Create(Users).Error } -func (mr *MembersRepository) UpdateMembers(members *models.Members) error { - return mr.DB.Save(members).Error +func (mr *UsersRepository) UpdateUser(Users *models.User) error { + return mr.DB.Save(Users).Error +} + +func (mr *UsersRepository) DeleteUser(id string) error { + return mr.DB.Where("id = ?", id).Delete(&models.User{}).Error } diff --git a/app/services/auth_service.go b/app/services/auth_service.go deleted file mode 100644 index e3a83f6..0000000 --- a/app/services/auth_service.go +++ /dev/null @@ -1,30 +0,0 @@ -package services - -import ( - "backend/app/models" - "backend/utils/token" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -func LoginCheckAdmin(db *gorm.DB, code string) (string, error) { - var err error - - adminFromDB := models.User{} - err = db.Model(&models.User{}).Where("code = ?", code).Take(&adminFromDB).Error - if err != nil { - return "", err - } - - err = bcrypt.CompareHashAndPassword([]byte(adminFromDB.Code), []byte(code)) - if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { - return "", err - } - - token, err := token.GenerateToken(adminFromDB.ID, adminFromDB.Role) - if err != nil { - return "", err - } - - return token, nil -} diff --git a/app/services/member_service.go b/app/services/member_service.go deleted file mode 100644 index 55497bd..0000000 --- a/app/services/member_service.go +++ /dev/null @@ -1,26 +0,0 @@ -package services - -import ( - "backend/app/models" - "backend/app/repositories" -) - -type MemberService struct { - Repo *repositories.MembersRepository -} - -func (ms *MemberService) GetMembers() ([]models.Members, error) { - return ms.Repo.GetMembers() -} - -func (ms *MemberService) GetMemberById(id string) (*models.Members, error) { - return ms.Repo.GetMembersById(id) -} - -func (ms *MemberService) CreateMember(member *models.Members) error { - return ms.Repo.CreateMembers(member) -} - -func (ms *MemberService) UpdateMember(member *models.Members) error { - return ms.Repo.UpdateMembers(member) -} diff --git a/app/services/user_service.go b/app/services/user_service.go new file mode 100644 index 0000000..9e9c11f --- /dev/null +++ b/app/services/user_service.go @@ -0,0 +1,30 @@ +package services + +import ( + "backend/app/models" + "backend/app/repositories" +) + +type UserService struct { + Repo *repositories.UsersRepository +} + +func (ms *UserService) GetUsers() ([]models.User, error) { + return ms.Repo.GetUsers() +} + +func (ms *UserService) GetUserById(id string) (*models.User, error) { + return ms.Repo.GetUsersById(id) +} + +func (ms *UserService) CreateUser(user *models.User) error { + return ms.Repo.CreateUsers(user) +} + +func (ms *UserService) UpdateUser(user *models.User) error { + return ms.Repo.UpdateUser(user) +} + +func (ms *UserService) DeleteUser(id string) error { + return ms.Repo.DeleteUser(id) +} diff --git a/config/automigrate.go b/config/automigrate.go index f8c7ff9..962d0bf 100644 --- a/config/automigrate.go +++ b/config/automigrate.go @@ -13,8 +13,8 @@ func AutoMigrate(db *gorm.DB) { &models.Achievement{}, &models.Activities{}, &models.News{}, + //&models.User{}, &models.User{}, - &models.Members{}, ) if err != nil { log.Fatal("Failed to auto migrate: ", err) diff --git a/routes/auth_routes.go b/routes/auth_routes.go index a8a29ff..c021c01 100644 --- a/routes/auth_routes.go +++ b/routes/auth_routes.go @@ -1,11 +1,22 @@ package routes import ( - "backend/app/controllers" + "backend/app/controllers/authentication" "github.com/gin-gonic/gin" "gorm.io/gorm" ) -func RegisterAuthRoutes(r *gin.Engine, db *gorm.DB) { - r.POST("/login-admin", controllers.LoginAdmin) +func RegisterAuthRouts(r *gin.Engine, db *gorm.DB) { + authController := &authentication.AuthController{DB: db} + + authGroup := r.Group("/auth") + { + authGroup.POST("/login", authController.Login) + authGroup.POST("/register", authController.Register) + } } + +//OLD +//func RegisterAuthRoutes(r *gin.Engine, db *gorm.DB) { +// r.POST("/login-admin", controllers.LoginAdmin) +//} diff --git a/routes/init_routes.go b/routes/init_routes.go index 67847ba..023180f 100644 --- a/routes/init_routes.go +++ b/routes/init_routes.go @@ -25,11 +25,11 @@ func InitRouter(db *gorm.DB) *gin.Engine { // Jika bukan GET , Cek token dulu - RegisterAuthRoutes(r, db) + RegisterAuthRouts(r, db) RegisterActivityRoutes(r, db) RegisterNewsRoutes(r, db) RegisterAchievementRoutes(r, db) - RegisterMemberRoutes(r, db) + RegisterUserRoutes(r, db) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("/swagger/doc.json"))) r.Static("/uploads", "./uploads") diff --git a/routes/members.go b/routes/members.go deleted file mode 100644 index 9d51ca4..0000000 --- a/routes/members.go +++ /dev/null @@ -1,23 +0,0 @@ -package routes - -import ( - "backend/app/controllers" - "backend/app/repositories" - "backend/app/services" - "github.com/gin-gonic/gin" - "gorm.io/gorm" -) - -func RegisterMemberRoutes(r *gin.Engine, db *gorm.DB) { - memberRepo := &repositories.MembersRepository{DB: db} - memberService := &services.MemberService{Repo: memberRepo} - memberController := &controllers.MemberController{Service: memberService} - - r.GET("/members", memberController.GetMembers) - r.GET("member/:id", memberController.GetMemberById) - - authGroup := AuthGroup(r) - authGroup.POST("/members", memberController.CreateMember) - authGroup.PATCH("/member/:id", memberController.UpdateMember) - authGroup.DELETE("/member/:id") -} diff --git a/routes/users.go b/routes/users.go new file mode 100644 index 0000000..81cdc4a --- /dev/null +++ b/routes/users.go @@ -0,0 +1,23 @@ +package routes + +import ( + "backend/app/controllers" + "backend/app/repositories" + "backend/app/services" + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func RegisterUserRoutes(r *gin.Engine, db *gorm.DB) { + userRepo := &repositories.UsersRepository{DB: db} + userService := &services.UserService{Repo: userRepo} + userController := &controllers.UserController{Service: userService} + + r.GET("/users", userController.GetUsers) + r.GET("user/:id", userController.GetUserById) + + authGroup := AuthGroup(r) + authGroup.POST("/users", userController.CreateUser) + authGroup.PATCH("/user/:id", userController.UpdateUser) + authGroup.DELETE("/user/:id", userController.DeleteUser) +} diff --git a/utils/hash.go b/utils/hash.go new file mode 100644 index 0000000..6457105 --- /dev/null +++ b/utils/hash.go @@ -0,0 +1,15 @@ +package utils + +import "golang.org/x/crypto/bcrypt" + +func HashPassword(password string) (string, error) { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedPassword), nil +} + +func CheckPassword(hashedPassword, password string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +}