tinyrelayfile
tinyrelay/internal/community/config.go/

internal/community/config.go raw
1package community
2
3import (
4 "context"
5 "database/sql"
6 "time"
7)
8
9// ConfigSnapshot is the community-owned portion of relay configuration.
10// The type deliberately contains no configport types so the domain owns its
11// storage contract.
12type ConfigSnapshot struct {
13 Members []Member
14 Bans []ConfigBan
15 IPBlocks []ConfigIPBlock
16 EventBans []ConfigEventBan
17 KindRules []ConfigKindRule
18 Retention []ConfigRetention
19}
20
21type ConfigBan struct {
22 PubKey string
23 Reason string
24}
25
26type ConfigIPBlock struct {
27 IP string
28 Reason string
29}
30
31type ConfigEventBan struct {
32 ID string
33 Reason string
34}
35
36type ConfigKindRule struct {
37 Kind int
38 Rule string
39}
40
41type ConfigRetention struct {
42 Kind *int
43 Days int
44}
45
46type ConfigMemberRetention struct {
47 PubKey string
48 Days int
49}
50
51// RetentionRules returns the community-owned retention settings.
52func (s *Service) RetentionRules(ctx context.Context) ([]ConfigRetention, error) {
53 rows, err := s.store.DB().QueryContext(ctx, `SELECT kind,days FROM community_retention ORDER BY kind`)
54 if err != nil {
55 return nil, err
56 }
57 defer rows.Close()
58 var out []ConfigRetention
59 for rows.Next() {
60 var kind, days int
61 if err := rows.Scan(&kind, &days); err != nil {
62 return nil, err
63 }
64 out = append(out, ConfigRetention{Kind: &kind, Days: days})
65 }
66 return out, rows.Err()
67}
68
69// MembersForRetention returns members with an explicit retention override.
70func (s *Service) MembersForRetention(ctx context.Context) ([]ConfigMemberRetention, error) {
71 rows, err := s.store.DB().QueryContext(ctx, `SELECT pubkey,keep_days FROM community_members WHERE keep_days>0 ORDER BY pubkey`)
72 if err != nil {
73 return nil, err
74 }
75 defer rows.Close()
76 var out []ConfigMemberRetention
77 for rows.Next() {
78 var member ConfigMemberRetention
79 if err := rows.Scan(&member.PubKey, &member.Days); err != nil {
80 return nil, err
81 }
82 out = append(out, member)
83 }
84 return out, rows.Err()
85}
86
87// ExportConfig reads the community-owned configuration tables.
88func ExportConfig(ctx context.Context, store interface {
89 DB() *sql.DB
90}) (ConfigSnapshot, error) {
91 var out ConfigSnapshot
92 rows, err := store.DB().QueryContext(ctx, `SELECT pubkey,name,note,role,invited_by,via,created_at,keep_days FROM community_members WHERE role<>'owner' ORDER BY pubkey`)
93 if err != nil {
94 return out, err
95 }
96 defer rows.Close()
97 for rows.Next() {
98 var member Member
99 if err := rows.Scan(&member.PubKey, &member.Name, &member.Note, &member.Role, &member.InvitedBy, &member.Via, &member.CreatedAt, &member.KeepDays); err != nil {
100 return out, err
101 }
102 out.Members = append(out.Members, member)
103 }
104 if err := rows.Err(); err != nil {
105 return out, err
106 }
107 if err := readConfigRows(ctx, store.DB(), `SELECT pubkey,reason FROM community_bans ORDER BY pubkey`, func(rows *sql.Rows) error {
108 var item ConfigBan
109 if err := rows.Scan(&item.PubKey, &item.Reason); err != nil {
110 return err
111 }
112 out.Bans = append(out.Bans, item)
113 return nil
114 }); err != nil {
115 return out, err
116 }
117 if err := readConfigRows(ctx, store.DB(), `SELECT ip,reason FROM community_ip_blocks ORDER BY ip`, func(rows *sql.Rows) error {
118 var item ConfigIPBlock
119 if err := rows.Scan(&item.IP, &item.Reason); err != nil {
120 return err
121 }
122 out.IPBlocks = append(out.IPBlocks, item)
123 return nil
124 }); err != nil {
125 return out, err
126 }
127 if err := readConfigRows(ctx, store.DB(), `SELECT id,reason FROM community_event_bans ORDER BY id`, func(rows *sql.Rows) error {
128 var item ConfigEventBan
129 if err := rows.Scan(&item.ID, &item.Reason); err != nil {
130 return err
131 }
132 out.EventBans = append(out.EventBans, item)
133 return nil
134 }); err != nil {
135 return out, err
136 }
137 if err := readConfigRows(ctx, store.DB(), `SELECT kind,rule FROM community_kind_rules ORDER BY kind`, func(rows *sql.Rows) error {
138 var item ConfigKindRule
139 if err := rows.Scan(&item.Kind, &item.Rule); err != nil {
140 return err
141 }
142 out.KindRules = append(out.KindRules, item)
143 return nil
144 }); err != nil {
145 return out, err
146 }
147 if err := readConfigRows(ctx, store.DB(), `SELECT kind,days FROM community_retention ORDER BY kind`, func(rows *sql.Rows) error {
148 var kind, days int
149 if err := rows.Scan(&kind, &days); err != nil {
150 return err
151 }
152 if kind < 0 {
153 out.Retention = append(out.Retention, ConfigRetention{Days: days})
154 return nil
155 }
156 out.Retention = append(out.Retention, ConfigRetention{Kind: &kind, Days: days})
157 return nil
158 }); err != nil {
159 return out, err
160 }
161 return out, nil
162}
163
164func readConfigRows(ctx context.Context, db *sql.DB, query string, scan func(*sql.Rows) error) error {
165 rows, err := db.QueryContext(ctx, query)
166 if err != nil {
167 return err
168 }
169 defer rows.Close()
170 for rows.Next() {
171 if err := scan(rows); err != nil {
172 return err
173 }
174 }
175 return rows.Err()
176}
177
178// ApplyConfigTx replaces each selected community configuration section inside
179// the caller-owned transaction. A selected empty section clears that section;
180// an unselected section is left unchanged. The caller commits or rolls back
181// the transaction after all related configuration writes succeed.
182func ApplyConfigTx(ctx context.Context, tx *sql.Tx, c ConfigSnapshot, members, bans, addresses, eventBans, kinds, retention bool) error {
183 now := time.Now().Unix()
184 if members {
185 if _, err := tx.ExecContext(ctx, `DELETE FROM community_members WHERE role<>'owner'`); err != nil {
186 return err
187 }
188 for _, member := range c.Members {
189 if _, err := tx.ExecContext(ctx, `INSERT INTO community_members(pubkey,name,note,role,invited_by,via,created_at,keep_days) VALUES(?,?,?,?,?,?,?,?)`, member.PubKey, member.Name, member.Note, member.Role, member.InvitedBy, member.Via, now, member.KeepDays); err != nil {
190 return err
191 }
192 }
193 }
194 if bans {
195 if _, err := tx.ExecContext(ctx, `DELETE FROM community_bans`); err != nil {
196 return err
197 }
198 for _, item := range c.Bans {
199 if item.PubKey != "" {
200 if _, err := tx.ExecContext(ctx, `INSERT INTO community_bans(pubkey,reason,at) VALUES(?,?,?)`, item.PubKey, item.Reason, now); err != nil {
201 return err
202 }
203 }
204 }
205 }
206 if addresses {
207 if _, err := tx.ExecContext(ctx, `DELETE FROM community_ip_blocks`); err != nil {
208 return err
209 }
210 for _, item := range c.IPBlocks {
211 if item.IP != "" {
212 if _, err := tx.ExecContext(ctx, `INSERT INTO community_ip_blocks(ip,reason,at) VALUES(?,?,?)`, item.IP, item.Reason, now); err != nil {
213 return err
214 }
215 }
216 }
217 }
218 if eventBans {
219 if _, err := tx.ExecContext(ctx, `DELETE FROM community_event_bans`); err != nil {
220 return err
221 }
222 for _, item := range c.EventBans {
223 if item.ID != "" {
224 if _, err := tx.ExecContext(ctx, `INSERT INTO community_event_bans(id,reason,at) VALUES(?,?,?)`, item.ID, item.Reason, now); err != nil {
225 return err
226 }
227 }
228 }
229 }
230 if kinds {
231 if _, err := tx.ExecContext(ctx, `DELETE FROM community_kind_rules`); err != nil {
232 return err
233 }
234 for _, item := range c.KindRules {
235 if _, err := tx.ExecContext(ctx, `INSERT INTO community_kind_rules(kind,rule) VALUES(?,?)`, item.Kind, item.Rule); err != nil {
236 return err
237 }
238 }
239 }
240 if retention {
241 if _, err := tx.ExecContext(ctx, `DELETE FROM community_retention`); err != nil {
242 return err
243 }
244 for _, item := range c.Retention {
245 kind := -1
246 if item.Kind != nil {
247 kind = *item.Kind
248 }
249 if _, err := tx.ExecContext(ctx, `INSERT INTO community_retention(kind,days) VALUES(?,?)`, kind, item.Days); err != nil {
250 return err
251 }
252 }
253 }
254 return nil
255}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

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

Refs