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 }