200 lines
4.4 KiB
Go
200 lines
4.4 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"mengyastore-backend/internal/models"
|
|
)
|
|
|
|
const defaultCoverURL = "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png"
|
|
|
|
type JSONStore struct {
|
|
path string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewJSONStore(path string) (*JSONStore, error) {
|
|
if err := ensureFile(path); err != nil {
|
|
return nil, err
|
|
}
|
|
return &JSONStore{path: path}, nil
|
|
}
|
|
|
|
func ensureFile(path string) error {
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return fmt.Errorf("mkdir data dir: %w", err)
|
|
}
|
|
if _, err := os.Stat(path); err == nil {
|
|
return nil
|
|
} else if !os.IsNotExist(err) {
|
|
return fmt.Errorf("stat data file: %w", err)
|
|
}
|
|
|
|
initial := []models.Product{}
|
|
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
|
|
}
|
|
|
|
func (s *JSONStore) ListAll() ([]models.Product, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.readAll()
|
|
}
|
|
|
|
func (s *JSONStore) ListActive() ([]models.Product, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items, err := s.readAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
active := make([]models.Product, 0, len(items))
|
|
for _, item := range items {
|
|
if item.Active {
|
|
active = append(active, item)
|
|
}
|
|
}
|
|
return active, nil
|
|
}
|
|
|
|
func (s *JSONStore) Create(p models.Product) (models.Product, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items, err := s.readAll()
|
|
if err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
p = normalizeProduct(p)
|
|
p.ID = uuid.NewString()
|
|
now := time.Now()
|
|
p.CreatedAt = now
|
|
p.UpdatedAt = now
|
|
items = append(items, p)
|
|
if err := s.writeAll(items); err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (s *JSONStore) Update(id string, patch models.Product) (models.Product, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items, err := s.readAll()
|
|
if err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
for i, item := range items {
|
|
if item.ID == id {
|
|
item.Name = patch.Name
|
|
item.Price = patch.Price
|
|
item.Quantity = patch.Quantity
|
|
item.CoverURL = patch.CoverURL
|
|
item.ScreenshotURLs = normalizeProduct(patch).ScreenshotURLs
|
|
item.Description = patch.Description
|
|
item.Active = patch.Active
|
|
item.UpdatedAt = time.Now()
|
|
items[i] = item
|
|
if err := s.writeAll(items); err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
return item, nil
|
|
}
|
|
}
|
|
return models.Product{}, fmt.Errorf("product not found")
|
|
}
|
|
|
|
func (s *JSONStore) Toggle(id string, active bool) (models.Product, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items, err := s.readAll()
|
|
if err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
for i, item := range items {
|
|
if item.ID == id {
|
|
item.Active = active
|
|
item.UpdatedAt = time.Now()
|
|
items[i] = item
|
|
if err := s.writeAll(items); err != nil {
|
|
return models.Product{}, err
|
|
}
|
|
return item, nil
|
|
}
|
|
}
|
|
return models.Product{}, fmt.Errorf("product not found")
|
|
}
|
|
|
|
func (s *JSONStore) Delete(id string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
items, err := s.readAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filtered := make([]models.Product, 0, len(items))
|
|
for _, item := range items {
|
|
if item.ID != id {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
if err := s.writeAll(filtered); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *JSONStore) readAll() ([]models.Product, error) {
|
|
bytes, err := os.ReadFile(s.path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read products: %w", err)
|
|
}
|
|
var items []models.Product
|
|
if err := json.Unmarshal(bytes, &items); err != nil {
|
|
return nil, fmt.Errorf("parse products: %w", err)
|
|
}
|
|
for i, item := range items {
|
|
items[i] = normalizeProduct(item)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (s *JSONStore) writeAll(items []models.Product) error {
|
|
for i, item := range items {
|
|
items[i] = normalizeProduct(item)
|
|
}
|
|
bytes, err := json.MarshalIndent(items, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("encode products: %w", err)
|
|
}
|
|
if err := os.WriteFile(s.path, bytes, 0o644); err != nil {
|
|
return fmt.Errorf("write products: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeProduct(item models.Product) models.Product {
|
|
item.CoverURL = strings.TrimSpace(item.CoverURL)
|
|
if item.CoverURL == "" {
|
|
item.CoverURL = defaultCoverURL
|
|
}
|
|
if item.ScreenshotURLs == nil {
|
|
item.ScreenshotURLs = []string{}
|
|
}
|
|
return item
|
|
}
|