Skip to content

Commit ce8726f

Browse files
committed
feat(googlereader): Add feed icon URLs endpoint
Adds an endpoint to the Google Reader integration to serve feed icon URLs.
1 parent ad02f21 commit ce8726f

File tree

4 files changed

+68
-4
lines changed

4 files changed

+68
-4
lines changed

internal/googlereader/handler.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func (r RequestModifiers) String() string {
210210
func Serve(router *mux.Router, store *storage.Storage) {
211211
handler := &handler{store, router}
212212
router.HandleFunc("/accounts/ClientLogin", handler.clientLoginHandler).Methods(http.MethodPost).Name("ClientLogin")
213+
router.HandleFunc("/reader/api/0/icon/{iconHash}", handler.iconHandler).Methods(http.MethodGet).Name("Icon")
213214

214215
middleware := newMiddleware(store)
215216
sr := router.PathPrefix("/reader/api/0").Subrouter()
@@ -727,6 +728,30 @@ func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
727728
})
728729
}
729730

731+
func (h *handler) iconHandler(w http.ResponseWriter, r *http.Request) {
732+
clientIP := request.ClientIP(r)
733+
iconHash := request.RouteStringParam(r, "iconHash")
734+
735+
slog.Debug("[GoogleReader] Handle /icon/{iconHash}",
736+
slog.String("handler", "iconHandler"),
737+
slog.String("client_ip", clientIP),
738+
slog.String("user_agent", r.UserAgent()),
739+
slog.String("icon_hash", iconHash),
740+
)
741+
742+
icon, err := h.store.IconByHash(iconHash)
743+
744+
if err != nil {
745+
json.ServerError(w, r, err)
746+
return
747+
}
748+
749+
builder := response.New(w, r)
750+
builder.WithHeader("Content-Type", icon.MimeType)
751+
builder.WithBody(icon.Content)
752+
builder.Write()
753+
}
754+
730755
func getFeed(stream Stream, store *storage.Storage, userID int64) (*model.Feed, error) {
731756
feedID, err := strconv.ParseInt(stream.ID, 10, 64)
732757
if err != nil {
@@ -827,6 +852,14 @@ func move(stream Stream, destination Stream, store *storage.Storage, userID int6
827852
return store.UpdateFeed(feed)
828853
}
829854

855+
func (h *handler) feedIconURL(f *model.Feed) string {
856+
if f.Icon != nil {
857+
return config.Opts.RootURL() + route.Path(h.router, "Icon", "iconHash", f.Icon.IconHash)
858+
} else {
859+
return ""
860+
}
861+
}
862+
830863
func (h *handler) editSubscriptionHandler(w http.ResponseWriter, r *http.Request) {
831864
userID := request.UserID(r)
832865
clientIP := request.ClientIP(r)
@@ -1208,6 +1241,7 @@ func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request
12081241
json.ServerError(w, r, err)
12091242
return
12101243
}
1244+
12111245
result.Subscriptions = make([]subscription, 0)
12121246
for _, feed := range feeds {
12131247
result.Subscriptions = append(result.Subscriptions, subscription{
@@ -1216,7 +1250,7 @@ func (h *handler) subscriptionListHandler(w http.ResponseWriter, r *http.Request
12161250
URL: feed.FeedURL,
12171251
Categories: []subscriptionCategory{{fmt.Sprintf(UserLabelPrefix, userID) + feed.Category.Title, feed.Category.Title, "folder"}},
12181252
HTMLURL: feed.SiteURL,
1219-
IconURL: "", // TODO: Icons are base64 encoded in the DB.
1253+
IconURL: h.feedIconURL(feed),
12201254
})
12211255
}
12221256
json.OK(w, r, result)

internal/model/icon.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Icons []*Icon
2626

2727
// FeedIcon is a junction table between feeds and icons.
2828
type FeedIcon struct {
29-
FeedID int64 `json:"feed_id"`
30-
IconID int64 `json:"icon_id"`
29+
FeedID int64 `json:"feed_id"`
30+
IconID int64 `json:"icon_id"`
31+
IconHash string `json:"icon_hash"`
3132
}

internal/storage/feed_query_builder.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
163163
c.title as category_title,
164164
c.hide_globally as category_hidden,
165165
fi.icon_id,
166+
i.hash,
166167
u.timezone,
167168
f.apprise_service_urls,
168169
f.webhook_url,
@@ -178,6 +179,8 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
178179
categories c ON c.id=f.category_id
179180
LEFT JOIN
180181
feed_icons fi ON fi.feed_id=f.id
182+
LEFT JOIN
183+
icons i ON i.id=fi.icon_id
181184
LEFT JOIN
182185
users u ON u.id=f.user_id
183186
WHERE %s
@@ -201,6 +204,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
201204
for rows.Next() {
202205
var feed model.Feed
203206
var iconID sql.NullInt64
207+
var iconHash string
204208
var tz string
205209
feed.Category = &model.Category{}
206210

@@ -237,6 +241,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
237241
&feed.Category.Title,
238242
&feed.Category.HideGlobally,
239243
&iconID,
244+
&iconHash,
240245
&tz,
241246
&feed.AppriseServiceURLs,
242247
&feed.WebhookURL,
@@ -253,7 +258,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
253258
}
254259

255260
if iconID.Valid {
256-
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: iconID.Int64}
261+
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: iconID.Int64, IconHash: iconHash}
257262
} else {
258263
feed.Icon = &model.FeedIcon{FeedID: feed.ID, IconID: 0}
259264
}

internal/storage/icon.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,30 @@ func (s *Storage) IconByFeedID(userID, feedID int64) (*model.Icon, error) {
5757
return &icon, nil
5858
}
5959

60+
// IconByHash returns a feed icon.
61+
func (s *Storage) IconByHash(hash string) (*model.Icon, error) {
62+
query := `
63+
SELECT
64+
icons.id,
65+
icons.hash,
66+
icons.mime_type,
67+
icons.content
68+
FROM icons
69+
LEFT JOIN feed_icons ON feed_icons.icon_id=icons.id
70+
LEFT JOIN feeds ON feeds.id=feed_icons.feed_id
71+
WHERE
72+
icons.hash=$1
73+
LIMIT 1
74+
`
75+
var icon model.Icon
76+
err := s.db.QueryRow(query, hash).Scan(&icon.ID, &icon.Hash, &icon.MimeType, &icon.Content)
77+
if err != nil {
78+
return nil, fmt.Errorf(`store: unable to fetch icon: %v`, err)
79+
}
80+
81+
return &icon, nil
82+
}
83+
6084
// StoreFeedIcon creates or updates a feed icon.
6185
func (s *Storage) StoreFeedIcon(feedID int64, icon *model.Icon) error {
6286
tx, err := s.db.Begin()

0 commit comments

Comments
 (0)