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/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/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/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 new file mode 100644 index 0000000..f80caa4 --- /dev/null +++ b/app/repositories/members_repository.go @@ -0,0 +1,36 @@ +package repositories + +import ( + "backend/app/models" + "gorm.io/gorm" +) + +type UsersRepository struct { + DB *gorm.DB +} + +func (mr *UsersRepository) GetUsers() ([]models.User, error) { + var Users []models.User + if err := mr.DB.Find(&Users).Error; err != nil { + return nil, err + } + return Users, nil +} + +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 *UsersRepository) CreateUsers(Users *models.User) error { + return mr.DB.Create(Users).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/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 f199114..962d0bf 100644 --- a/config/automigrate.go +++ b/config/automigrate.go @@ -13,6 +13,7 @@ func AutoMigrate(db *gorm.DB) { &models.Achievement{}, &models.Activities{}, &models.News{}, + //&models.User{}, &models.User{}, ) if err != nil { 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 8a866d7..023180f 100644 --- a/routes/init_routes.go +++ b/routes/init_routes.go @@ -25,10 +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) + RegisterUserRoutes(r, db) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("/swagger/doc.json"))) r.Static("/uploads", "./uploads") 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)) +}