|
|
@ -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,51 @@ 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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateLoginRecord(c, user.ID, time.Now()) |
|
|
|
|
|
|
|
|
|
|
|
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,7 +122,6 @@ 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{ |
|
|
@ -108,11 +130,8 @@ func LogIn(c *gin.Context) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func LogOut(c *gin.Context) { |
|
|
|
func LogOut(c *gin.Context) { |
|
|
|
// Hapus informasi autentikasi atau sesi dari pengguna (misalnya token, cookie, atau data sesi)
|
|
|
|
|
|
|
|
// Contoh: Hapus cookie autentikasi
|
|
|
|
|
|
|
|
c.SetCookie("Authorization", "", -1, "/", "", false, true) |
|
|
|
c.SetCookie("Authorization", "", -1, "/", "", false, true) |
|
|
|
|
|
|
|
|
|
|
|
// Berikan respons berhasil logout
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
|
c.JSON(http.StatusOK, gin.H{ |
|
|
|
"message": "Logout successful", |
|
|
|
"message": "Logout successful", |
|
|
|
}) |
|
|
|
}) |
|
|
@ -125,3 +144,97 @@ 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, |
|
|
|
|
|
|
|
TotalUsers: 1, |
|
|
|
|
|
|
|
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.TotalUsers++ |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update jumlah pengguna (TotalUsers) saat pengguna baru melakukan login
|
|
|
|
|
|
|
|
loginRecord.TotalUsers = len(users) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
initializers.DB.Save(&loginRecord) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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{ |
|
|
|
|
|
|
|
"error": "Failed to fetch login records", |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
|
|
Records []models.LoginRecord `json:"records"` |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
response.Records = loginRecords |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, response) |
|
|
|
|
|
|
|
} |
|
|
|