tinyrelayfile
tinyrelay/internal/records/records.go/

internal/records/records.go raw
1package records
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "strconv"
8 "sync"
9 "time"
10
11 "github.com/FelineStateMachine/tinyrelay/internal/communityread"
12 "github.com/FelineStateMachine/tinyrelay/internal/event"
13 "github.com/FelineStateMachine/tinyrelay/internal/policy"
14 "github.com/FelineStateMachine/tinyrelay/internal/storage"
15)
16
17type Config struct {
18 // Store holds the tenant database and records settings.
19 Store *storage.Store
20 // Policy returns the current durable tenant policy when records evaluates
21 // access or builds a projection.
22 Policy func() policy.Policy
23 // SetPolicy persists a policy change requested by records management.
24 SetPolicy func(policy.Policy) error
25 RelayURL string
26 GroupID string
27 // OnGenerated receives a signed event after its records clock transaction
28 // commits. The callback commonly persists, publishes or queues the event.
29 OnGenerated func(context.Context, event.Event) error
30 // DeliverNotification delivers a sealed notification event to its target.
31 DeliverNotification func(context.Context, event.Event, string) error
32 // PushNotification receives a plaintext summary of each notification for
33 // device delivery. The gift wrap itself is opaque to the relay once sealed.
34 PushNotification func(context.Context, string, string, string, string) error
35 OnTransfer func(context.Context, string, string) error
36 // Community supplies membership and moderation projections through the
37 // CommunityReader boundary.
38 Community CommunityReader
39}
40
41// CommunityReader is the read-only boundary records needs from community.
42// Implementations own the community schema and may use a transaction-backed
43// snapshot when the host is publishing a projection.
44type CommunityReader interface {
45 Members(ctx context.Context) ([]communityread.Member, error)
46 ModerationCounts(ctx context.Context, since int64) (communityread.ModerationCounts, error)
47 MemberStatus(ctx context.Context, pubkey string) (member bool, memberCount int, err error)
48}
49
50type Member = communityread.Member
51type ModerationCounts = communityread.ModerationCounts
52
53type Service struct {
54 store *storage.Store
55 policy func() policy.Policy
56 setPolicy func(policy.Policy) error
57 relayURL string
58 groupID string
59 secret string
60 onGenerated func(context.Context, event.Event) error
61 deliverNotification func(context.Context, event.Event, string) error
62 pushNotification func(context.Context, string, string, string, string) error
63 onTransfer func(context.Context, string, string) error
64 community CommunityReader
65 mu sync.Mutex
66}
67
68type MembershipChange struct {
69 PubKey string
70 Added *bool
71 Roles []string
72}
73
74const secretSetting = "records.relay_secret"
75
76const recordsSchema = `
77CREATE TABLE IF NOT EXISTS records_audit(
78 id INTEGER PRIMARY KEY AUTOINCREMENT, at INTEGER NOT NULL, actor TEXT NOT NULL,
79 action TEXT NOT NULL, target TEXT NOT NULL DEFAULT '', detail TEXT NOT NULL DEFAULT '');
80CREATE TABLE IF NOT EXISTS records_presence(
81 pubkey TEXT PRIMARY KEY, seen_at INTEGER NOT NULL);
82CREATE TABLE IF NOT EXISTS records_pins(
83 position INTEGER PRIMARY KEY, ref TEXT NOT NULL UNIQUE);
84CREATE TABLE IF NOT EXISTS records_view_runs(
85 name TEXT NOT NULL, at INTEGER NOT NULL, rows INTEGER NOT NULL);
86`
87
88func New(ctx context.Context, cfg Config) (*Service, error) {
89 if cfg.Store == nil {
90 return nil, errors.New("records: nil store")
91 }
92 if cfg.Policy == nil {
93 return nil, errors.New("records: nil policy function")
94 }
95 if cfg.Community == nil {
96 return nil, errors.New("records: nil community reader")
97 }
98 if _, err := cfg.Store.DB().ExecContext(ctx, recordsSchema); err != nil {
99 return nil, fmt.Errorf("records schema: %w", err)
100 }
101 secret, err := loadOrCreateSecret(ctx, cfg.Store)
102 if err != nil {
103 return nil, err
104 }
105 groupID := cfg.GroupID
106 if groupID == "" {
107 groupID = cfg.RelayURL
108 }
109 return &Service{store: cfg.Store, policy: cfg.Policy, setPolicy: cfg.SetPolicy, relayURL: cfg.RelayURL, groupID: groupID, secret: secret, onGenerated: cfg.OnGenerated, deliverNotification: cfg.DeliverNotification, pushNotification: cfg.PushNotification, onTransfer: cfg.OnTransfer, community: cfg.Community}, nil
110}
111
112var roleOrder = []string{"owner", "moderator", "member", "agent"}
113var rolesAbout = map[string]string{"owner": "relay owner", "moderator": "relay moderator", "member": "relay member", "agent": "agent under an owner-signed grant"}
114
115type Presence struct {
116 PubKey string `json:"pubkey"`
117 SeenAt int64 `json:"seen_at"`
118}
119
120type View struct {
121 Name string `json:"name"`
122 Event event.Event `json:"event"`
123 Rows int `json:"rows"`
124}
125
126type ViewRun struct {
127 At int64 `json:"at"`
128 Rows int `json:"rows"`
129}
130
131var viewNames = []string{"profiles", "relays", "calendar", "moderation", "articles", "zaps", "presence"}
132
133func calendarStart(e event.Event) int64 {
134 s := event.Tag(e, "start")
135 if e.Kind == 31922 {
136 t, err := time.Parse("2006-01-02", s)
137 if err != nil {
138 return 0
139 }
140 return t.Unix()
141 }
142 n, err := strconv.ParseInt(s, 10, 64)
143 if err != nil {
144 return 0
145 }
146 return n
147}
148func articlePublished(e event.Event) int64 {
149 s := event.Tag(e, "published_at")
150 if n, err := strconv.ParseInt(s, 10, 64); err == nil {
151 return n
152 }
153 return e.CreatedAt
154}
155
156func minInt(a, b int) int {
157 if a < b {
158 return a
159 }
160 return b
161}
162
163func trimString(v any, n int) string {
164 s, ok := v.(string)
165 if !ok {
166 return ""
167 }
168 if len(s) > n {
169 return s[:n]
170 }
171 return s
172}
173
174type successionLog struct {
175 At int64 `json:"at"`
176 From string `json:"from"`
177 To string `json:"to"`
178}
179
180func maxInt(a, b int) int {
181 if a > b {
182 return a
183 }
184 return b
185}
186
187var _ = strconv.IntSize

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs