+1
-1
appview/middleware/middleware.go
+1
-1
appview/middleware/middleware.go
···
183
183
id, err := mw.idResolver.ResolveIdent(req.Context(), didOrHandle)
184
184
if err != nil {
185
185
// invalid did or handle
186
-
log.Println("failed to resolve did/handle:", err)
186
+
log.Printf("failed to resolve did/handle '%s': %s\n", didOrHandle, err)
187
187
mw.pages.Error404(w)
188
188
return
189
189
}
+112
appview/state/profile.go
+112
appview/state/profile.go
···
1
1
package state
2
2
3
3
import (
4
+
"context"
4
5
"fmt"
5
6
"log"
6
7
"net/http"
···
13
14
"github.com/bluesky-social/indigo/atproto/syntax"
14
15
lexutil "github.com/bluesky-social/indigo/lex/util"
15
16
"github.com/go-chi/chi/v5"
17
+
"github.com/gorilla/feeds"
16
18
"tangled.sh/tangled.sh/core/api/tangled"
17
19
"tangled.sh/tangled.sh/core/appview/db"
18
20
"tangled.sh/tangled.sh/core/appview/pages"
···
202
204
Following: following,
203
205
},
204
206
})
207
+
}
208
+
209
+
func (s *State) feedFromRequest(w http.ResponseWriter, r *http.Request) *feeds.Feed {
210
+
ident, ok := r.Context().Value("resolvedId").(identity.Identity)
211
+
if !ok {
212
+
s.pages.Error404(w)
213
+
return nil
214
+
}
215
+
216
+
feed, err := s.GetProfileFeed(r.Context(), ident.Handle.String(), ident.DID.String())
217
+
if err != nil {
218
+
s.pages.Error500(w)
219
+
return nil
220
+
}
221
+
222
+
return feed
223
+
}
224
+
225
+
func (s *State) AtomFeedPage(w http.ResponseWriter, r *http.Request) {
226
+
feed := s.feedFromRequest(w, r)
227
+
if feed == nil {
228
+
return
229
+
}
230
+
231
+
atom, err := feed.ToAtom()
232
+
if err != nil {
233
+
s.pages.Error500(w)
234
+
return
235
+
}
236
+
237
+
w.Header().Set("content-type", "application/atom+xml")
238
+
w.Write([]byte(atom))
239
+
}
240
+
241
+
func (s *State) GetProfileFeed(ctx context.Context, handle string, did string) (*feeds.Feed, error) {
242
+
timeline, err := db.MakeProfileTimeline(s.db, did)
243
+
if err != nil {
244
+
return nil, err
245
+
}
246
+
247
+
author := &feeds.Author{
248
+
Name: fmt.Sprintf("@%s", handle),
249
+
}
250
+
feed := &feeds.Feed{
251
+
Title: fmt.Sprintf("timeline feed for %s", author.Name),
252
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.AppviewHost, handle), Type: "text/html", Rel: "alternate"},
253
+
Items: make([]*feeds.Item, 0),
254
+
Updated: time.UnixMilli(0),
255
+
Author: author,
256
+
}
257
+
for _, byMonth := range timeline.ByMonth {
258
+
for _, pull := range byMonth.PullEvents.Items {
259
+
owner, err := s.idResolver.ResolveIdent(ctx, pull.Repo.Did)
260
+
if err != nil {
261
+
return nil, err
262
+
}
263
+
feed.Items = append(feed.Items, &feeds.Item{
264
+
Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name),
265
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"},
266
+
Created: pull.Created,
267
+
Author: author,
268
+
})
269
+
for _, submission := range pull.Submissions {
270
+
feed.Items = append(feed.Items, &feeds.Item{
271
+
Title: fmt.Sprintf("%s submitted pull request '%s' (round #%d) in @%s/%s", author.Name, pull.Title, submission.RoundNumber, owner.Handle, pull.Repo.Name),
272
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"},
273
+
Created: submission.Created,
274
+
Author: author,
275
+
})
276
+
}
277
+
}
278
+
for _, issue := range byMonth.IssueEvents.Items {
279
+
owner, err := s.idResolver.ResolveIdent(ctx, issue.Metadata.Repo.Did)
280
+
if err != nil {
281
+
return nil, err
282
+
}
283
+
feed.Items = append(feed.Items, &feeds.Item{
284
+
Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Metadata.Repo.Name),
285
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.AppviewHost, owner.Handle, issue.Metadata.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"},
286
+
Created: issue.Created,
287
+
Author: author,
288
+
})
289
+
}
290
+
for _, repo := range byMonth.RepoEvents {
291
+
var title string
292
+
if repo.Source != nil {
293
+
id, err := s.idResolver.ResolveIdent(ctx, repo.Source.Did)
294
+
if err != nil {
295
+
return nil, err
296
+
}
297
+
title = fmt.Sprintf("%s forked repository @%s/%s to '%s'", author.Name, id.Handle, repo.Source.Name, repo.Repo.Name)
298
+
} else {
299
+
title = fmt.Sprintf("%s created repository '%s'", author.Name, repo.Repo.Name)
300
+
}
301
+
feed.Items = append(feed.Items, &feeds.Item{
302
+
Title: title,
303
+
Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.AppviewHost, handle, repo.Repo.Name), Type: "text/html", Rel: "alternate"},
304
+
Created: repo.Repo.Created,
305
+
Author: author,
306
+
})
307
+
}
308
+
}
309
+
slices.SortFunc(feed.Items, func(a *feeds.Item, b *feeds.Item) int {
310
+
return int(b.Created.UnixMilli()) - int(a.Created.UnixMilli())
311
+
})
312
+
if len(feed.Items) > 0 {
313
+
feed.Updated = feed.Items[0].Created
314
+
}
315
+
316
+
return feed, nil
205
317
}
206
318
207
319
func (s *State) UpdateProfileBio(w http.ResponseWriter, r *http.Request) {
+1
appview/state/router.go
+1
appview/state/router.go
+1
go.mod
+1
go.mod
···
88
88
github.com/golang/mock v1.6.0 // indirect
89
89
github.com/google/go-querystring v1.1.0 // indirect
90
90
github.com/gorilla/css v1.0.1 // indirect
91
+
github.com/gorilla/feeds v1.2.0 // indirect
91
92
github.com/gorilla/securecookie v1.1.2 // indirect
92
93
github.com/hashicorp/errwrap v1.1.0 // indirect
93
94
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
+2
go.sum
+2
go.sum
···
173
173
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
174
174
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
175
175
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
176
+
github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc=
177
+
github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
176
178
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
177
179
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
178
180
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=