feat: sync project
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -15,20 +16,26 @@ import (
|
||||
)
|
||||
|
||||
const defaultCoverURL = "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png"
|
||||
const viewCooldown = 6 * time.Hour
|
||||
const maxScreenshotURLs = 5
|
||||
|
||||
type JSONStore struct {
|
||||
path string
|
||||
mu sync.Mutex
|
||||
path string
|
||||
mu sync.Mutex
|
||||
recentViews map[string]time.Time
|
||||
}
|
||||
|
||||
func NewJSONStore(path string) (*JSONStore, error) {
|
||||
if err := ensureFile(path); err != nil {
|
||||
if err := ensureProductsFile(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &JSONStore{path: path}, nil
|
||||
return &JSONStore{
|
||||
path: path,
|
||||
recentViews: make(map[string]time.Time),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ensureFile(path string) error {
|
||||
func ensureProductsFile(path string) error {
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir data dir: %w", err)
|
||||
@@ -72,6 +79,22 @@ func (s *JSONStore) ListActive() ([]models.Product, error) {
|
||||
return active, nil
|
||||
}
|
||||
|
||||
func (s *JSONStore) GetByID(id string) (models.Product, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
items, err := s.readAll()
|
||||
if err != nil {
|
||||
return models.Product{}, err
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.ID == id {
|
||||
return item, nil
|
||||
}
|
||||
}
|
||||
return models.Product{}, fmt.Errorf("product not found")
|
||||
}
|
||||
|
||||
func (s *JSONStore) Create(p models.Product) (models.Product, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -100,13 +123,18 @@ func (s *JSONStore) Update(id string, patch models.Product) (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
|
||||
normalized := normalizeProduct(patch)
|
||||
item.Name = normalized.Name
|
||||
item.Price = normalized.Price
|
||||
item.DiscountPrice = normalized.DiscountPrice
|
||||
item.Tags = normalized.Tags
|
||||
item.CoverURL = normalized.CoverURL
|
||||
item.ScreenshotURLs = normalized.ScreenshotURLs
|
||||
item.VerificationURL = normalized.VerificationURL
|
||||
item.Codes = normalized.Codes
|
||||
item.Quantity = normalized.Quantity
|
||||
item.Description = normalized.Description
|
||||
item.Active = normalized.Active
|
||||
item.UpdatedAt = time.Now()
|
||||
items[i] = item
|
||||
if err := s.writeAll(items); err != nil {
|
||||
@@ -139,6 +167,43 @@ func (s *JSONStore) Toggle(id string, active bool) (models.Product, error) {
|
||||
return models.Product{}, fmt.Errorf("product not found")
|
||||
}
|
||||
|
||||
func (s *JSONStore) IncrementView(id, fingerprint string) (models.Product, bool, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
items, err := s.readAll()
|
||||
if err != nil {
|
||||
return models.Product{}, false, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
s.cleanupRecentViews(now)
|
||||
key := buildViewKey(id, fingerprint)
|
||||
if lastViewedAt, ok := s.recentViews[key]; ok && now.Sub(lastViewedAt) < viewCooldown {
|
||||
for _, item := range items {
|
||||
if item.ID == id {
|
||||
return item, false, nil
|
||||
}
|
||||
}
|
||||
return models.Product{}, false, fmt.Errorf("product not found")
|
||||
}
|
||||
|
||||
for i, item := range items {
|
||||
if item.ID == id {
|
||||
item.ViewCount++
|
||||
item.UpdatedAt = now
|
||||
items[i] = item
|
||||
s.recentViews[key] = now
|
||||
if err := s.writeAll(items); err != nil {
|
||||
return models.Product{}, false, err
|
||||
}
|
||||
return item, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return models.Product{}, false, fmt.Errorf("product not found")
|
||||
}
|
||||
|
||||
func (s *JSONStore) Delete(id string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -192,8 +257,75 @@ func normalizeProduct(item models.Product) models.Product {
|
||||
if item.CoverURL == "" {
|
||||
item.CoverURL = defaultCoverURL
|
||||
}
|
||||
if item.Tags == nil {
|
||||
item.Tags = []string{}
|
||||
}
|
||||
item.Tags = sanitizeTags(item.Tags)
|
||||
if item.ScreenshotURLs == nil {
|
||||
item.ScreenshotURLs = []string{}
|
||||
}
|
||||
if len(item.ScreenshotURLs) > maxScreenshotURLs {
|
||||
item.ScreenshotURLs = item.ScreenshotURLs[:maxScreenshotURLs]
|
||||
}
|
||||
if item.Codes == nil {
|
||||
item.Codes = []string{}
|
||||
}
|
||||
if item.DiscountPrice <= 0 || item.DiscountPrice >= item.Price {
|
||||
item.DiscountPrice = 0
|
||||
}
|
||||
item.VerificationURL = strings.TrimSpace(item.VerificationURL)
|
||||
item.Codes = sanitizeCodes(item.Codes)
|
||||
item.Quantity = len(item.Codes)
|
||||
return item
|
||||
}
|
||||
|
||||
func sanitizeCodes(codes []string) []string {
|
||||
clean := make([]string, 0, len(codes))
|
||||
seen := map[string]bool{}
|
||||
for _, code := range codes {
|
||||
trimmed := strings.TrimSpace(code)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
if seen[trimmed] {
|
||||
continue
|
||||
}
|
||||
seen[trimmed] = true
|
||||
clean = append(clean, trimmed)
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
||||
func sanitizeTags(tags []string) []string {
|
||||
clean := make([]string, 0, len(tags))
|
||||
seen := map[string]bool{}
|
||||
for _, tag := range tags {
|
||||
t := strings.TrimSpace(tag)
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(t)
|
||||
if seen[key] {
|
||||
continue
|
||||
}
|
||||
seen[key] = true
|
||||
clean = append(clean, t)
|
||||
if len(clean) >= 20 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
||||
func buildViewKey(id, fingerprint string) string {
|
||||
sum := sha256.Sum256([]byte(id + "|" + fingerprint))
|
||||
return fmt.Sprintf("%x", sum)
|
||||
}
|
||||
|
||||
func (s *JSONStore) cleanupRecentViews(now time.Time) {
|
||||
for key, lastViewedAt := range s.recentViews {
|
||||
if now.Sub(lastViewedAt) >= viewCooldown {
|
||||
delete(s.recentViews, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user