Compare commits

...

5 Commits

  1. 2
      .env
  2. 180
      controllers/usersController.go
  3. BIN
      go-crud.exe
  4. BIN
      go-crud.exe~
  5. 10
      initializers/syncDatabase.go
  6. 5
      main.go
  7. 10
      middleware/requireAuth.go
  8. 15
      migrate/migrate.go
  9. 14
      models/recordData.go
  10. 26
      models/userModel.go

2
.env

@ -1,3 +1,3 @@
PORT=8000 PORT=1000
SECRET=sdc87sdf6gv87df6v8sd7f6v8sdf76v8sdf7v6 SECRET=sdc87sdf6gv87df6v8sd7f6v8sdf76v8sdf7v6
DB_URL="host=dumbo.db.elephantsql.com user=yvvpeiee password=QwAhcTtiKL8j9qRyUBIqs9kNbu3O0_FB dbname=yvvpeiee port=5432 sslmode=disable" DB_URL="host=dumbo.db.elephantsql.com user=yvvpeiee password=QwAhcTtiKL8j9qRyUBIqs9kNbu3O0_FB dbname=yvvpeiee port=5432 sslmode=disable"

180
controllers/usersController.go

@ -1,8 +1,11 @@
package controllers package controllers
import ( import (
"fmt"
"net/http" "net/http"
"os" "os"
"strconv"
"strings"
"time" "time"
"go-crud/initializers" "go-crud/initializers"
@ -11,32 +14,53 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
) )
func SignUp(c *gin.Context) { func SignUp(c *gin.Context) {
// Get the email/pass off req body
var body struct { var body struct {
Email string Email string
Password string Password string
Nik string
Name string
Photo string
Birthdate time.Time
JobTitle string
CreatedBy string
UpdatedBy string
DeletedBy string
} }
if err := c.ShouldBind(&body); err != nil {
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to read body", "error": "Failed to read body",
}) })
return return
} }
// Hash the password
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10) hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to generate pass hash", "error": "Failed to generate password hash",
}) })
return return
} }
// Create the user user := models.User{
user := models.User{Email: body.Email, Password: string(hash)} Email: body.Email,
Password: string(hash),
Nik: body.Nik,
Name: body.Name,
Photo: body.Photo,
Birthdate: body.Birthdate,
JobTitle: body.JobTitle,
LastLoginAt: time.Now(),
CreatedBy: body.Nik,
UpdatedBy: body.Nik,
DeletedBy: body.Nik,
Token: "",
}
result := initializers.DB.Create(&user) result := initializers.DB.Create(&user)
if result.Error != nil { if result.Error != nil {
@ -46,52 +70,49 @@ func SignUp(c *gin.Context) {
return return
} }
// Respond updateLoginRecord(c, user.ID, time.Now())
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
} }
func LogIn(c *gin.Context) { func LogIn(c *gin.Context) {
// Get the email/pass off req body
var body struct { var body struct {
Email string Email string
Password string Password string
} }
if err := c.ShouldBind(&body); err != nil { if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to read body", "error": "Failed to read body",
}) })
return return
} }
// Look up requested user
var user models.User var user models.User
initializers.DB.First(&user, "email = ?", body.Email) initializers.DB.First(&user, "email = ?", body.Email)
if user.ID == 0 { if user.ID == 0 {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid email or pass", "error": "Invalid email or password",
}) })
return return
} }
// Compare sent in pass with saved user pass hash
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password)) err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid email or pass", "error": "Invalid email or password",
}) })
return return
} }
// Generate a jwt token user.LastLoginAt = time.Now()
initializers.DB.Save(&user)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": user.ID, "sub": user.ID,
"exp": time.Now().Add(time.Hour * 24 * 30).Unix(), "exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
}) })
// Sign and get the complete encoded token as a string using the secret token
tokenString, err := token.SignedString([]byte(os.Getenv("SECRET"))) tokenString, err := token.SignedString([]byte(os.Getenv("SECRET")))
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": "Failed to create token", "error": "Failed to create token",
@ -99,12 +120,22 @@ func LogIn(c *gin.Context) {
return return
} }
// Send it back
c.SetSameSite(http.SameSiteLaxMode) c.SetSameSite(http.SameSiteLaxMode)
c.SetCookie("Authorization", tokenString, 3600*24*30, "", "", false, true) c.SetCookie("Authorization", tokenString, 3600*24*30, "", "", false, true)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"token": tokenString, "token": tokenString,
}) })
updateLoginRecord(c, user.ID, time.Now())
}
func LogOut(c *gin.Context) {
c.SetCookie("Authorization", "", -1, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{
"message": "Logout successful",
})
} }
func Validate(c *gin.Context) { func Validate(c *gin.Context) {
@ -114,3 +145,112 @@ func Validate(c *gin.Context) {
"message": user, "message": user,
}) })
} }
func updateLoginRecord(c *gin.Context, userID uint, date time.Time) {
// Cari data rekap login berdasarkan tanggal
var loginRecord models.LoginRecord
result := initializers.DB.Where("login_date = ?", date.Format("2006-01-02")).First(&loginRecord)
if result.Error != nil && result.Error != gorm.ErrRecordNotFound {
return
}
if result.Error == gorm.ErrRecordNotFound {
// Jika belum ada data rekap untuk hari ini, buat data baru
loginRecord = models.LoginRecord{
LoginDate: date,
UserIDs: fmt.Sprintf("%d", userID),
}
initializers.DB.Create(&loginRecord)
} else {
// Jika sudah ada data rekap, cek apakah user sudah login pada hari ini
if !containsInt(loginRecord.UserIDs, userID) {
// Jika user belum login hari ini, tambahkan ID pengguna ke dalam array user_ids
loginRecord.UserIDs = fmt.Sprintf("%s,%d", loginRecord.UserIDs, userID)
initializers.DB.Save(&loginRecord)
}
}
// Ambil data pengguna yang login pada hari ini
var users []models.User
initializers.DB.Find(&users, "id IN (?)", parseUserIDs(loginRecord.UserIDs))
// Update login record dengan pengguna yang login pada hari ini
loginRecord.Users = users
initializers.DB.Save(&loginRecord)
// Update LastLoginAt pada tabel User jika dalam satu hari user melakukan login kembali
user := models.User{}
initializers.DB.First(&user, userID)
user.LastLoginAt = date
initializers.DB.Save(&user)
}
// Helper function to check if an int is present in a slice
func containsInt(slice string, val uint) bool {
ids := parseUserIDs(slice)
for _, item := range ids {
if item == val {
return true
}
}
return false
}
// Helper function to parse user IDs from a string
func parseUserIDs(idsStr string) []uint {
ids := strings.Split(idsStr, ",")
var userIDs []uint
for _, idStr := range ids {
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
continue
}
userIDs = append(userIDs, uint(id))
}
return userIDs
}
func GetLoginRecords(c *gin.Context) {
var loginRecords []models.LoginRecord
result := initializers.DB.Find(&loginRecords)
if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": http.StatusInternalServerError,
"data": nil,
"message": "Failed to fetch login records",
"status": false,
"totalRecord": 0,
})
return
}
// Retrieve user details for each login record
for i := range loginRecords {
var users []models.User
initializers.DB.Find(&users, "id IN (?)", parseUserIDs(loginRecords[i].UserIDs))
loginRecords[i].Users = users
}
// Create JSON response
var response struct {
Code int `json:"code"`
Data []models.LoginRecord `json:"data"`
Message string `json:"message"`
Status bool `json:"status"`
TotalRecord int `json:"totalRecord"`
}
response.Code = http.StatusOK
response.Data = loginRecords
response.Message = "OK"
response.Status = true
response.TotalRecord = len(loginRecords)
c.JSON(http.StatusOK, response)
}

BIN
go-crud.exe

Binary file not shown.

BIN
go-crud.exe~

Binary file not shown.

10
initializers/syncDatabase.go

@ -0,0 +1,10 @@
package initializers
import "go-crud/models"
func SyncDatabase() {
DB.AutoMigrate(&models.Post{})
DB.AutoMigrate(&models.Event{})
DB.AutoMigrate(&models.User{})
DB.AutoMigrate(&models.LoginRecord{})
}

5
main.go

@ -11,14 +11,19 @@ import (
func init() { func init() {
initializers.LoadEnvVariables() initializers.LoadEnvVariables()
initializers.ConnectToDB() initializers.ConnectToDB()
initializers.SyncDatabase()
} }
func main() { func main() {
r := gin.Default() r := gin.Default()
r.POST("/signup", controllers.SignUp) r.POST("/signup", controllers.SignUp)
r.POST("/login", controllers.LogIn) r.POST("/login", controllers.LogIn)
r.GET("/login-records", controllers.GetLoginRecords)
r.GET("/validate", middleware.RequireAuth, controllers.Validate) r.GET("/validate", middleware.RequireAuth, controllers.Validate)
r.Use(middleware.RequireAuth)
r.POST("/logout", controllers.LogOut)
r.POST("/posts", controllers.PostsCreate) r.POST("/posts", controllers.PostsCreate)
r.PUT("/posts/:id", controllers.PostsUpdate) r.PUT("/posts/:id", controllers.PostsUpdate)
r.GET("/posts", controllers.PostsIndex) r.GET("/posts", controllers.PostsIndex)

10
middleware/requireAuth.go

@ -52,11 +52,21 @@ func RequireAuth(c *gin.Context) {
var user models.User var user models.User
initializers.DB.First(&user, claims["sub"]) initializers.DB.First(&user, claims["sub"])
var recordData models.LoginRecord
initializers.DB.First(&recordData, claims["sub"])
var post models.Post
initializers.DB.First(&post, claims["sub"])
var event models.Event
initializers.DB.First(&event, claims["sub"])
if user.ID == 0 { if user.ID == 0 {
c.AbortWithStatus(http.StatusUnauthorized) c.AbortWithStatus(http.StatusUnauthorized)
return return
} }
// Attach to req // Attach to req
c.Set("user", user) c.Set("user", user)

15
migrate/migrate.go

@ -1,15 +0,0 @@
package main
import "go-crud/initializers"
import "go-crud/models"
func init() {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
}
func main() {
initializers.DB.AutoMigrate(&models.Post{})
initializers.DB.AutoMigrate(&models.Event{})
initializers.DB.AutoMigrate(&models.User{})
}

14
models/recordData.go

@ -0,0 +1,14 @@
package models
import (
"time"
"gorm.io/gorm"
)
type LoginRecord struct {
gorm.Model
LoginDate time.Time
UserIDs string `gorm:"type:text"`
Users []User `json:"users" gorm:"-"`
}

26
models/userModel.go

@ -1,9 +1,29 @@
package models package models
import "gorm.io/gorm" import (
"time"
"gorm.io/gorm"
)
type User struct { type User struct {
gorm.Model gorm.Model
Email string `gorm:"unique"` Nik string
Password string Name string
Photo string
Birthdate time.Time
JobTitle string
Email string `gorm:"unique"`
LastLoginAt time.Time
CreatedBy string
UpdatedBy string
DeletedBy string
Token string
Username string
RoleID int
Password string
} }

Loading…
Cancel
Save