Files
m8sh/database/leveldb/posts.go
T
2026-06-06 19:20:38 +03:00

153 lines
3.4 KiB
Go

package leveldb
import (
"bytes"
"encoding/gob"
"fmt"
"time"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
"m8sh.su/x/m8sh/database"
)
type posts struct {
db *leveldb.DB
}
func (p *posts) Create(post database.Post) error {
ts := formatTime(post.Date)
metaKey := key(post.Author, prefixPostMeta, ts)
bodyKey := key(post.Author, prefixPostBody, ts)
var metaBuf bytes.Buffer
if err := gob.NewEncoder(&metaBuf).Encode(post); err != nil {
return fmt.Errorf("encode post for %q: %w", post.Author, err)
}
batch := new(leveldb.Batch)
defer batch.Reset()
batch.Put(metaKey, metaBuf.Bytes())
batch.Put(bodyKey, []byte(post.Body))
if err := p.db.Write(batch, woSync); err != nil {
return fmt.Errorf("store post for %q: %w", post.Author, err)
}
return nil
}
func (p *posts) Get(author string, date time.Time) (database.Post, error) {
ts := formatTime(date)
metaKey := key(author, prefixPostMeta, ts)
bodyKey := key(author, prefixPostBody, ts)
data, err := p.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.Post{}, database.ErrNotFound
}
return database.Post{}, fmt.Errorf("read post meta for %q: %w", author, err)
}
var post database.Post
if err = gob.NewDecoder(bytes.NewReader(data)).Decode(&post); err != nil {
return database.Post{}, fmt.Errorf("decode post for %q: %w", author, err)
}
body, err := p.db.Get(bodyKey, nil)
if err == nil {
post.Body = string(body)
}
return post, nil
}
func (p *posts) List(author string, limit, offset int) ([]database.Post, error) {
prefix := key(author, prefixPostMeta)
iter := p.db.NewIterator(util.BytesPrefix(prefix), nil)
defer iter.Release()
var posts []database.Post
skipped := 0
for iter.Next() {
var post database.Post
if err := gob.NewDecoder(bytes.NewReader(iter.Value())).Decode(&post); err != nil {
return nil, fmt.Errorf("decode post for %q: %w", author, err)
}
if skipped < offset {
skipped++
continue
}
posts = append(posts, post)
if len(posts) >= limit {
break
}
}
return posts, nil
}
func (p *posts) Update(post database.Post) error {
ts := formatTime(post.Date)
metaKey := key(post.Author, prefixPostMeta, ts)
_, err := p.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.ErrNotFound
}
return fmt.Errorf("check post for %q: %w", post.Author, err)
}
bodyKey := key(post.Author, prefixPostBody, ts)
var metaBuf bytes.Buffer
if err = gob.NewEncoder(&metaBuf).Encode(post); err != nil {
return fmt.Errorf("encode post for %q: %w", post.Author, err)
}
batch := new(leveldb.Batch)
defer batch.Reset()
batch.Put(metaKey, metaBuf.Bytes())
batch.Put(bodyKey, []byte(post.Body))
if err = p.db.Write(batch, woSync); err != nil {
return fmt.Errorf("update post for %q: %w", post.Author, err)
}
return nil
}
func (p *posts) Delete(author string, date time.Time) error {
ts := formatTime(date)
metaKey := key(author, prefixPostMeta, ts)
bodyKey := key(author, prefixPostBody, ts)
_, err := p.db.Get(metaKey, nil)
if err != nil {
if err == leveldb.ErrNotFound {
return database.ErrNotFound
}
return fmt.Errorf("check post for %q: %w", author, err)
}
batch := new(leveldb.Batch)
defer batch.Reset()
batch.Delete(metaKey)
batch.Delete(bodyKey)
if err = p.db.Write(batch, woSync); err != nil {
return fmt.Errorf("delete post for %q: %w", author, err)
}
return nil
}