package store import ( "encoding/json" "fmt" "os" "path/filepath" "reflect" "sync" ) // Store provides generic storage operations type Store[T any] interface { LoadFromJSON(filename string) error SaveToJSON(filename string) error LoadData(dataPath string) error SaveData(dataPath string) error } // BaseStore provides generic JSON persistence type BaseStore[T any] struct { items map[int]*T maxID int mu sync.RWMutex itemType reflect.Type } // NewBaseStore creates a new base store for type T func NewBaseStore[T any]() *BaseStore[T] { var zero T return &BaseStore[T]{ items: make(map[int]*T), maxID: 0, itemType: reflect.TypeOf(zero), } } // GetNextID returns the next available ID atomically func (bs *BaseStore[T]) GetNextID() int { bs.mu.Lock() defer bs.mu.Unlock() bs.maxID++ return bs.maxID } // GetByID retrieves an item by ID func (bs *BaseStore[T]) GetByID(id int) (*T, bool) { bs.mu.RLock() defer bs.mu.RUnlock() item, exists := bs.items[id] return item, exists } // Add adds an item to the store func (bs *BaseStore[T]) Add(id int, item *T) { bs.mu.Lock() defer bs.mu.Unlock() bs.items[id] = item if id > bs.maxID { bs.maxID = id } } // Remove removes an item from the store func (bs *BaseStore[T]) Remove(id int) { bs.mu.Lock() defer bs.mu.Unlock() delete(bs.items, id) } // GetAll returns all items func (bs *BaseStore[T]) GetAll() map[int]*T { bs.mu.RLock() defer bs.mu.RUnlock() result := make(map[int]*T, len(bs.items)) for k, v := range bs.items { result[k] = v } return result } // Clear removes all items func (bs *BaseStore[T]) Clear() { bs.mu.Lock() defer bs.mu.Unlock() bs.items = make(map[int]*T) bs.maxID = 0 } // LoadFromJSON loads items from JSON using reflection func (bs *BaseStore[T]) LoadFromJSON(filename string) error { bs.mu.Lock() defer bs.mu.Unlock() data, err := os.ReadFile(filename) if err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("failed to read JSON: %w", err) } if len(data) == 0 { return nil } // Create slice of pointers to T sliceType := reflect.SliceOf(reflect.PointerTo(bs.itemType)) slicePtr := reflect.New(sliceType) if err := json.Unmarshal(data, slicePtr.Interface()); err != nil { return fmt.Errorf("failed to unmarshal JSON: %w", err) } // Clear existing data bs.items = make(map[int]*T) bs.maxID = 0 // Extract items using reflection slice := slicePtr.Elem() for i := 0; i < slice.Len(); i++ { item := slice.Index(i).Interface().(*T) // Get ID using reflection itemValue := reflect.ValueOf(item).Elem() idField := itemValue.FieldByName("ID") if !idField.IsValid() { return fmt.Errorf("item type must have an ID field") } id := int(idField.Int()) bs.items[id] = item if id > bs.maxID { bs.maxID = id } } return nil } // SaveToJSON saves items to JSON atomically func (bs *BaseStore[T]) SaveToJSON(filename string) error { bs.mu.RLock() defer bs.mu.RUnlock() items := make([]*T, 0, len(bs.items)) for _, item := range bs.items { items = append(items, item) } data, err := json.MarshalIndent(items, "", " ") if err != nil { return fmt.Errorf("failed to marshal to JSON: %w", err) } // Atomic write tempFile := filename + ".tmp" if err := os.WriteFile(tempFile, data, 0644); err != nil { return fmt.Errorf("failed to write temp JSON: %w", err) } if err := os.Rename(tempFile, filename); err != nil { os.Remove(tempFile) return fmt.Errorf("failed to rename temp JSON: %w", err) } return nil } // LoadData loads from JSON file or starts empty func (bs *BaseStore[T]) LoadData(dataPath string) error { if err := bs.LoadFromJSON(dataPath); err != nil { if os.IsNotExist(err) { fmt.Println("No existing data found, starting with empty store") return nil } return fmt.Errorf("failed to load from JSON: %w", err) } fmt.Printf("Loaded %d items from JSON\n", len(bs.items)) return nil } // SaveData saves to JSON file func (bs *BaseStore[T]) SaveData(dataPath string) error { // Ensure directory exists dataDir := filepath.Dir(dataPath) if err := os.MkdirAll(dataDir, 0755); err != nil { return fmt.Errorf("failed to create data directory: %w", err) } if err := bs.SaveToJSON(dataPath); err != nil { return fmt.Errorf("failed to save to JSON: %w", err) } fmt.Printf("Saved %d items to JSON\n", len(bs.items)) return nil }