feat: major update - MySQL, chat, wishlist, PWA, admin overhaul

This commit is contained in:
2026-03-21 20:22:00 +08:00
committed by 树萌芽
parent 48fb818b8c
commit 84874707f5
71 changed files with 13457 additions and 2031 deletions

View File

@@ -1,140 +1,139 @@
package storage
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"mengyastore-backend/internal/database"
"mengyastore-backend/internal/models"
)
type OrderStore struct {
path string
mu sync.Mutex
db *gorm.DB
}
func NewOrderStore(path string) (*OrderStore, error) {
if err := ensureOrdersFile(path); err != nil {
return nil, err
}
return &OrderStore{path: path}, nil
func NewOrderStore(db *gorm.DB) (*OrderStore, error) {
return &OrderStore{db: db}, nil
}
func (s *OrderStore) Count() (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
items, err := s.readAll()
if err != nil {
return 0, err
func orderRowToModel(row database.OrderRow) models.Order {
return models.Order{
ID: row.ID,
ProductID: row.ProductID,
ProductName: row.ProductName,
UserAccount: row.UserAccount,
UserName: row.UserName,
Quantity: row.Quantity,
DeliveredCodes: row.DeliveredCodes,
Status: row.Status,
DeliveryMode: row.DeliveryMode,
Note: row.Note,
ContactPhone: row.ContactPhone,
ContactEmail: row.ContactEmail,
NotifyEmail: row.NotifyEmail,
CreatedAt: row.CreatedAt,
}
return len(items), nil
}
func (s *OrderStore) ListByAccount(account string) ([]models.Order, error) {
s.mu.Lock()
defer s.mu.Unlock()
items, err := s.readAll()
if err != nil {
return nil, err
}
matched := make([]models.Order, 0)
for i := len(items) - 1; i >= 0; i-- {
if items[i].UserAccount == account {
matched = append(matched, items[i])
}
}
return matched, nil
}
func (s *OrderStore) Confirm(id string) (models.Order, error) {
s.mu.Lock()
defer s.mu.Unlock()
items, err := s.readAll()
if err != nil {
return models.Order{}, err
}
for i, item := range items {
if item.ID == id {
if item.Status == "completed" {
return item, nil
}
items[i].Status = "completed"
if err := s.writeAll(items); err != nil {
return models.Order{}, err
}
return items[i], nil
}
}
return models.Order{}, fmt.Errorf("order not found")
}
func (s *OrderStore) Create(order models.Order) (models.Order, error) {
s.mu.Lock()
defer s.mu.Unlock()
items, err := s.readAll()
if err != nil {
return models.Order{}, err
}
order.ID = uuid.NewString()
order.CreatedAt = time.Now()
items = append(items, order)
if err := s.writeAll(items); err != nil {
if order.ID == "" {
order.ID = uuid.NewString()
}
if len(order.DeliveredCodes) == 0 {
order.DeliveredCodes = []string{}
}
row := database.OrderRow{
ID: order.ID,
ProductID: order.ProductID,
ProductName: order.ProductName,
UserAccount: order.UserAccount,
UserName: order.UserName,
Quantity: order.Quantity,
DeliveredCodes: database.StringSlice(order.DeliveredCodes),
Status: order.Status,
DeliveryMode: order.DeliveryMode,
Note: order.Note,
ContactPhone: order.ContactPhone,
ContactEmail: order.ContactEmail,
NotifyEmail: order.NotifyEmail,
}
if err := s.db.Create(&row).Error; err != nil {
return models.Order{}, err
}
order.CreatedAt = row.CreatedAt
return order, nil
}
func (s *OrderStore) readAll() ([]models.Order, error) {
bytes, err := os.ReadFile(s.path)
if err != nil {
return nil, fmt.Errorf("read orders: %w", err)
func (s *OrderStore) GetByID(id string) (models.Order, error) {
var row database.OrderRow
if err := s.db.First(&row, "id = ?", id).Error; err != nil {
return models.Order{}, fmt.Errorf("order not found")
}
var items []models.Order
if err := json.Unmarshal(bytes, &items); err != nil {
return nil, fmt.Errorf("parse orders: %w", err)
}
return items, nil
return orderRowToModel(row), nil
}
func (s *OrderStore) writeAll(items []models.Order) error {
bytes, err := json.MarshalIndent(items, "", " ")
if err != nil {
return fmt.Errorf("encode orders: %w", err)
func (s *OrderStore) Confirm(id string) (models.Order, error) {
var row database.OrderRow
if err := s.db.First(&row, "id = ?", id).Error; err != nil {
return models.Order{}, fmt.Errorf("order not found")
}
if err := os.WriteFile(s.path, bytes, 0o644); err != nil {
return fmt.Errorf("write orders: %w", err)
if err := s.db.Model(&row).Update("status", "completed").Error; err != nil {
return models.Order{}, err
}
return nil
row.Status = "completed"
return orderRowToModel(row), nil
}
func ensureOrdersFile(path string) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("mkdir data dir: %w", err)
func (s *OrderStore) ListByAccount(account string) ([]models.Order, error) {
var rows []database.OrderRow
if err := s.db.Where("user_account = ?", account).Order("created_at DESC").Find(&rows).Error; err != nil {
return nil, err
}
if _, err := os.Stat(path); err == nil {
return nil
} else if !os.IsNotExist(err) {
return fmt.Errorf("stat data file: %w", err)
orders := make([]models.Order, len(rows))
for i, r := range rows {
orders[i] = orderRowToModel(r)
}
initial := []models.Order{}
bytes, err := json.MarshalIndent(initial, "", " ")
if err != nil {
return fmt.Errorf("init json: %w", err)
}
if err := os.WriteFile(path, bytes, 0o644); err != nil {
return fmt.Errorf("write init json: %w", err)
}
return nil
return orders, nil
}
func (s *OrderStore) ListAll() ([]models.Order, error) {
var rows []database.OrderRow
if err := s.db.Order("created_at DESC").Find(&rows).Error; err != nil {
return nil, err
}
orders := make([]models.Order, len(rows))
for i, r := range rows {
orders[i] = orderRowToModel(r)
}
return orders, nil
}
func (s *OrderStore) CountPurchasedByAccount(account, productID string) (int, error) {
var total int64
err := s.db.Model(&database.OrderRow{}).
Where("user_account = ? AND product_id = ? AND status = ?", account, productID, "completed").
Select("COALESCE(SUM(quantity), 0)").Scan(&total).Error
return int(total), err
}
// Count returns the total number of orders.
func (s *OrderStore) Count() (int, error) {
var count int64
if err := s.db.Model(&database.OrderRow{}).Count(&count).Error; err != nil {
return 0, err
}
return int(count), nil
}
// Delete removes a single order by ID.
func (s *OrderStore) Delete(id string) error {
return s.db.Delete(&database.OrderRow{}, "id = ?", id).Error
}
// UpdateCodes replaces the delivered codes for an order (used by auto-delivery to set codes after extracting).
func (s *OrderStore) UpdateCodes(id string, codes []string) error {
return s.db.Model(&database.OrderRow{}).Where("id = ?", id).
Update("delivered_codes", database.StringSlice(codes)).Error
}