清理目录链接功能提交中的 CRLF
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/teris-io/shortid"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/teris-io/shortid"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
func (s *Storage) CreateEntry(category, title, channelLink, directLink string) (*Entry, error) {
|
||||
entry := &Entry{
|
||||
ID: shortid.MustGenerate(),
|
||||
@@ -19,131 +19,131 @@ func (s *Storage) CreateEntry(category, title, channelLink, directLink string) (
|
||||
DirectLink: directLink,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(entry.ID), data)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetEntry(id string) (*Entry, error) {
|
||||
var entry *Entry
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
entry = &Entry{}
|
||||
return decodeJSON(data, entry)
|
||||
})
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteEntry(id string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
if b.Get([]byte(id)) == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
return b.Delete([]byte(id))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateEntryTitle(id, title string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
|
||||
var entry Entry
|
||||
if err := decodeJSON(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry.Title = title
|
||||
newData, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(id), newData)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateEntryCategory(id, category string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
|
||||
var entry Entry
|
||||
if err := decodeJSON(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry.Category = category
|
||||
newData, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(id), newData)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) ListEntries(category string) ([]Entry, error) {
|
||||
var entries []Entry
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
return b.ForEach(func(k, v []byte) error {
|
||||
var entry Entry
|
||||
if err := decodeJSON(v, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if category == "" || entry.Category == category {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].Timestamp.Before(entries[j].Timestamp)
|
||||
})
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (s *Storage) ListAllEntries() ([]Entry, error) {
|
||||
return s.ListEntries("")
|
||||
}
|
||||
|
||||
func (s *Storage) GetEntriesByCategory() (map[string][]Entry, error) {
|
||||
entries, err := s.ListAllEntries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string][]Entry)
|
||||
for _, e := range entries {
|
||||
result[e.Category] = append(result[e.Category], e)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(entry.ID), data)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (s *Storage) GetEntry(id string) (*Entry, error) {
|
||||
var entry *Entry
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
entry = &Entry{}
|
||||
return decodeJSON(data, entry)
|
||||
})
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func (s *Storage) DeleteEntry(id string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
if b.Get([]byte(id)) == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
return b.Delete([]byte(id))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateEntryTitle(id, title string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
|
||||
var entry Entry
|
||||
if err := decodeJSON(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry.Title = title
|
||||
newData, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(id), newData)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) UpdateEntryCategory(id, category string) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
data := b.Get([]byte(id))
|
||||
if data == nil {
|
||||
return fmt.Errorf("entry %q not found", id)
|
||||
}
|
||||
|
||||
var entry Entry
|
||||
if err := decodeJSON(data, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entry.Category = category
|
||||
newData, err := encodeJSON(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return b.Put([]byte(id), newData)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) ListEntries(category string) ([]Entry, error) {
|
||||
var entries []Entry
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketEntries)
|
||||
return b.ForEach(func(k, v []byte) error {
|
||||
var entry Entry
|
||||
if err := decodeJSON(v, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if category == "" || entry.Category == category {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].Timestamp.Before(entries[j].Timestamp)
|
||||
})
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (s *Storage) ListAllEntries() ([]Entry, error) {
|
||||
return s.ListEntries("")
|
||||
}
|
||||
|
||||
func (s *Storage) GetEntriesByCategory() (map[string][]Entry, error) {
|
||||
entries, err := s.ListAllEntries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string][]Entry)
|
||||
for _, e := range entries {
|
||||
result[e.Category] = append(result[e.Category], e)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package storage
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
@@ -8,25 +8,25 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var (
|
||||
bucketAppConfig = []byte("AppConfig")
|
||||
bucketCategories = []byte("Categories")
|
||||
bucketEntries = []byte("Entries")
|
||||
bucketAdmins = []byte("Admins")
|
||||
|
||||
keyTocMsgID = []byte("toc_msg_id")
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
Name string `json:"name"`
|
||||
Order int `json:"order"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
var (
|
||||
bucketAppConfig = []byte("AppConfig")
|
||||
bucketCategories = []byte("Categories")
|
||||
bucketEntries = []byte("Entries")
|
||||
bucketAdmins = []byte("Admins")
|
||||
|
||||
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"`
|
||||
@@ -48,120 +48,120 @@ func (e Entry) HasDirectLink() bool {
|
||||
direct := strings.TrimSpace(e.DirectLink)
|
||||
return direct != "" && direct != e.MessageLink()
|
||||
}
|
||||
|
||||
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, bucketAdmins}
|
||||
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)
|
||||
}
|
||||
|
||||
// Admin management
|
||||
|
||||
func (s *Storage) AddAdmin(userID int64) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
return b.Put(key, []byte("1"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) RemoveAdmin(userID int64) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
return b.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) IsAdmin(userID int64) bool {
|
||||
var exists bool
|
||||
s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
exists = b.Get(key) != nil
|
||||
return nil
|
||||
})
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s *Storage) ListAdmins() ([]int64, error) {
|
||||
var admins []int64
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
return b.ForEach(func(k, v []byte) error {
|
||||
if len(k) == 8 {
|
||||
userID := int64(binary.BigEndian.Uint64(k))
|
||||
admins = append(admins, userID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return admins, err
|
||||
}
|
||||
|
||||
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, bucketAdmins}
|
||||
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)
|
||||
}
|
||||
|
||||
// Admin management
|
||||
|
||||
func (s *Storage) AddAdmin(userID int64) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
return b.Put(key, []byte("1"))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) RemoveAdmin(userID int64) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
return b.Delete(key)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Storage) IsAdmin(userID int64) bool {
|
||||
var exists bool
|
||||
s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
key := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(key, uint64(userID))
|
||||
exists = b.Get(key) != nil
|
||||
return nil
|
||||
})
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s *Storage) ListAdmins() ([]int64, error) {
|
||||
var admins []int64
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(bucketAdmins)
|
||||
return b.ForEach(func(k, v []byte) error {
|
||||
if len(k) == 8 {
|
||||
userID := int64(binary.BigEndian.Uint64(k))
|
||||
admins = append(admins, userID)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return admins, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user