98 lines
2.4 KiB
Go
98 lines
2.4 KiB
Go
|
|
package adapter
|
||
|
|
|
||
|
|
import (
|
||
|
|
"sort"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// State is a publication's lifecycle, per contracts/r1.openapi.yaml's `state`
|
||
|
|
// enum on listPosts.
|
||
|
|
type State string
|
||
|
|
|
||
|
|
const (
|
||
|
|
StatePending State = "pending"
|
||
|
|
StatePublished State = "published"
|
||
|
|
StateFailed State = "failed"
|
||
|
|
StateSuperseded State = "superseded"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Publication is this adapter's own record of a post_id: which Telegram
|
||
|
|
// message implements it. The evidence store records that a publication
|
||
|
|
// happened; this is the adapter's mapping, kept separately per
|
||
|
|
// docs/adapter-contract.md.
|
||
|
|
type Publication struct {
|
||
|
|
PostID string
|
||
|
|
State State
|
||
|
|
TelegramMsgID int
|
||
|
|
Body string // the rendered text or caption last sent, for edit-detection
|
||
|
|
SourceEntryID string
|
||
|
|
Variant string
|
||
|
|
PublishedAt time.Time
|
||
|
|
LastAttemptErr string
|
||
|
|
}
|
||
|
|
|
||
|
|
// Store is the adapter's idempotency mapping from post_id to what was sent.
|
||
|
|
// It is the adapter's own state, never fluid-core's.
|
||
|
|
type Store interface {
|
||
|
|
Get(postID string) (Publication, bool)
|
||
|
|
Put(Publication)
|
||
|
|
List(filter ListFilter) []Publication
|
||
|
|
}
|
||
|
|
|
||
|
|
// ListFilter narrows GET /v1/channel-posts, mirroring its query parameters.
|
||
|
|
type ListFilter struct {
|
||
|
|
State State
|
||
|
|
Variant string
|
||
|
|
SourceEntryID string
|
||
|
|
Limit int
|
||
|
|
}
|
||
|
|
|
||
|
|
type memStore struct {
|
||
|
|
mu sync.RWMutex
|
||
|
|
byID map[string]Publication
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewMemStore is an in-process idempotency store. It is what one adapter
|
||
|
|
// instance needs for one channel; nothing here is shared with fluid-core or
|
||
|
|
// with another channel's adapter instance.
|
||
|
|
func NewMemStore() Store {
|
||
|
|
return &memStore{byID: map[string]Publication{}}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *memStore) Get(postID string) (Publication, bool) {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
p, ok := s.byID[postID]
|
||
|
|
return p, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *memStore) Put(p Publication) {
|
||
|
|
s.mu.Lock()
|
||
|
|
defer s.mu.Unlock()
|
||
|
|
s.byID[p.PostID] = p
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *memStore) List(f ListFilter) []Publication {
|
||
|
|
s.mu.RLock()
|
||
|
|
defer s.mu.RUnlock()
|
||
|
|
out := make([]Publication, 0, len(s.byID))
|
||
|
|
for _, p := range s.byID {
|
||
|
|
if f.State != "" && p.State != f.State {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if f.Variant != "" && p.Variant != f.Variant {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if f.SourceEntryID != "" && p.SourceEntryID != f.SourceEntryID {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
out = append(out, p)
|
||
|
|
}
|
||
|
|
sort.Slice(out, func(i, j int) bool { return out[i].PublishedAt.After(out[j].PublishedAt) })
|
||
|
|
if f.Limit > 0 && len(out) > f.Limit {
|
||
|
|
out = out[:f.Limit]
|
||
|
|
}
|
||
|
|
return out
|
||
|
|
}
|