-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
automod: add generic caching, and hydrate some account meta
- Loading branch information
Showing
12 changed files
with
310 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package automod | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
appbsky "github.com/bluesky-social/indigo/api/bsky" | ||
"github.com/bluesky-social/indigo/atproto/identity" | ||
) | ||
|
||
type ProfileSummary struct { | ||
HasAvatar bool | ||
Description *string | ||
DisplayName *string | ||
} | ||
|
||
type AccountPrivate struct { | ||
Email string | ||
EmailConfirmed bool | ||
} | ||
|
||
// information about a repo/account/identity, always pre-populated and relevant to many rules | ||
type AccountMeta struct { | ||
Identity *identity.Identity | ||
Profile ProfileSummary | ||
Private *AccountPrivate | ||
AccountLabels []string | ||
FollowersCount int64 | ||
FollowsCount int64 | ||
PostsCount int64 | ||
IndexedAt *time.Time | ||
} | ||
|
||
func (e *Engine) GetAccountMeta(ctx context.Context, ident *identity.Identity) (*AccountMeta, error) { | ||
|
||
// wipe parsed public key; it's a waste of space and can't serialize | ||
ident.ParsedPublicKey = nil | ||
|
||
existing, err := e.Cache.Get(ctx, "acct", ident.DID.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if existing != "" { | ||
var am AccountMeta | ||
err := json.Unmarshal([]byte(existing), &am) | ||
if err != nil { | ||
return nil, fmt.Errorf("parsing AccountMeta from cache: %v", err) | ||
} | ||
am.Identity = ident | ||
return &am, nil | ||
} | ||
|
||
// fetch account metadata | ||
pv, err := appbsky.ActorGetProfile(ctx, e.BskyClient, ident.DID.String()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var labels []string | ||
for _, lbl := range pv.Labels { | ||
labels = append(labels, lbl.Val) | ||
} | ||
|
||
am := AccountMeta{ | ||
Identity: ident, | ||
Profile: ProfileSummary{ | ||
HasAvatar: pv.Avatar != nil, | ||
Description: pv.Description, | ||
DisplayName: pv.DisplayName, | ||
}, | ||
AccountLabels: dedupeStrings(labels), | ||
} | ||
if pv.PostsCount != nil { | ||
am.PostsCount = *pv.PostsCount | ||
} | ||
if pv.FollowersCount != nil { | ||
am.FollowersCount = *pv.FollowersCount | ||
} | ||
if pv.FollowsCount != nil { | ||
am.FollowsCount = *pv.FollowsCount | ||
} | ||
|
||
if e.AdminClient != nil { | ||
// XXX: get admin-level info (email, indexed at, etc). requires lexgen update | ||
} | ||
|
||
val, err := json.Marshal(&am) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := e.Cache.Set(ctx, "acct", ident.DID.String(), string(val)); err != nil { | ||
return nil, err | ||
} | ||
return &am, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package automod | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/hashicorp/golang-lru/v2/expirable" | ||
) | ||
|
||
type CacheStore interface { | ||
Get(ctx context.Context, name, key string) (string, error) | ||
Set(ctx context.Context, name, key string, val string) error | ||
} | ||
|
||
type MemCacheStore struct { | ||
Data *expirable.LRU[string, string] | ||
} | ||
|
||
func NewMemCacheStore(capacity int, ttl time.Duration) MemCacheStore { | ||
return MemCacheStore{ | ||
Data: expirable.NewLRU[string, string](capacity, nil, ttl), | ||
} | ||
} | ||
|
||
func (s MemCacheStore) Get(ctx context.Context, name, key string) (string, error) { | ||
v, ok := s.Data.Get(name + "/" + key) | ||
if !ok { | ||
return "", nil | ||
} | ||
return v, nil | ||
} | ||
|
||
func (s MemCacheStore) Set(ctx context.Context, name, key string, val string) error { | ||
s.Data.Add(name+"/"+key, val) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package automod | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/go-redis/cache/v9" | ||
"github.com/redis/go-redis/v9" | ||
) | ||
|
||
type RedisCacheStore struct { | ||
Data *cache.Cache | ||
TTL time.Duration | ||
} | ||
|
||
var _ CacheStore = (*RedisCacheStore)(nil) | ||
|
||
func NewRedisCacheStore(redisURL string, ttl time.Duration) (*RedisCacheStore, error) { | ||
opt, err := redis.ParseURL(redisURL) | ||
if err != nil { | ||
return nil, err | ||
} | ||
rdb := redis.NewClient(opt) | ||
// check redis connection | ||
_, err = rdb.Ping(context.TODO()).Result() | ||
if err != nil { | ||
return nil, err | ||
} | ||
data := cache.New(&cache.Options{ | ||
Redis: rdb, | ||
LocalCache: cache.NewTinyLFU(10_000, ttl), | ||
}) | ||
return &RedisCacheStore{ | ||
Data: data, | ||
TTL: ttl, | ||
}, nil | ||
} | ||
|
||
func redisCacheKey(name, key string) string { | ||
return "cache/" + name + "/" + key | ||
} | ||
|
||
func (s RedisCacheStore) Get(ctx context.Context, name, key string) (string, error) { | ||
var val string | ||
err := s.Data.Get(ctx, redisCacheKey(name, key), &val) | ||
if err == cache.ErrCacheMiss { | ||
return "", nil | ||
} | ||
if err != nil { | ||
return "", err | ||
} | ||
return val, nil | ||
} | ||
|
||
func (s RedisCacheStore) Set(ctx context.Context, name, key string, val string) error { | ||
s.Data.Set(&cache.Item{ | ||
Ctx: ctx, | ||
Key: redisCacheKey(name, key), | ||
Value: val, | ||
TTL: s.TTL, | ||
}) | ||
return nil | ||
} |
Oops, something went wrong.