Improve skyreader lexicon supportUnverified
561b114 parent: a229c5f modified
internal/atproto/lexicon_external.go +14 -5 | @@ -11,12 +11,21 @@ type FollowRecord struct { | ||
| 11 | 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 | 14 | 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"` | |
| 20 | 29 | } |
| 21 | 30 | |
| 22 | 31 | 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) { | ||
| 89 | 89 | func TestMarginNoteRecordMatchesLexicon(t *testing.T) { |
| 90 | 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 | +} | |
| @@ -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 | ||
| 222 | 222 | 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 | 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 | 232 | if errors.Is(err, db.ErrDuplicateSubscription) { |
| 229 | 233 | return nil |
| 230 | 234 | } |
| @@ -222,9 +222,13 @@ func (h *StreamDBHandler) handleSkyreaderSubscription(ctx context.Context, event | |||
| 222 | return nil | 222 | 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 nil | 233 | 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 | +} | ||