Initial commit: 商品售卖网站

This commit is contained in:
2026-04-13 07:20:09 +08:00
commit c6154273f2
865 changed files with 26573 additions and 0 deletions
+124
View File
@@ -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"})
}
+164
View File
@@ -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})
}
+322
View File
@@ -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,
},
})
}
+53
View File
@@ -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"})
}
+263
View File
@@ -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"})
}
+414
View File
@@ -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})
}
+356
View File
@@ -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})
}
+247
View File
@@ -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})
}
+166
View File
@@ -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"})
}
+138
View File
@@ -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
}
}