nandi/gleanpublic⑂ Fork 0
⑂ 561b114
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Improve skyreader lexicon supportUnverified

Julien Robert committed 2026-04-22T10:29:26+02:00 Browse files
561b114 parent: a229c5f
modified internal/atproto/lexicon_external.go +14 -5
@@ -11,12 +11,21 @@ type FollowRecord struct {
1111 Via json.RawMessage `json:"via,omitempty"`
1212 }
1313
14-// Note: app.skyreader.feed.subscription is not published, so thre is no tests that verifies this is correct. (ref: https://tangled.org/julien.rbrt.fr/glean/issues/3#comment-5760)
1514 type SkyreaderSubscriptionRecord struct {
16- CreatedAt string `json:"createdAt"`
17- FeedURL string `json:"feedUrl"`
18- Title string `json:"title,omitempty"`
19- SiteURL string `json:"siteUrl,omitempty"`
15+ CreatedAt string `json:"createdAt"`
16+ FeedURL string `json:"feedUrl"`
17+ Title string `json:"title"`
18+ SiteURL string `json:"siteUrl"`
19+ Category string `json:"category"`
20+ Tags []string `json:"tags,omitempty"`
21+ UpdatedAt string `json:"updatedAt,omitempty"`
22+ Source string `json:"source,omitempty"`
23+ ExternalRef string `json:"externalRef,omitempty"`
24+ SourceType string `json:"sourceType,omitempty"`
25+ SubjectDid string `json:"subjectDid,omitempty"`
26+ CollectionNsid string `json:"collectionNsid,omitempty"`
27+ CustomTitle string `json:"customTitle,omitempty"`
28+ CustomIconURL string `json:"customIconUrl,omitempty"`
2029 }
2130
2231 type MarginNoteRecord struct {
@@ -11,12 +11,21 @@ type FollowRecord struct {
11 Via json.RawMessage `json:"via,omitempty"`11 Via json.RawMessage `json:"via,omitempty"`
12 }12 }
13 13
14-// Note: app.skyreader.feed.subscription is not published, so thre is no tests that verifies this is correct. (ref: https://tangled.org/julien.rbrt.fr/glean/issues/3#comment-5760)
15 type SkyreaderSubscriptionRecord struct {14 type SkyreaderSubscriptionRecord struct {
16- CreatedAt string `json:"createdAt"`15+ CreatedAt string `json:"createdAt"`
17- FeedURL string `json:"feedUrl"`16+ FeedURL string `json:"feedUrl"`
18- Title string `json:"title,omitempty"`17+ Title string `json:"title"`
19- SiteURL string `json:"siteUrl,omitempty"`18+ SiteURL string `json:"siteUrl"`
19+ Category string `json:"category"`
20+ Tags []string `json:"tags,omitempty"`
21+ UpdatedAt string `json:"updatedAt,omitempty"`
22+ Source string `json:"source,omitempty"`
23+ ExternalRef string `json:"externalRef,omitempty"`
24+ SourceType string `json:"sourceType,omitempty"`
25+ SubjectDid string `json:"subjectDid,omitempty"`
26+ CollectionNsid string `json:"collectionNsid,omitempty"`
27+ CustomTitle string `json:"customTitle,omitempty"`
28+ CustomIconURL string `json:"customIconUrl,omitempty"`
20 }29 }
21 30
22 type MarginNoteRecord struct {31 type MarginNoteRecord struct {
modified internal/atproto/lexicon_test.go +4 -0
@@ -89,3 +89,7 @@ func TestFollowRecordMatchesLexicon(t *testing.T) {
8989 func TestMarginNoteRecordMatchesLexicon(t *testing.T) {
9090 assertStructMatchesLexiconPath[MarginNoteRecord](t, lexiconPathFromRoot("at/margin/note.json"))
9191 }
92+
93+func TestSkyreaderSubscriptionRecordMatchesLexicon(t *testing.T) {
94+ assertStructMatchesLexiconPath[SkyreaderSubscriptionRecord](t, lexiconPathFromRoot("app/skyreader/feed/subscription.json"))
95+}
@@ -89,3 +89,7 @@ func TestFollowRecordMatchesLexicon(t *testing.T) {
89 func TestMarginNoteRecordMatchesLexicon(t *testing.T) {89 func TestMarginNoteRecordMatchesLexicon(t *testing.T) {
90 assertStructMatchesLexiconPath[MarginNoteRecord](t, lexiconPathFromRoot("at/margin/note.json"))90 assertStructMatchesLexiconPath[MarginNoteRecord](t, lexiconPathFromRoot("at/margin/note.json"))
91 }91 }
92+
93+func TestSkyreaderSubscriptionRecordMatchesLexicon(t *testing.T) {
94+ assertStructMatchesLexiconPath[SkyreaderSubscriptionRecord](t, lexiconPathFromRoot("app/skyreader/feed/subscription.json"))
95+}
modified internal/atproto/stream_handler.go +6 -2
@@ -222,9 +222,13 @@ func (h *StreamDBHandler) handleSkyreaderSubscription(ctx context.Context, event
222222 return nil
223223 }
224224
225- f := &db.Feed{FeedURL: rec.FeedURL, Title: db.NullStr(rec.Title), SiteURL: db.NullStr(rec.SiteURL)}
225+ f := &db.Feed{
226+ FeedURL: rec.FeedURL,
227+ Title: db.NullStr(rec.Title),
228+ SiteURL: db.NullStr(rec.SiteURL),
229+ }
226230 _ = h.db.UpsertFeed(ctx, f)
227- err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, "", event.URI, event.CID)
231+ err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, rec.Category, event.URI, event.CID)
228232 if errors.Is(err, db.ErrDuplicateSubscription) {
229233 return nil
230234 }
@@ -222,9 +222,13 @@ func (h *StreamDBHandler) handleSkyreaderSubscription(ctx context.Context, event
222 return nil222 return nil
223 }223 }
224 224
225- f := &db.Feed{FeedURL: rec.FeedURL, Title: db.NullStr(rec.Title), SiteURL: db.NullStr(rec.SiteURL)}225+ f := &db.Feed{
226+ FeedURL: rec.FeedURL,
227+ Title: db.NullStr(rec.Title),
228+ SiteURL: db.NullStr(rec.SiteURL),
229+ }
226 _ = h.db.UpsertFeed(ctx, f)230 _ = h.db.UpsertFeed(ctx, f)
227- err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, "", event.URI, event.CID)231+ err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, rec.Category, event.URI, event.CID)
228 if errors.Is(err, db.ErrDuplicateSubscription) {232 if errors.Is(err, db.ErrDuplicateSubscription) {
229 return nil233 return nil
230 }234 }
added lexicons/app/skyreader/feed/subscription.json +93 -0
new file mode 100644
@@ -0,0 +1,93 @@
1+{
2+ "lexicon": 1,
3+ "id": "app.skyreader.feed.subscription",
4+ "defs": {
5+ "main": {
6+ "type": "record",
7+ "description": "A subscription to an RSS/Atom feed or AT Protocol content stream",
8+ "key": "tid",
9+ "record": {
10+ "type": "object",
11+ "required": ["createdAt"],
12+ "properties": {
13+ "feedUrl": {
14+ "type": "string",
15+ "format": "uri",
16+ "maxLength": 2048,
17+ "description": "The URL of the RSS/Atom feed. Required for RSS subscriptions."
18+ },
19+ "title": {
20+ "type": "string",
21+ "maxLength": 512,
22+ "description": "User-provided or auto-detected feed title"
23+ },
24+ "siteUrl": {
25+ "type": "string",
26+ "format": "uri",
27+ "maxLength": 2048,
28+ "description": "The main website URL associated with the feed"
29+ },
30+ "category": {
31+ "type": "string",
32+ "maxLength": 128,
33+ "description": "User-defined category/folder for organization"
34+ },
35+ "tags": {
36+ "type": "array",
37+ "maxLength": 10,
38+ "items": {
39+ "type": "string",
40+ "maxLength": 64
41+ },
42+ "description": "User-defined tags for the subscription"
43+ },
44+ "createdAt": {
45+ "type": "string",
46+ "format": "datetime"
47+ },
48+ "updatedAt": {
49+ "type": "string",
50+ "format": "datetime"
51+ },
52+ "source": {
53+ "type": "string",
54+ "maxLength": 64,
55+ "description": "Origin of this subscription (e.g., 'leaflet', 'opml', 'manual')"
56+ },
57+ "externalRef": {
58+ "type": "string",
59+ "format": "at-uri",
60+ "maxLength": 2048,
61+ "description": "Reference to the external source record (e.g., Leaflet subscription at-uri)"
62+ },
63+ "sourceType": {
64+ "type": "string",
65+ "maxLength": 64,
66+ "description": "Content source type: 'rss', 'atproto.shares', 'atproto.documents', 'atproto.collection'. Omitted means RSS."
67+ },
68+ "subjectDid": {
69+ "type": "string",
70+ "maxLength": 2048,
71+ "description": "The AT Protocol account DID. Required for atproto.* source types."
72+ },
73+ "collectionNsid": {
74+ "type": "string",
75+ "maxLength": 256,
76+ "description": "Collection NSID for atproto.collection source type (future extensibility)."
77+ },
78+ "customTitle": {
79+ "type": "string",
80+ "maxLength": 512,
81+ "description": "User-set custom display title override"
82+ },
83+ "customIconUrl": {
84+ "type": "string",
85+ "format": "uri",
86+ "maxLength": 2048,
87+ "description": "User-set custom icon URL override"
88+ }
89+ }
90+ }
91+ }
92+ }
93+}
new file mode 100644
@@ -0,0 +1,93 @@
1+{
2+ "lexicon": 1,
3+ "id": "app.skyreader.feed.subscription",
4+ "defs": {
5+ "main": {
6+ "type": "record",
7+ "description": "A subscription to an RSS/Atom feed or AT Protocol content stream",
8+ "key": "tid",
9+ "record": {
10+ "type": "object",
11+ "required": ["createdAt"],
12+ "properties": {
13+ "feedUrl": {
14+ "type": "string",
15+ "format": "uri",
16+ "maxLength": 2048,
17+ "description": "The URL of the RSS/Atom feed. Required for RSS subscriptions."
18+ },
19+ "title": {
20+ "type": "string",
21+ "maxLength": 512,
22+ "description": "User-provided or auto-detected feed title"
23+ },
24+ "siteUrl": {
25+ "type": "string",
26+ "format": "uri",
27+ "maxLength": 2048,
28+ "description": "The main website URL associated with the feed"
29+ },
30+ "category": {
31+ "type": "string",
32+ "maxLength": 128,
33+ "description": "User-defined category/folder for organization"
34+ },
35+ "tags": {
36+ "type": "array",
37+ "maxLength": 10,
38+ "items": {
39+ "type": "string",
40+ "maxLength": 64
41+ },
42+ "description": "User-defined tags for the subscription"
43+ },
44+ "createdAt": {
45+ "type": "string",
46+ "format": "datetime"
47+ },
48+ "updatedAt": {
49+ "type": "string",
50+ "format": "datetime"
51+ },
52+ "source": {
53+ "type": "string",
54+ "maxLength": 64,
55+ "description": "Origin of this subscription (e.g., 'leaflet', 'opml', 'manual')"
56+ },
57+ "externalRef": {
58+ "type": "string",
59+ "format": "at-uri",
60+ "maxLength": 2048,
61+ "description": "Reference to the external source record (e.g., Leaflet subscription at-uri)"
62+ },
63+ "sourceType": {
64+ "type": "string",
65+ "maxLength": 64,
66+ "description": "Content source type: 'rss', 'atproto.shares', 'atproto.documents', 'atproto.collection'. Omitted means RSS."
67+ },
68+ "subjectDid": {
69+ "type": "string",
70+ "maxLength": 2048,
71+ "description": "The AT Protocol account DID. Required for atproto.* source types."
72+ },
73+ "collectionNsid": {
74+ "type": "string",
75+ "maxLength": 256,
76+ "description": "Collection NSID for atproto.collection source type (future extensibility)."
77+ },
78+ "customTitle": {
79+ "type": "string",
80+ "maxLength": 512,
81+ "description": "User-set custom display title override"
82+ },
83+ "customIconUrl": {
84+ "type": "string",
85+ "format": "uri",
86+ "maxLength": 2048,
87+ "description": "User-set custom icon URL override"
88+ }
89+ }
90+ }
91+ }
92+ }
93+}