mirror of
https://github.com/charmbracelet/glow.git
synced 2026-08-09 17:59:10 +02:00
221 lines
5.5 KiB
Go
221 lines
5.5 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/charm/kv"
|
|
"github.com/dgraph-io/badger/v3"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Client provides the Glow interface to the Charm Cloud
|
|
type Client struct {
|
|
kv *kv.KV
|
|
}
|
|
|
|
var stashPrefix = []byte("stash_")
|
|
|
|
// ErrorPageOutOfBounds is an error for an invalid page number.
|
|
var ErrorPageOutOfBounds = errors.New("page must be a value of 1 or greater")
|
|
|
|
// MarkdownsByCreatedAtDesc sorts markdown documents by date in descending
|
|
// order. It implements sort.Interface for []Markdown based on the CreatedAt
|
|
// field.
|
|
type MarkdownsByCreatedAtDesc []*Markdown
|
|
|
|
// Sort implementation for MarkdownByCreatedAt.
|
|
func (m MarkdownsByCreatedAtDesc) Len() int { return len(m) }
|
|
func (m MarkdownsByCreatedAtDesc) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
|
func (m MarkdownsByCreatedAtDesc) Less(i, j int) bool { return m[i].CreatedAt.After(m[j].CreatedAt) }
|
|
|
|
// Markdown is the struct that contains the markdown and note data. If
|
|
// EncryptKeyID is not blank, the content should be assumed to be encrypted.
|
|
// Once decrypted, that field will be blanked.
|
|
type Markdown struct {
|
|
ID string `json:"id"`
|
|
Note string `json:"note"`
|
|
Body string `json:"body,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// NewClient creates a new Client with the default settings
|
|
func NewClient() (*Client, error) {
|
|
kv, err := kv.OpenWithDefaults("charm.sh.glow")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = kv.Sync()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Client{kv: kv}, nil
|
|
}
|
|
|
|
// GetNews returns the Glow paginated news results.
|
|
func (cc *Client) GetNews(page int) ([]*Markdown, error) {
|
|
if page < 1 {
|
|
return nil, ErrorPageOutOfBounds
|
|
}
|
|
var news []*Markdown
|
|
// err := cc.makeAPIRequest("GET", fmt.Sprintf("news?page=%d", page), nil, &news)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
return news, nil
|
|
}
|
|
|
|
// GetNewsMarkdown returns the Markdown struct for the given news markdown ID.
|
|
func (cc *Client) GetNewsMarkdown(markdownID string) (*Markdown, error) {
|
|
var md Markdown
|
|
// err := cc.makeAPIRequest("GET", fmt.Sprintf("news/%d", markdownID), nil, &md)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
return &md, nil
|
|
}
|
|
|
|
// GetStash returns the paginated user stash for the authenticated Charm user.
|
|
func (cc *Client) GetStash(page int) ([]*Markdown, error) {
|
|
if page < 1 {
|
|
return nil, ErrorPageOutOfBounds
|
|
}
|
|
limit := 50
|
|
startOffset := (page * limit) - limit
|
|
endOffset := page * limit
|
|
var stash MarkdownsByCreatedAtDesc
|
|
err := cc.kv.View(func(txn *badger.Txn) error {
|
|
opt := badger.DefaultIteratorOptions
|
|
opt.Prefix = stashPrefix
|
|
it := txn.NewIterator(opt)
|
|
defer it.Close()
|
|
for it.Seek(stashPrefix); it.ValidForPrefix(stashPrefix); it.Next() {
|
|
item := it.Item()
|
|
err := item.Value(func(v []byte) error {
|
|
md := &Markdown{}
|
|
err := json.Unmarshal(v, md)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stash = append(stash, md)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(stash) > endOffset {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Sort(stash)
|
|
if startOffset >= len(stash) {
|
|
return []*Markdown{}, nil
|
|
}
|
|
if endOffset > len(stash) {
|
|
return stash[startOffset:], nil
|
|
}
|
|
return stash[startOffset:endOffset], nil
|
|
}
|
|
|
|
// GetStashMarkdown returns the Markdown struct for the given stash markdown ID.
|
|
func (cc *Client) GetStashMarkdown(markdownID string) (*Markdown, error) {
|
|
var md Markdown
|
|
d, err := cc.kv.Get([]byte(markdownID))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = json.Unmarshal(d, &md)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &md, nil
|
|
}
|
|
|
|
// StashMarkdown encrypts and stashes a new markdown file with note.
|
|
func (cc *Client) StashMarkdown(note string, body string) (*Markdown, error) {
|
|
gid := uuid.New().String()
|
|
md := &Markdown{Note: note, Body: body, ID: gid, CreatedAt: time.Now()}
|
|
err := cc.saveMarkdown(md)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return md, nil
|
|
}
|
|
|
|
// DeleteMarkdown deletes the stash markdown for the given ID.
|
|
func (cc *Client) DeleteMarkdown(markdownID string) error {
|
|
txn, err := cc.kv.NewTransaction(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mid, sid := markdownKeys(markdownID)
|
|
err = txn.Delete(mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = txn.Delete(sid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return cc.kv.Commit(txn, func(err error) {
|
|
if err != nil {
|
|
log.Printf("Badger commit error: %s", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// SetMarkdownNote updates the note for a given stash markdown ID.
|
|
func (cc *Client) SetMarkdownNote(markdownID string, note string) error {
|
|
md, err := cc.GetStashMarkdown(markdownID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
md.Note = note
|
|
return cc.saveMarkdown(md)
|
|
}
|
|
|
|
func (cc *Client) saveMarkdown(md *Markdown) error {
|
|
mid, sid := markdownKeys(md.ID)
|
|
txn, err := cc.kv.NewTransaction(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf := bytes.NewBuffer(nil)
|
|
err = json.NewEncoder(buf).Encode(md)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = txn.Set(mid, buf.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
buf = bytes.NewBuffer(nil)
|
|
smd := &Markdown{ID: md.ID, Note: md.Note, CreatedAt: md.CreatedAt}
|
|
err = json.NewEncoder(buf).Encode(smd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = txn.Set(sid, buf.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return cc.kv.Commit(txn, func(err error) {
|
|
if err != nil {
|
|
log.Printf("Badger commit error: %s", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func markdownKeys(markdownID string) ([]byte, []byte) {
|
|
return []byte(markdownID), []byte(fmt.Sprintf("%s%s", stashPrefix, markdownID))
|
|
}
|