Initial commit: 商品售卖网站
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AddressHandler struct{}
|
||||
|
||||
func NewAddressHandler() *AddressHandler {
|
||||
return &AddressHandler{}
|
||||
}
|
||||
|
||||
func (h *AddressHandler) List(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var addresses []models.Address
|
||||
utils.DB.Where("user_id = ?", userID).Order("is_default DESC, created_at DESC").Find(&addresses)
|
||||
c.JSON(http.StatusOK, gin.H{"data": addresses})
|
||||
}
|
||||
|
||||
func (h *AddressHandler) Create(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.CreateAddressRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.IsDefault {
|
||||
utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false)
|
||||
}
|
||||
|
||||
address := models.Address{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Phone: req.Phone,
|
||||
Province: req.Province,
|
||||
City: req.City,
|
||||
District: req.District,
|
||||
Address: req.Address,
|
||||
IsDefault: req.IsDefault,
|
||||
}
|
||||
|
||||
utils.DB.Create(&address)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": address})
|
||||
}
|
||||
|
||||
func (h *AddressHandler) Update(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var address models.Address
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Address not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateAddressRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Phone != nil {
|
||||
updates["phone"] = *req.Phone
|
||||
}
|
||||
if req.Province != nil {
|
||||
updates["province"] = *req.Province
|
||||
}
|
||||
if req.City != nil {
|
||||
updates["city"] = *req.City
|
||||
}
|
||||
if req.District != nil {
|
||||
updates["district"] = *req.District
|
||||
}
|
||||
if req.Address != nil {
|
||||
updates["address"] = *req.Address
|
||||
}
|
||||
if req.IsDefault != nil && *req.IsDefault {
|
||||
utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false)
|
||||
updates["is_default"] = true
|
||||
}
|
||||
|
||||
utils.DB.Model(&address).Updates(updates)
|
||||
c.JSON(http.StatusOK, gin.H{"data": address})
|
||||
}
|
||||
|
||||
func (h *AddressHandler) Delete(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Address{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete address"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Address deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *AddressHandler) SetDefault(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var address models.Address
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Address not found"})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false)
|
||||
utils.DB.Model(&address).Update("is_default", true)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Default address set successfully"})
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ArticleHandler struct{}
|
||||
|
||||
func NewArticleHandler() *ArticleHandler {
|
||||
return &ArticleHandler{}
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
var total int64
|
||||
utils.DB.Model(&models.Article{}).Where("is_published = ?", true).Count(&total)
|
||||
|
||||
var articles []models.Article
|
||||
offset := (page - 1) * pageSize
|
||||
utils.DB.Where("is_published = ?", true).
|
||||
Order("is_pinned DESC, sort_order ASC, created_at DESC").
|
||||
Offset(offset).Limit(pageSize).Find(&articles)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": articles,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) GetByID(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var article models.Article
|
||||
if err := utils.DB.Preload("Author").First(&article, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": article})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) AdminList(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
var total int64
|
||||
utils.DB.Model(&models.Article{}).Count(&total)
|
||||
|
||||
var articles []models.Article
|
||||
offset := (page - 1) * pageSize
|
||||
utils.DB.Preload("Author").
|
||||
Order("is_pinned DESC, sort_order ASC, created_at DESC").
|
||||
Offset(offset).Limit(pageSize).Find(&articles)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": articles,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) Create(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.CreateArticleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
article := models.Article{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Summary: req.Summary,
|
||||
CoverImage: req.CoverImage,
|
||||
IsPinned: req.IsPinned,
|
||||
IsPublished: req.IsPublished,
|
||||
SortOrder: req.SortOrder,
|
||||
AuthorID: &userID,
|
||||
}
|
||||
|
||||
utils.DB.Create(&article)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": article})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var article models.Article
|
||||
if err := utils.DB.First(&article, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateArticleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Title != nil {
|
||||
updates["title"] = *req.Title
|
||||
}
|
||||
if req.Content != nil {
|
||||
updates["content"] = *req.Content
|
||||
}
|
||||
if req.Summary != nil {
|
||||
updates["summary"] = *req.Summary
|
||||
}
|
||||
if req.CoverImage != nil {
|
||||
updates["cover_image"] = *req.CoverImage
|
||||
}
|
||||
if req.IsPinned != nil {
|
||||
updates["is_pinned"] = *req.IsPinned
|
||||
}
|
||||
if req.IsPublished != nil {
|
||||
updates["is_published"] = *req.IsPublished
|
||||
}
|
||||
if req.SortOrder != nil {
|
||||
updates["sort_order"] = *req.SortOrder
|
||||
}
|
||||
|
||||
utils.DB.Model(&article).Updates(updates)
|
||||
c.JSON(http.StatusOK, gin.H{"data": article})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if err := utils.DB.Delete(&models.Article{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete article"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Article deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *ArticleHandler) TogglePin(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var article models.Article
|
||||
if err := utils.DB.First(&article, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&article).Update("is_pinned", !article.IsPinned)
|
||||
c.JSON(http.StatusOK, gin.H{"data": article})
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthHandler struct{}
|
||||
|
||||
func NewAuthHandler() *AuthHandler {
|
||||
return &AuthHandler{}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req schemas.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var existingUser models.User
|
||||
if err := utils.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "Username already taken"})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
PasswordHash: hashedPassword,
|
||||
Role: "user",
|
||||
InviteCode: utils.GenerateInviteCode(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if req.InviteCode != "" {
|
||||
var referrer models.User
|
||||
if err := utils.DB.Where("invite_code = ?", req.InviteCode).First(&referrer).Error; err == nil {
|
||||
user.ReferredBy = &referrer.ID
|
||||
}
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
|
||||
return
|
||||
}
|
||||
|
||||
if user.ReferredBy != nil {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", *user.ReferredBy).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + 1"))
|
||||
}
|
||||
|
||||
verifyCode := utils.GenerateVerifyCode()
|
||||
utils.SendVerifyEmail(req.Email, verifyCode)
|
||||
|
||||
token, _ := utils.GenerateToken(user.ID, user.Role)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"token": token,
|
||||
"user": schemas.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
PurchaseCredits: user.PurchaseCredits,
|
||||
InviteCode: user.InviteCode,
|
||||
EmailVerified: user.EmailVerified,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req schemas.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CheckPassword(req.Password, user.PasswordHash) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"})
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsActive {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"})
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := utils.GenerateToken(user.ID, user.Role)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": token,
|
||||
"user": schemas.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
PurchaseCredits: user.PurchaseCredits,
|
||||
InviteCode: user.InviteCode,
|
||||
EmailVerified: user.EmailVerified,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ForgotPassword(c *gin.Context) {
|
||||
var req schemas.ForgotPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
|
||||
return
|
||||
}
|
||||
|
||||
code := utils.GenerateVerifyCode()
|
||||
utils.SendResetPasswordEmail(req.Email, code)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ResetPassword(c *gin.Context) {
|
||||
var req schemas.ResetPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email"})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&user).Update("password_hash", hashedPassword)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) VerifyEmail(c *gin.Context) {
|
||||
var req schemas.VerifyEmailRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&user).Update("email_verified", true)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) GetProfile(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var user models.User
|
||||
if err := utils.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, schemas.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
PurchaseCredits: user.PurchaseCredits,
|
||||
InviteCode: user.InviteCode,
|
||||
EmailVerified: user.EmailVerified,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) UpdateProfile(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var user models.User
|
||||
if err := utils.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var updateData map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updateData); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if username, ok := updateData["username"].(string); ok && username != "" {
|
||||
user.Username = username
|
||||
}
|
||||
|
||||
utils.DB.Save(&user)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ChangePassword(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := utils.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CheckPassword(req.OldPassword, user.PasswordHash) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Old password is incorrect"})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, _ := utils.HashPassword(req.NewPassword)
|
||||
utils.DB.Model(&user).Update("password_hash", hashedPassword)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Password changed successfully"})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) SendVerifyCode(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var user models.User
|
||||
if err := utils.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if user.EmailVerified {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Email already verified"})
|
||||
return
|
||||
}
|
||||
|
||||
code := utils.GenerateVerifyCode()
|
||||
utils.SendVerifyEmail(user.Email, code)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Verification code sent", "expires_at": time.Now().Add(30 * time.Minute)})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) CheckInstalled(c *gin.Context) {
|
||||
var count int64
|
||||
utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
|
||||
c.JSON(http.StatusOK, gin.H{"installed": count > 0})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Install(c *gin.Context) {
|
||||
var count int64
|
||||
utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "System already installed"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.InstallRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
|
||||
return
|
||||
}
|
||||
|
||||
admin := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
PasswordHash: hashedPassword,
|
||||
Role: "admin",
|
||||
InviteCode: utils.GenerateInviteCode(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&admin).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create admin user"})
|
||||
return
|
||||
}
|
||||
|
||||
token, _ := utils.GenerateToken(admin.ID, admin.Role)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"token": token,
|
||||
"user": schemas.UserResponse{
|
||||
ID: admin.ID,
|
||||
Username: admin.Username,
|
||||
Email: admin.Email,
|
||||
Role: admin.Role,
|
||||
PurchaseCredits: admin.PurchaseCredits,
|
||||
InviteCode: admin.InviteCode,
|
||||
EmailVerified: admin.EmailVerified,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sale/internal/models"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BrandHandler struct{}
|
||||
|
||||
func NewBrandHandler() *BrandHandler {
|
||||
return &BrandHandler{}
|
||||
}
|
||||
|
||||
func (h *BrandHandler) List(c *gin.Context) {
|
||||
var brands []models.Brand
|
||||
utils.DB.Where("deleted_at IS NULL").Order("sort_order ASC, name ASC").Find(&brands)
|
||||
c.JSON(http.StatusOK, gin.H{"data": brands})
|
||||
}
|
||||
|
||||
func (h *BrandHandler) Create(c *gin.Context) {
|
||||
var brand models.Brand
|
||||
if err := c.ShouldBindJSON(&brand); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
utils.DB.Create(&brand)
|
||||
c.JSON(http.StatusOK, brand)
|
||||
}
|
||||
|
||||
func (h *BrandHandler) Update(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var brand models.Brand
|
||||
if err := utils.DB.First(&brand, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Brand not found"})
|
||||
return
|
||||
}
|
||||
var input models.Brand
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
utils.DB.Model(&brand).Updates(input)
|
||||
c.JSON(http.StatusOK, brand)
|
||||
}
|
||||
|
||||
func (h *BrandHandler) Delete(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
utils.DB.Delete(&models.Brand{}, id)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Brand deleted"})
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type LotteryHandler struct{}
|
||||
|
||||
func NewLotteryHandler() *LotteryHandler {
|
||||
return &LotteryHandler{}
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) List(c *gin.Context) {
|
||||
var lotteries []models.Lottery
|
||||
utils.DB.Where("is_active = ?", true).Preload("Prizes").Order("created_at DESC").Find(&lotteries)
|
||||
c.JSON(http.StatusOK, gin.H{"data": lotteries})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) GetByID(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var lottery models.Lottery
|
||||
if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": lottery})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) Register(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var lottery models.Lottery
|
||||
if err := utils.DB.First(&lottery, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if now.Before(lottery.StartTime) || now.After(lottery.EndTime) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not in registration period"})
|
||||
return
|
||||
}
|
||||
|
||||
var existing models.LotteryParticipant
|
||||
if err := utils.DB.Where("lottery_id = ? AND user_id = ?", id, userID).First(&existing).Error; err == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Already registered"})
|
||||
return
|
||||
}
|
||||
|
||||
var totalSpent float64
|
||||
utils.DB.Model(&models.Order{}).
|
||||
Where("user_id = ? AND status = ?", userID, models.OrderStatusCompleted).
|
||||
Select("COALESCE(SUM(total_amount), 0)").
|
||||
Scan(&totalSpent)
|
||||
|
||||
participant := models.LotteryParticipant{
|
||||
LotteryID: lottery.ID,
|
||||
UserID: userID,
|
||||
PurchaseWeight: int(totalSpent),
|
||||
}
|
||||
|
||||
utils.DB.Create(&participant)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Registered successfully"})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) Create(c *gin.Context) {
|
||||
var req schemas.CreateLotteryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
startTime, _ := time.Parse(time.RFC3339, req.StartTime)
|
||||
endTime, _ := time.Parse(time.RFC3339, req.EndTime)
|
||||
|
||||
lottery := models.Lottery{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
Cycle: req.Cycle,
|
||||
DailyQuota: req.DailyQuota,
|
||||
TotalQuota: req.TotalQuota,
|
||||
RegistrationValidity: req.RegistrationValidity,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
utils.DB.Create(&lottery)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": lottery})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var lottery models.Lottery
|
||||
if err := utils.DB.First(&lottery, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateLotteryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.StartTime != nil {
|
||||
t, _ := time.Parse(time.RFC3339, *req.StartTime)
|
||||
updates["start_time"] = t
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
t, _ := time.Parse(time.RFC3339, *req.EndTime)
|
||||
updates["end_time"] = t
|
||||
}
|
||||
if req.Cycle != nil {
|
||||
updates["cycle"] = *req.Cycle
|
||||
}
|
||||
if req.DailyQuota != nil {
|
||||
updates["daily_quota"] = *req.DailyQuota
|
||||
}
|
||||
if req.TotalQuota != nil {
|
||||
updates["total_quota"] = *req.TotalQuota
|
||||
}
|
||||
if req.RegistrationValidity != nil {
|
||||
updates["registration_validity"] = *req.RegistrationValidity
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
updates["is_active"] = *req.IsActive
|
||||
}
|
||||
|
||||
utils.DB.Model(&lottery).Updates(updates)
|
||||
c.JSON(http.StatusOK, gin.H{"data": lottery})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
utils.DB.Delete(&models.Lottery{}, id)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Lottery deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) AddPrize(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var req schemas.AddLotteryPrizeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
prize := models.LotteryPrize{
|
||||
LotteryID: uint(id),
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Quantity: req.Quantity,
|
||||
Weight: req.Weight,
|
||||
CreditReward: req.CreditReward,
|
||||
DrawMode: req.DrawMode,
|
||||
}
|
||||
|
||||
utils.DB.Create(&prize)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": prize})
|
||||
}
|
||||
|
||||
func (h *LotteryHandler) Draw(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var lottery models.Lottery
|
||||
if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var participants []models.LotteryParticipant
|
||||
utils.DB.Where("lottery_id = ?", id).Find(&participants)
|
||||
|
||||
if len(participants) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No participants"})
|
||||
return
|
||||
}
|
||||
|
||||
var winners []models.LotteryWinner
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
for _, prize := range lottery.Prizes {
|
||||
remaining := prize.Quantity
|
||||
|
||||
var weightParticipants []models.LotteryParticipant
|
||||
var randomParticipants []models.LotteryParticipant
|
||||
|
||||
if prize.DrawMode == models.DrawModeWeight {
|
||||
weightParticipants = participants
|
||||
} else {
|
||||
randomParticipants = participants
|
||||
}
|
||||
|
||||
selectedParticipants := weightParticipants
|
||||
if len(selectedParticipants) == 0 {
|
||||
selectedParticipants = randomParticipants
|
||||
}
|
||||
|
||||
totalWeight := 0
|
||||
for _, p := range selectedParticipants {
|
||||
w := p.PurchaseWeight
|
||||
if w < 1 {
|
||||
w = 1
|
||||
}
|
||||
totalWeight += w
|
||||
}
|
||||
|
||||
selected := make(map[uint]bool)
|
||||
for remaining > 0 && len(selected) < len(selectedParticipants) {
|
||||
r := rng.Intn(totalWeight)
|
||||
cumWeight := 0
|
||||
for _, p := range selectedParticipants {
|
||||
if selected[p.UserID] {
|
||||
continue
|
||||
}
|
||||
w := p.PurchaseWeight
|
||||
if w < 1 {
|
||||
w = 1
|
||||
}
|
||||
cumWeight += w
|
||||
if cumWeight > r {
|
||||
winner := models.LotteryWinner{
|
||||
LotteryID: lottery.ID,
|
||||
PrizeID: prize.ID,
|
||||
UserID: p.UserID,
|
||||
DrawnAt: time.Now(),
|
||||
}
|
||||
winners = append(winners, winner)
|
||||
selected[p.UserID] = true
|
||||
remaining--
|
||||
|
||||
if prize.Type == models.PrizeTypeCredit && prize.CreditReward != nil {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", p.UserID).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", *prize.CreditReward))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(winners) > 0 {
|
||||
utils.DB.Create(&winners)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": winners, "message": "Draw completed successfully"})
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CartHandler struct{}
|
||||
|
||||
func NewCartHandler() *CartHandler {
|
||||
return &CartHandler{}
|
||||
}
|
||||
|
||||
func (h *CartHandler) List(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var carts []models.Cart
|
||||
utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts)
|
||||
c.JSON(http.StatusOK, gin.H{"data": carts})
|
||||
}
|
||||
|
||||
func (h *CartHandler) Add(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.AddToCartRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var cart models.Cart
|
||||
result := utils.DB.Where("user_id = ? AND product_id = ?", userID, req.ProductID).First(&cart)
|
||||
if result.Error == nil {
|
||||
cart.Quantity += req.Quantity
|
||||
utils.DB.Save(&cart)
|
||||
} else {
|
||||
cart = models.Cart{
|
||||
UserID: userID,
|
||||
ProductID: req.ProductID,
|
||||
Quantity: req.Quantity,
|
||||
}
|
||||
utils.DB.Create(&cart)
|
||||
}
|
||||
|
||||
utils.DB.Preload("Product").First(&cart, cart.ID)
|
||||
c.JSON(http.StatusOK, gin.H{"data": cart})
|
||||
}
|
||||
|
||||
func (h *CartHandler) Update(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var cart models.Cart
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&cart).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Cart item not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateCartRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
cart.Quantity = req.Quantity
|
||||
utils.DB.Save(&cart)
|
||||
utils.DB.Preload("Product").First(&cart, cart.ID)
|
||||
c.JSON(http.StatusOK, gin.H{"data": cart})
|
||||
}
|
||||
|
||||
func (h *CartHandler) Delete(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Cart{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete cart item"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Cart item deleted successfully"})
|
||||
}
|
||||
|
||||
type OrderHandler struct{}
|
||||
|
||||
func NewOrderHandler() *OrderHandler {
|
||||
return &OrderHandler{}
|
||||
}
|
||||
|
||||
func (h *OrderHandler) List(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
role, _ := c.Get("role")
|
||||
|
||||
var orders []models.Order
|
||||
query := utils.DB.Model(&models.Order{})
|
||||
|
||||
if role == "user" {
|
||||
query = query.Where("user_id = ?", userID)
|
||||
} else if role == "supplier" {
|
||||
query = query.Where("supplier_id = ?", userID)
|
||||
}
|
||||
|
||||
query.Preload("OrderItems.Product").Preload("ShippingAddress").
|
||||
Order("created_at DESC").Find(&orders)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": orders})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) GetByID(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
role, _ := c.Get("role")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var order models.Order
|
||||
query := utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User")
|
||||
|
||||
if role == "user" {
|
||||
query = query.Where("user_id = ?", userID)
|
||||
} else if role == "supplier" {
|
||||
query = query.Where("supplier_id = ?", userID)
|
||||
}
|
||||
|
||||
if err := query.First(&order, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": order})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) Create(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.CreateOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var carts []models.Cart
|
||||
utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts)
|
||||
|
||||
if len(carts) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cart is empty"})
|
||||
return
|
||||
}
|
||||
|
||||
var subtotal float64
|
||||
var totalQuantity int
|
||||
var orderItems []models.OrderItem
|
||||
supplierMap := make(map[uint]bool)
|
||||
|
||||
for _, cart := range carts {
|
||||
if cart.Product.RequireCredit {
|
||||
var user models.User
|
||||
utils.DB.First(&user, userID)
|
||||
if user.PurchaseCredits < cart.Product.CreditCost*cart.Quantity {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient purchase credits for " + cart.Product.Name})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var inventory models.Inventory
|
||||
if err := utils.DB.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil {
|
||||
if inventory.Quantity < cart.Quantity {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for " + cart.Product.Name})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
subtotal += cart.Product.Price * float64(cart.Quantity)
|
||||
totalQuantity += cart.Quantity
|
||||
orderItems = append(orderItems, models.OrderItem{
|
||||
ProductID: cart.ProductID,
|
||||
Quantity: cart.Quantity,
|
||||
Price: cart.Product.Price,
|
||||
})
|
||||
|
||||
if inventory.SupplierID != 0 {
|
||||
supplierMap[inventory.SupplierID] = true
|
||||
}
|
||||
}
|
||||
|
||||
var supplierID *uint
|
||||
for sid := range supplierMap {
|
||||
sid := sid
|
||||
supplierID = &sid
|
||||
break
|
||||
}
|
||||
|
||||
var shippingFeeFirstWeight, shippingFeePerGram, serviceFeeRate, taxRate float64
|
||||
var setting models.SystemSetting
|
||||
if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil {
|
||||
shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64)
|
||||
}
|
||||
if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil {
|
||||
shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64)
|
||||
}
|
||||
if err := utils.DB.Where("`key` = ?", "service_fee_rate").First(&setting).Error; err == nil {
|
||||
serviceFeeRate, _ = strconv.ParseFloat(setting.Value, 64)
|
||||
}
|
||||
if err := utils.DB.Where("`key` = ?", "tax_rate").First(&setting).Error; err == nil {
|
||||
taxRate, _ = strconv.ParseFloat(setting.Value, 64)
|
||||
}
|
||||
|
||||
shippingFee := shippingFeeFirstWeight
|
||||
if subtotal >= 99 || shippingFeeFirstWeight == 0 {
|
||||
shippingFee = 0
|
||||
} else if totalQuantity > 500 {
|
||||
shippingFee += shippingFeePerGram * float64(totalQuantity-500)
|
||||
}
|
||||
|
||||
serviceFee := subtotal * serviceFeeRate / 100
|
||||
tax := subtotal * taxRate / 100
|
||||
totalAmount := subtotal + shippingFee + serviceFee + tax
|
||||
|
||||
order := models.Order{
|
||||
UserID: userID,
|
||||
SupplierID: supplierID,
|
||||
Subtotal: subtotal,
|
||||
ShippingFee: shippingFee,
|
||||
ServiceFee: serviceFee,
|
||||
Tax: tax,
|
||||
TotalAmount: totalAmount,
|
||||
Status: models.OrderStatusPendingPayment,
|
||||
ShippingAddressID: &req.ShippingAddressID,
|
||||
PaymentMethod: req.PaymentMethod,
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&order).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order"})
|
||||
return
|
||||
}
|
||||
|
||||
for i := range orderItems {
|
||||
orderItems[i].OrderID = order.ID
|
||||
}
|
||||
utils.DB.Create(&orderItems)
|
||||
|
||||
for _, cart := range carts {
|
||||
if cart.Product.RequireCredit {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", cart.Product.CreditCost*cart.Quantity))
|
||||
}
|
||||
if cart.Product.CreditReward > 0 {
|
||||
utils.DB.Model(&models.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", cart.Product.CreditReward*cart.Quantity))
|
||||
}
|
||||
}
|
||||
|
||||
utils.DB.Where("user_id = ?", userID).Delete(&models.Cart{})
|
||||
|
||||
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").First(&order, order.ID)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": order})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) Refund(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var order models.Order
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status == models.OrderStatusShipped || order.Status == models.OrderStatusCompleted {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot refund a shipped or completed order"})
|
||||
return
|
||||
}
|
||||
|
||||
if order.RefundStatus == models.RefundStatusPending {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Refund already requested"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.RefundRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&order).Updates(map[string]interface{}{
|
||||
"refund_status": models.RefundStatusPending,
|
||||
"refund_reason": req.Reason,
|
||||
"status": models.OrderStatusRefunding,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Refund request submitted successfully"})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) ConfirmOrder(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var order models.Order
|
||||
if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status != models.OrderStatusPendingConfirm {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&order).Update("status", models.OrderStatusPendingShip)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) ShipOrder(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var order models.Order
|
||||
if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status != models.OrderStatusPendingShip {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be shipped"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.ShipOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&order).Updates(map[string]interface{}{
|
||||
"status": models.OrderStatusShipped,
|
||||
"tracking_number": req.TrackingNumber,
|
||||
"shipping_photo": req.ShippingPhoto,
|
||||
"express_photo": req.ExpressPhoto,
|
||||
"customs_photo": req.CustomsPhoto,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Order shipped successfully"})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) AdminList(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
var total int64
|
||||
utils.DB.Model(&models.Order{}).Count(&total)
|
||||
|
||||
var orders []models.Order
|
||||
offset := (page - 1) * pageSize
|
||||
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
|
||||
Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": orders,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) ProcessRefund(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var order models.Order
|
||||
if err := utils.DB.First(&order, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.ProcessRefundRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Status == "approved" {
|
||||
var setting models.SystemSetting
|
||||
feeRate := 0.0
|
||||
if err := utils.DB.Where("`key` = ?", "payment_channel_fee_rate").First(&setting).Error; err == nil {
|
||||
feeRate, _ = strconv.ParseFloat(setting.Value, 64)
|
||||
}
|
||||
|
||||
refundAmount := order.TotalAmount * (1 - feeRate/100)
|
||||
utils.DB.Model(&order).Updates(map[string]interface{}{
|
||||
"refund_status": models.RefundStatusCompleted,
|
||||
"refund_amount": refundAmount,
|
||||
"status": models.OrderStatusRefunded,
|
||||
})
|
||||
} else {
|
||||
utils.DB.Model(&order).Updates(map[string]interface{}{
|
||||
"refund_status": models.RefundStatusRejected,
|
||||
"status": models.OrderStatusPendingConfirm,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Refund processed successfully"})
|
||||
}
|
||||
|
||||
func (h *OrderHandler) Export(c *gin.Context) {
|
||||
var orders []models.Order
|
||||
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
|
||||
Order("created_at DESC").Find(&orders)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": orders})
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CategoryHandler struct{}
|
||||
|
||||
func NewCategoryHandler() *CategoryHandler {
|
||||
return &CategoryHandler{}
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) List(c *gin.Context) {
|
||||
var categories []models.Category
|
||||
utils.DB.Where("parent_id IS NULL").Order("sort_order ASC, id ASC").Find(&categories)
|
||||
for i := range categories {
|
||||
utils.DB.Where("parent_id = ?", categories[i].ID).Order("sort_order ASC, id ASC").Find(&categories[i].Children)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": categories})
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Create(c *gin.Context) {
|
||||
var req schemas.CreateCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
category := models.Category{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
ParentID: req.ParentID,
|
||||
MinAmount: req.MinAmount,
|
||||
MaxAmount: req.MaxAmount,
|
||||
MinQuantity: req.MinQuantity,
|
||||
MaxQuantity: req.MaxQuantity,
|
||||
MinWeight: req.MinWeight,
|
||||
MaxWeight: req.MaxWeight,
|
||||
SortOrder: req.SortOrder,
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&category).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create category"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"data": category})
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var category models.Category
|
||||
if err := utils.DB.First(&category, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateCategoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.ParentID != nil {
|
||||
updates["parent_id"] = *req.ParentID
|
||||
}
|
||||
if req.MinAmount != nil {
|
||||
updates["min_amount"] = *req.MinAmount
|
||||
}
|
||||
if req.MaxAmount != nil {
|
||||
updates["max_amount"] = *req.MaxAmount
|
||||
}
|
||||
if req.MinQuantity != nil {
|
||||
updates["min_quantity"] = *req.MinQuantity
|
||||
}
|
||||
if req.MaxQuantity != nil {
|
||||
updates["max_quantity"] = *req.MaxQuantity
|
||||
}
|
||||
if req.MinWeight != nil {
|
||||
updates["min_weight"] = *req.MinWeight
|
||||
}
|
||||
if req.MaxWeight != nil {
|
||||
updates["max_weight"] = *req.MaxWeight
|
||||
}
|
||||
if req.SortOrder != nil {
|
||||
updates["sort_order"] = *req.SortOrder
|
||||
}
|
||||
|
||||
utils.DB.Model(&category).Updates(updates)
|
||||
c.JSON(http.StatusOK, gin.H{"data": category})
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if err := utils.DB.Delete(&models.Category{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete category"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"})
|
||||
}
|
||||
|
||||
type ProductHandler struct{}
|
||||
|
||||
func NewProductHandler() *ProductHandler {
|
||||
return &ProductHandler{}
|
||||
}
|
||||
|
||||
func (h *ProductHandler) List(c *gin.Context) {
|
||||
var req schemas.ProductListRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
query := utils.DB.Model(&models.Product{}).Where("is_active = ?", true)
|
||||
|
||||
if req.CategoryID != nil {
|
||||
query = query.Joins("JOIN product_categories ON product_categories.product_id = products.id").
|
||||
Where("product_categories.category_id = ?", *req.CategoryID)
|
||||
}
|
||||
|
||||
if req.BrandID != nil {
|
||||
query = query.Where("brand_id = ?", *req.BrandID)
|
||||
}
|
||||
|
||||
if req.Keyword != "" {
|
||||
query = query.Where("name ILIKE ?", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
if req.MinPrice != nil {
|
||||
query = query.Where("price >= ?", *req.MinPrice)
|
||||
}
|
||||
|
||||
if req.MaxPrice != nil {
|
||||
query = query.Where("price <= ?", *req.MaxPrice)
|
||||
}
|
||||
|
||||
var total int64
|
||||
query.Count(&total)
|
||||
|
||||
var products []models.Product
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
query.Preload("Categories").Preload("CustomFields").Preload("Brand").
|
||||
Order("created_at DESC").
|
||||
Offset(offset).Limit(req.PageSize).
|
||||
Find(&products)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(req.PageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": products,
|
||||
"pagination": gin.H{
|
||||
"page": req.Page,
|
||||
"page_size": req.PageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) GetByID(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var product models.Product
|
||||
if err := utils.DB.Preload("Categories").Preload("CustomFields").First(&product, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": product})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) AdminList(c *gin.Context) {
|
||||
var products []models.Product
|
||||
utils.DB.Preload("Categories").Preload("Brand").Order("created_at DESC").Find(&products)
|
||||
c.JSON(http.StatusOK, gin.H{"data": products})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) Create(c *gin.Context) {
|
||||
var req schemas.CreateProductRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
product := models.Product{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
Price: req.Price,
|
||||
MinPurchase: req.MinPurchase,
|
||||
MaxPurchase: req.MaxPurchase,
|
||||
MinWeight: req.MinWeight,
|
||||
MaxWeight: req.MaxWeight,
|
||||
MinAmount: req.MinAmount,
|
||||
MaxAmount: req.MaxAmount,
|
||||
RequireCredit: req.RequireCredit,
|
||||
CreditCost: req.CreditCost,
|
||||
CreditReward: req.CreditReward,
|
||||
Images: req.Images,
|
||||
BrandID: req.BrandID,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := utils.DB.Create(&product).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.CategoryIDs) > 0 {
|
||||
var categories []models.Category
|
||||
utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories)
|
||||
utils.DB.Model(&product).Association("Categories").Replace(categories)
|
||||
}
|
||||
|
||||
if len(req.CustomFields) > 0 {
|
||||
for _, cf := range req.CustomFields {
|
||||
utils.DB.Create(&models.ProductCustomField{
|
||||
ProductID: product.ID,
|
||||
FieldName: cf.FieldName,
|
||||
FieldValue: cf.FieldValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": product})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var product models.Product
|
||||
if err := utils.DB.First(&product, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateProductRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != nil {
|
||||
updates["name"] = *req.Name
|
||||
}
|
||||
if req.Description != nil {
|
||||
updates["description"] = *req.Description
|
||||
}
|
||||
if req.Price != nil {
|
||||
updates["price"] = *req.Price
|
||||
}
|
||||
if req.MinPurchase != nil {
|
||||
updates["min_purchase"] = *req.MinPurchase
|
||||
}
|
||||
if req.MaxPurchase != nil {
|
||||
updates["max_purchase"] = *req.MaxPurchase
|
||||
}
|
||||
if req.MinWeight != nil {
|
||||
updates["min_weight"] = *req.MinWeight
|
||||
}
|
||||
if req.MaxWeight != nil {
|
||||
updates["max_weight"] = *req.MaxWeight
|
||||
}
|
||||
if req.MinAmount != nil {
|
||||
updates["min_amount"] = *req.MinAmount
|
||||
}
|
||||
if req.MaxAmount != nil {
|
||||
updates["max_amount"] = *req.MaxAmount
|
||||
}
|
||||
if req.RequireCredit != nil {
|
||||
updates["require_credit"] = *req.RequireCredit
|
||||
}
|
||||
if req.CreditCost != nil {
|
||||
updates["credit_cost"] = *req.CreditCost
|
||||
}
|
||||
if req.CreditReward != nil {
|
||||
updates["credit_reward"] = *req.CreditReward
|
||||
}
|
||||
if req.Images != nil {
|
||||
updates["images"] = *req.Images
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
updates["is_active"] = *req.IsActive
|
||||
}
|
||||
if req.BrandID != nil {
|
||||
updates["brand_id"] = *req.BrandID
|
||||
}
|
||||
|
||||
utils.DB.Model(&product).Updates(updates)
|
||||
|
||||
if req.CategoryIDs != nil {
|
||||
var categories []models.Category
|
||||
utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories)
|
||||
utils.DB.Model(&product).Association("Categories").Replace(categories)
|
||||
}
|
||||
|
||||
if req.CustomFields != nil {
|
||||
utils.DB.Where("product_id = ?", product.ID).Delete(&models.ProductCustomField{})
|
||||
for _, cf := range req.CustomFields {
|
||||
utils.DB.Create(&models.ProductCustomField{
|
||||
ProductID: product.ID,
|
||||
FieldName: cf.FieldName,
|
||||
FieldValue: cf.FieldValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID)
|
||||
c.JSON(http.StatusOK, gin.H{"data": product})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
if err := utils.DB.Delete(&models.Product{}, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete product"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Product deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *ProductHandler) AddCustomField(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var product models.Product
|
||||
if err := utils.DB.First(&product, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.CustomFieldRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
field := models.ProductCustomField{
|
||||
ProductID: product.ID,
|
||||
FieldName: req.FieldName,
|
||||
FieldValue: req.FieldValue,
|
||||
}
|
||||
|
||||
utils.DB.Create(&field)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": field})
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SystemHandler struct{}
|
||||
|
||||
func NewSystemHandler() *SystemHandler {
|
||||
return &SystemHandler{}
|
||||
}
|
||||
|
||||
func (h *SystemHandler) GetSettings(c *gin.Context) {
|
||||
var settings []models.SystemSetting
|
||||
utils.DB.Find(&settings)
|
||||
|
||||
result := make(map[string]string)
|
||||
for _, s := range settings {
|
||||
result[s.Key] = s.Value
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
func (h *SystemHandler) GetPublicSettings(c *gin.Context) {
|
||||
publicKeys := []string{
|
||||
"payment_channel_fee_rate",
|
||||
"shipping_fee_first_weight",
|
||||
"shipping_fee_per_gram",
|
||||
"service_fee_rate",
|
||||
"tax_rate",
|
||||
"enabled_payments",
|
||||
}
|
||||
|
||||
var settings []models.SystemSetting
|
||||
utils.DB.Where("`key` IN ?", publicKeys).Find(&settings)
|
||||
|
||||
result := make(map[string]string)
|
||||
for _, s := range settings {
|
||||
result[s.Key] = s.Value
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
func (h *SystemHandler) GetStats(c *gin.Context) {
|
||||
var userCount, productCount, orderCount int64
|
||||
var totalRevenue float64
|
||||
|
||||
utils.DB.Model(&models.User{}).Count(&userCount)
|
||||
utils.DB.Model(&models.Product{}).Count(&productCount)
|
||||
utils.DB.Model(&models.Order{}).Count(&orderCount)
|
||||
utils.DB.Model(&models.Order{}).Where("status = ?", "completed").Select("COALESCE(SUM(total), 0)").Scan(&totalRevenue)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"users": userCount,
|
||||
"products": productCount,
|
||||
"orders": orderCount,
|
||||
"revenue": totalRevenue,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *SystemHandler) UpdateSettings(c *gin.Context) {
|
||||
var req schemas.UpdateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for key, value := range req.Settings {
|
||||
var setting models.SystemSetting
|
||||
result := utils.DB.Where("`key` = ?", key).First(&setting)
|
||||
if result.Error != nil {
|
||||
utils.DB.Create(&models.SystemSetting{Key: key, Value: value})
|
||||
} else {
|
||||
utils.DB.Model(&setting).Update("value", value)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Settings updated successfully"})
|
||||
}
|
||||
|
||||
func (h *SystemHandler) UpdateSMTP(c *gin.Context) {
|
||||
var req map[string]string
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
smtpKeys := []string{"smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_from"}
|
||||
for _, key := range smtpKeys {
|
||||
if value, ok := req[key]; ok {
|
||||
var setting models.SystemSetting
|
||||
result := utils.DB.Where("`key` = ?", key).First(&setting)
|
||||
if result.Error != nil {
|
||||
utils.DB.Create(&models.SystemSetting{Key: key, Value: value})
|
||||
} else {
|
||||
utils.DB.Model(&setting).Update("value", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "SMTP settings updated successfully"})
|
||||
}
|
||||
|
||||
func (h *SystemHandler) UpdatePayment(c *gin.Context) {
|
||||
var req map[string]string
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for key, value := range req {
|
||||
var setting models.SystemSetting
|
||||
result := utils.DB.Where("`key` = ?", key).First(&setting)
|
||||
if result.Error != nil {
|
||||
utils.DB.Create(&models.SystemSetting{Key: key, Value: value})
|
||||
} else {
|
||||
utils.DB.Model(&setting).Update("value", value)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Payment settings updated successfully"})
|
||||
}
|
||||
|
||||
type SupplierHandler struct{}
|
||||
|
||||
func NewSupplierHandler() *SupplierHandler {
|
||||
return &SupplierHandler{}
|
||||
}
|
||||
|
||||
func (h *SupplierHandler) List(c *gin.Context) {
|
||||
var suppliers []models.User
|
||||
utils.DB.Where("role = ?", "supplier").Find(&suppliers)
|
||||
c.JSON(http.StatusOK, gin.H{"data": suppliers})
|
||||
}
|
||||
|
||||
func (h *SupplierHandler) Create(c *gin.Context) {
|
||||
var req schemas.AddSupplierRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, _ := utils.HashPassword(req.Password)
|
||||
supplier := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
PasswordHash: hashedPassword,
|
||||
Role: "supplier",
|
||||
InviteCode: utils.GenerateInviteCode(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
utils.DB.Create(&supplier)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": supplier})
|
||||
}
|
||||
|
||||
func (h *SupplierHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var supplier models.User
|
||||
if err := utils.DB.Where("id = ? AND role = ?", id, "supplier").First(&supplier).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Supplier not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var updateData map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&updateData); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&supplier).Updates(updateData)
|
||||
c.JSON(http.StatusOK, gin.H{"data": supplier})
|
||||
}
|
||||
|
||||
func (h *SupplierHandler) Delete(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
utils.DB.Where("id = ? AND role = ?", id, "supplier").Delete(&models.User{})
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Supplier deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *SupplierHandler) Authorize(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var req schemas.AuthorizeSupplierRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
auth := models.SupplierAuthorization{
|
||||
SupplierID: uint(id),
|
||||
Type: req.Type,
|
||||
RefID: req.RefID,
|
||||
}
|
||||
|
||||
utils.DB.Create(&auth)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": auth})
|
||||
}
|
||||
|
||||
type InventoryHandler struct{}
|
||||
|
||||
func NewInventoryHandler() *InventoryHandler {
|
||||
return &InventoryHandler{}
|
||||
}
|
||||
|
||||
func (h *InventoryHandler) SupplierList(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var inventory []models.Inventory
|
||||
utils.DB.Where("supplier_id = ?", userID).Preload("Product").Find(&inventory)
|
||||
c.JSON(http.StatusOK, gin.H{"data": inventory})
|
||||
}
|
||||
|
||||
func (h *InventoryHandler) Update(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var inventory models.Inventory
|
||||
if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&inventory).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Inventory not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateInventoryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&inventory).Update("quantity", req.Quantity)
|
||||
c.JSON(http.StatusOK, gin.H{"data": inventory})
|
||||
}
|
||||
|
||||
func (h *InventoryHandler) AdminList(c *gin.Context) {
|
||||
var inventory []models.Inventory
|
||||
utils.DB.Preload("Product").Find(&inventory)
|
||||
c.JSON(http.StatusOK, gin.H{"data": inventory})
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"sale/internal/models"
|
||||
"sale/internal/schemas"
|
||||
"sale/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type TicketHandler struct{}
|
||||
|
||||
func NewTicketHandler() *TicketHandler {
|
||||
return &TicketHandler{}
|
||||
}
|
||||
|
||||
func (h *TicketHandler) List(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
var total int64
|
||||
utils.DB.Model(&models.Ticket{}).Where("user_id = ?", userID).Count(&total)
|
||||
|
||||
var tickets []models.Ticket
|
||||
offset := (page - 1) * pageSize
|
||||
utils.DB.Where("user_id = ?", userID).Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tickets)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": tickets,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *TicketHandler) Create(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
var req schemas.CreateTicketRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
category := req.Category
|
||||
if category == "" {
|
||||
category = models.TicketCategoryOther
|
||||
}
|
||||
|
||||
ticket := models.Ticket{
|
||||
UserID: userID,
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Category: category,
|
||||
Status: models.TicketStatusPending,
|
||||
}
|
||||
|
||||
utils.DB.Create(&ticket)
|
||||
c.JSON(http.StatusCreated, gin.H{"data": ticket})
|
||||
}
|
||||
|
||||
func (h *TicketHandler) GetByID(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
|
||||
var ticket models.Ticket
|
||||
if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&ticket).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": ticket})
|
||||
}
|
||||
|
||||
func (h *TicketHandler) AdminList(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
status := c.Query("status")
|
||||
|
||||
var total int64
|
||||
query := utils.DB.Model(&models.Ticket{})
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
query.Count(&total)
|
||||
|
||||
var tickets []models.Ticket
|
||||
offset := (page - 1) * pageSize
|
||||
q := utils.DB.Preload("User").Preload("Assignee")
|
||||
if status != "" {
|
||||
q = q.Where("status = ?", status)
|
||||
}
|
||||
q.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tickets)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": tickets,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *TicketHandler) Update(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var ticket models.Ticket
|
||||
if err := utils.DB.First(&ticket, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req schemas.UpdateTicketRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Status != nil {
|
||||
updates["status"] = *req.Status
|
||||
}
|
||||
if req.AssignedTo != nil {
|
||||
updates["assigned_to"] = *req.AssignedTo
|
||||
}
|
||||
if req.Reply != nil {
|
||||
updates["reply"] = *req.Reply
|
||||
}
|
||||
|
||||
utils.DB.Model(&ticket).Updates(updates)
|
||||
c.JSON(http.StatusOK, gin.H{"data": ticket})
|
||||
}
|
||||
|
||||
func (h *TicketHandler) Assign(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var ticket models.Ticket
|
||||
if err := utils.DB.First(&ticket, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
AssignedTo uint `json:"assigned_to" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
utils.DB.Model(&ticket).Updates(map[string]interface{}{
|
||||
"assigned_to": req.AssignedTo,
|
||||
"status": models.TicketStatusProcessing,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Ticket assigned successfully"})
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type UploadHandler struct{}
|
||||
|
||||
func NewUploadHandler() *UploadHandler {
|
||||
return &UploadHandler{}
|
||||
}
|
||||
|
||||
func (h *UploadHandler) UploadImage(c *gin.Context) {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded"})
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true}
|
||||
if !allowedExts[ext] {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file type. Only jpg, jpeg, png, gif, webp are allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
uploadDir := "uploads/images"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"})
|
||||
return
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
|
||||
filepath := filepath.Join(uploadDir, filename)
|
||||
|
||||
if err := c.SaveUploadedFile(file, filepath); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
|
||||
return
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("/uploads/images/%s", filename)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"url": url,
|
||||
"filename": filename,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UploadHandler) UploadMultiple(c *gin.Context) {
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No files uploaded"})
|
||||
return
|
||||
}
|
||||
|
||||
files := form.File["files"]
|
||||
if len(files) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "No files uploaded"})
|
||||
return
|
||||
}
|
||||
|
||||
uploadDir := "uploads/images"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"})
|
||||
return
|
||||
}
|
||||
|
||||
allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true}
|
||||
var urls []string
|
||||
|
||||
for _, file := range files {
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if !allowedExts[ext] {
|
||||
continue
|
||||
}
|
||||
|
||||
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
|
||||
filepath := filepath.Join(uploadDir, filename)
|
||||
|
||||
if err := c.SaveUploadedFile(file, filepath); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
urls = append(urls, fmt.Sprintf("/uploads/images/%s", filename))
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"urls": urls,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *UploadHandler) DeleteImage(c *gin.Context) {
|
||||
filename := c.Param("filename")
|
||||
if filename == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Filename is required"})
|
||||
return
|
||||
}
|
||||
|
||||
filepath := filepath.Join("uploads/images", filename)
|
||||
if err := os.Remove(filepath); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "File not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "File deleted successfully"})
|
||||
}
|
||||
|
||||
func (h *UploadHandler) ExportOrders(c *gin.Context) {
|
||||
filename := fmt.Sprintf("orders_%s.csv", time.Now().Format("20060102150405"))
|
||||
filepath := filepath.Join("uploads", filename)
|
||||
|
||||
file, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create file"})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
c.Header("Content-Description", "File Transfer")
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
c.Header("Content-Disposition", "attachment; filename="+filename)
|
||||
c.Header("Content-Type", "text/csv")
|
||||
|
||||
file.Seek(0, 0)
|
||||
_, err = io.Copy(c.Writer, file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send file"})
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user