Monorepo for Tangled

appview: add "starred-by" page at /{user}/{repo}/stars

Signed-off-by: Patrick Dewey <p@pdewey.com>

pdewey.com a7a1bf5d d272c794

verified
+82
+13
appview/pages/pages.go
··· 675 IsStarred bool 676 SubjectAt syntax.ATURI 677 StarCount int 678 HxSwapOob bool 679 } 680 ··· 1385 1386 func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { 1387 return p.executePlain("repo/fragments/editLabelPanel", w, params) 1388 } 1389 1390 type PipelinesParams struct {
··· 675 IsStarred bool 676 SubjectAt syntax.ATURI 677 StarCount int 678 + StarsHref string 679 HxSwapOob bool 680 } 681 ··· 1386 1387 func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { 1388 return p.executePlain("repo/fragments/editLabelPanel", w, params) 1389 + } 1390 + 1391 + type RepoStarsParams struct { 1392 + LoggedInUser *oauth.MultiAccountUser 1393 + RepoInfo repoinfo.RepoInfo 1394 + Active string 1395 + Starrers []models.Star 1396 + } 1397 + 1398 + func (p *Pages) RepoStars(w io.Writer, params RepoStarsParams) error { 1399 + params.Active = "stars" 1400 + return p.executeRepo("repo/stars", w, params) 1401 } 1402 1403 type PipelinesParams struct {
+24
appview/pages/templates/repo/stars.html
···
··· 1 + {{ define "title" }}stars · {{ .RepoInfo.FullName }}{{ end }} 2 + {{ define "repoContent" }} 3 + <div class="flex flex-col gap-4"> 4 + <h2 class="text-sm uppercase font-bold">Starred by</h2> 5 + <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> 6 + {{ range .Starrers }} 7 + {{ $handle := resolve .Did }} 8 + <div class="border border-gray-200 dark:border-gray-700 rounded p-4"> 9 + <div class="flex items-center gap-3"> 10 + {{ template "user/fragments/picLink" (list .Did "size-10") }} 11 + <div class="flex-1 min-w-0"> 12 + <a href="/{{ $handle }}" class="block truncate">{{ $handle }}</a> 13 + <p class="text-sm text-gray-500 dark:text-gray-400"> 14 + starred {{ .Created | relTimeFmt }} 15 + </p> 16 + </div> 17 + </div> 18 + </div> 19 + {{ else }} 20 + <p class="text-gray-500 dark:text-gray-400 col-span-3">No stars yet.</p> 21 + {{ end }} 22 + </div> 23 + </div> 24 + {{ end }}
+23
appview/repo/repo.go
··· 1195 } 1196 } 1197 1198 // this is used to rollback changes made to the PDS 1199 // 1200 // it is a no-op if the provided ATURI is empty
··· 1195 } 1196 } 1197 1198 + func (rp *Repo) Stars(w http.ResponseWriter, r *http.Request) { 1199 + l := rp.logger.With("handler", "Stars") 1200 + 1201 + user := rp.oauth.GetMultiAccountUser(r) 1202 + f, err := rp.repoResolver.Resolve(r) 1203 + if err != nil { 1204 + l.Error("failed to resolve source repo", "err", err) 1205 + return 1206 + } 1207 + 1208 + starrers, err := db.GetStars(rp.db, f.RepoAt()) 1209 + if err != nil { 1210 + l.Error("failed to fetch starrers", "err", err, "repoAt", f.RepoAt()) 1211 + return 1212 + } 1213 + 1214 + rp.pages.RepoStars(w, pages.RepoStarsParams{ 1215 + LoggedInUser: user, 1216 + RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1217 + Starrers: starrers, 1218 + }) 1219 + } 1220 + 1221 // this is used to rollback changes made to the PDS 1222 // 1223 // it is a no-op if the provided ATURI is empty
+2
appview/repo/router.go
··· 45 // a file path 46 r.Get("/archive/{ref}", rp.DownloadArchive) 47 48 r.Route("/fork", func(r chi.Router) { 49 r.Use(middleware.AuthMiddleware(rp.oauth)) 50 r.Get("/", rp.ForkRepo)
··· 45 // a file path 46 r.Get("/archive/{ref}", rp.DownloadArchive) 47 48 + r.Get("/stars", rp.Stars) 49 + 50 r.Route("/fork", func(r chi.Router) { 51 r.Use(middleware.AuthMiddleware(rp.oauth)) 52 r.Get("/", rp.ForkRepo)
+20
appview/state/star.go
··· 1 package state 2 3 import ( 4 "log" 5 "net/http" 6 "time" ··· 35 log.Println("failed to authorize client", err) 36 return 37 } 38 39 switch r.Method { 40 case http.MethodPost: ··· 79 IsStarred: true, 80 SubjectAt: subjectUri, 81 StarCount: starCount, 82 }) 83 84 return ··· 119 IsStarred: false, 120 SubjectAt: subjectUri, 121 StarCount: starCount, 122 }) 123 124 return 125 } 126 127 }
··· 1 package state 2 3 import ( 4 + "context" 5 + "fmt" 6 "log" 7 "net/http" 8 "time" ··· 37 log.Println("failed to authorize client", err) 38 return 39 } 40 + 41 + starsHref := s.starsHref(r.Context(), subjectUri) 42 43 switch r.Method { 44 case http.MethodPost: ··· 83 IsStarred: true, 84 SubjectAt: subjectUri, 85 StarCount: starCount, 86 + StarsHref: starsHref, 87 }) 88 89 return ··· 124 IsStarred: false, 125 SubjectAt: subjectUri, 126 StarCount: starCount, 127 + StarsHref: starsHref, 128 }) 129 130 return 131 } 132 133 } 134 + 135 + func (s *State) starsHref(ctx context.Context, subjectUri syntax.ATURI) string { 136 + repo, err := db.GetRepoByAtUri(s.db, subjectUri.String()) 137 + if err != nil { 138 + return "" 139 + } 140 + 141 + id, err := s.idResolver.ResolveIdent(ctx, repo.Did) 142 + if err != nil { 143 + return fmt.Sprintf("/%s/%s/stars", repo.Did, repo.Name) 144 + } 145 + 146 + return fmt.Sprintf("/%s/%s/stars", id.Handle, repo.Name) 147 + }