package storage import ( "encoding/binary" "encoding/json" "fmt" "os" "path/filepath" "time" bolt "go.etcd.io/bbolt" ) var ( bucketAppConfig = []byte("AppConfig") bucketCategories = []byte("Categories") bucketEntries = []byte("Entries") keyTocMsgID = []byte("toc_msg_id") ) type Category struct { Name string `json:"name"` Order int `json:"order"` CreatedAt time.Time `json:"created_at"` } type Entry struct { ID string `json:"id"` Category string `json:"category"` Title string `json:"title"` Link string `json:"link"` Timestamp time.Time `json:"timestamp"` } type Storage struct { db *bolt.DB } func New(path string) (*Storage, error) { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { return nil, fmt.Errorf("failed to create data directory: %w", err) } db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second}) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) } s := &Storage{db: db} if err := s.initBuckets(); err != nil { db.Close() return nil, err } return s, nil } func (s *Storage) initBuckets() error { return s.db.Update(func(tx *bolt.Tx) error { buckets := [][]byte{bucketAppConfig, bucketCategories, bucketEntries} for _, b := range buckets { if _, err := tx.CreateBucketIfNotExists(b); err != nil { return fmt.Errorf("failed to create bucket %s: %w", b, err) } } return nil }) } func (s *Storage) Close() error { return s.db.Close() } func (s *Storage) GetTocMsgID() (int, error) { var msgID int err := s.db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucketAppConfig) v := b.Get(keyTocMsgID) if v != nil && len(v) >= 4 { msgID = int(binary.BigEndian.Uint32(v)) } return nil }) return msgID, err } func (s *Storage) SetTocMsgID(msgID int) error { return s.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucketAppConfig) buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, uint32(msgID)) return b.Put(keyTocMsgID, buf) }) } func encodeJSON(v any) ([]byte, error) { return json.Marshal(v) } func decodeJSON(data []byte, v any) error { return json.Unmarshal(data, v) }