Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/db: native knot membership queries

Lewis: May this revision serve well! <lewis@tangled.org>

author
Lewis
committer
Tangled
date (Jun 8, 2026, 4:18 PM +0300) commit d588ea08 parent 9be5d66f change-id spotsktq
+132
+12
appview/config/config.go
··· 8 8 "time" 9 9 10 10 "github.com/sethvargo/go-envconfig" 11 + 12 + "tangled.org/core/consts" 11 13 ) 12 14 13 15 type CoreConfig struct { ··· 104 106 return strings.TrimRight(pdsHost, "/") == strings.TrimRight(p.Host, "/") 105 107 } 106 108 109 + type KnotConfig struct { 110 + Default string `env:"DEFAULT"` 111 + AdminSecret string `env:"ADMIN_SECRET"` 112 + } 113 + 107 114 type R2Config struct { 108 115 AccessKeyID string `env:"ACCESS_KEY_ID"` 109 116 SecretAccessKey string `env:"SECRET_ACCESS_KEY"` ··· 183 190 Redis RedisConfig `env:",prefix=TANGLED_REDIS_"` 184 191 Plc PlcConfig `env:",prefix=TANGLED_PLC_"` 185 192 Pds PdsConfig `env:",prefix=TANGLED_PDS_"` 193 + Knot KnotConfig `env:",prefix=TANGLED_KNOT_"` 186 194 Cloudflare Cloudflare `env:",prefix=TANGLED_CLOUDFLARE_"` 187 195 Label LabelConfig `env:",prefix=TANGLED_LABEL_"` 188 196 Bluesky BlueskyConfig `env:",prefix=TANGLED_BLUESKY_"` ··· 197 205 err := envconfig.Process(ctx, &cfg) 198 206 if err != nil { 199 207 return nil, err 208 + } 209 + 210 + if cfg.Knot.Default == "" { 211 + cfg.Knot.Default = consts.DefaultKnot 200 212 } 201 213 202 214 return &cfg, nil
+32
appview/config/config_test.go
··· 1 + package config 2 + 3 + import ( 4 + "context" 5 + "testing" 6 + 7 + "tangled.org/core/consts" 8 + ) 9 + 10 + func TestLoadConfig_DefaultKnotFallsBackToConst(t *testing.T) { 11 + t.Setenv("TANGLED_KNOT_DEFAULT", "") 12 + 13 + cfg, err := LoadConfig(context.Background()) 14 + if err != nil { 15 + t.Fatalf("LoadConfig: %v", err) 16 + } 17 + if cfg.Knot.Default != consts.DefaultKnot { 18 + t.Fatalf("unset TANGLED_KNOT_DEFAULT = %q, want fallback %q", cfg.Knot.Default, consts.DefaultKnot) 19 + } 20 + } 21 + 22 + func TestLoadConfig_DefaultKnotHonorsOverride(t *testing.T) { 23 + t.Setenv("TANGLED_KNOT_DEFAULT", "kt.tngl.oyster.cafe") 24 + 25 + cfg, err := LoadConfig(context.Background()) 26 + if err != nil { 27 + t.Fatalf("LoadConfig: %v", err) 28 + } 29 + if cfg.Knot.Default != "kt.tngl.oyster.cafe" { 30 + t.Fatalf("TANGLED_KNOT_DEFAULT override = %q, want kt.tngl.oyster.cafe", cfg.Knot.Default) 31 + } 32 + }
+11
appview/db/db.go
··· 2158 2158 `) 2159 2159 return err 2160 2160 }) 2161 + 2162 + orm.RunMigration(conn, logger, "add-knot-acl-native", func(tx *sql.Tx) error { 2163 + _, err := tx.Exec(` 2164 + create table if not exists knot_acl_native ( 2165 + domain text primary key, 2166 + since text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) 2167 + ); 2168 + `) 2169 + return err 2170 + }) 2171 + 2161 2172 return &DB{ 2162 2173 db, 2163 2174 logger,
+28
appview/db/knot_acl_native.go
··· 1 + package db 2 + 3 + import ( 4 + "context" 5 + "database/sql" 6 + "errors" 7 + ) 8 + 9 + func MarkKnotAclNative(ctx context.Context, e Execer, domain string) error { 10 + _, err := e.ExecContext( 11 + ctx, 12 + `insert into knot_acl_native (domain) values (?) on conflict (domain) do nothing`, 13 + domain, 14 + ) 15 + return err 16 + } 17 + 18 + func IsKnotAclNative(ctx context.Context, e Execer, domain string) (bool, error) { 19 + var one int 20 + err := e.QueryRowContext(ctx, `select 1 from knot_acl_native where domain = ?`, domain).Scan(&one) 21 + if errors.Is(err, sql.ErrNoRows) { 22 + return false, nil 23 + } 24 + if err != nil { 25 + return false, err 26 + } 27 + return true, nil 28 + }
+49
appview/db/knot_acl_native_test.go
··· 1 + package db 2 + 3 + import ( 4 + "context" 5 + "testing" 6 + ) 7 + 8 + func TestKnotAclNativeDefaultsFalse(t *testing.T) { 9 + d := newTestDB(t) 10 + 11 + native, err := IsKnotAclNative(context.Background(), d, "clam.nel.pet") 12 + if err != nil { 13 + t.Fatalf("IsKnotAclNative: %v", err) 14 + } 15 + if native { 16 + t.Fatal("an unseen knot must default to not native") 17 + } 18 + } 19 + 20 + func TestKnotAclNativeMarkLatches(t *testing.T) { 21 + d := newTestDB(t) 22 + 23 + if err := MarkKnotAclNative(context.Background(), d, "whelk.nel.pet"); err != nil { 24 + t.Fatalf("MarkKnotAclNative: %v", err) 25 + } 26 + 27 + native, err := IsKnotAclNative(context.Background(), d, "whelk.nel.pet") 28 + if err != nil { 29 + t.Fatalf("IsKnotAclNative: %v", err) 30 + } 31 + if !native { 32 + t.Fatal("a marked knot must read back native") 33 + } 34 + } 35 + 36 + func TestKnotAclNativeMarkIsIdempotent(t *testing.T) { 37 + d := newTestDB(t) 38 + 39 + if err := MarkKnotAclNative(context.Background(), d, "limpet.nel.pet"); err != nil { 40 + t.Fatalf("first mark: %v", err) 41 + } 42 + if err := MarkKnotAclNative(context.Background(), d, "limpet.nel.pet"); err != nil { 43 + t.Fatalf("second mark must be a no-op, got: %v", err) 44 + } 45 + 46 + if n := countRows(t, d, `select count(*) from knot_acl_native where domain = ?`, "limpet.nel.pet"); n != 1 { 47 + t.Fatalf("rows = %d, want exactly 1 after a repeated mark", n) 48 + } 49 + }