forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview/timeline: basic timeline view

It's pretty rudimentary. All repos and follows (for now) are
consolidated into a []TimelineEvent and sorted by time.

anirudh.fi 63cc13ae e26055b7

verified
Changed files
+153 -4
appview
+34
appview/db/follow.go
···
_, err := d.db.Exec(`delete from follows where user_did = ? and subject_did = ?`, userDid, subjectDid)
return err
}
+
+
func (d *DB) GetAllFollows() ([]Follow, error) {
+
var follows []Follow
+
+
rows, err := d.db.Query(`select user_did, subject_did, followed_at, at_uri from follows`)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
var follow Follow
+
var followedAt string
+
if err := rows.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.RKey); err != nil {
+
return nil, err
+
}
+
+
followedAtTime, err := time.Parse(time.RFC3339, followedAt)
+
if err != nil {
+
log.Println("unable to determine followed at time")
+
follow.FollowedAt = nil
+
} else {
+
follow.FollowedAt = &followedAtTime
+
}
+
+
follows = append(follows, follow)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return follows, nil
+
}
+24
appview/db/repos.go
···
Rkey string
}
+
func (d *DB) GetAllRepos() ([]Repo, error) {
+
var repos []Repo
+
+
rows, err := d.db.Query(`select did, name, knot, created from repos`)
+
if err != nil {
+
return nil, err
+
}
+
defer rows.Close()
+
+
for rows.Next() {
+
repo, err := scanRepo(rows)
+
if err != nil {
+
return nil, err
+
}
+
repos = append(repos, *repo)
+
}
+
+
if err := rows.Err(); err != nil {
+
return nil, err
+
}
+
+
return repos, nil
+
}
+
func (d *DB) GetAllReposByDid(did string) ([]Repo, error) {
var repos []Repo
+48
appview/db/timeline.go
···
+
package db
+
+
import (
+
"sort"
+
"time"
+
)
+
+
type TimelineEvent struct {
+
*Repo
+
*Follow
+
EventAt *time.Time
+
}
+
+
func (d *DB) MakeTimeline() ([]TimelineEvent, error) {
+
var events []TimelineEvent
+
+
repos, err := d.GetAllRepos()
+
if err != nil {
+
return nil, err
+
}
+
+
follows, err := d.GetAllFollows()
+
if err != nil {
+
return nil, err
+
}
+
+
for _, repo := range repos {
+
events = append(events, TimelineEvent{
+
Repo: &repo,
+
Follow: nil,
+
EventAt: repo.Created,
+
})
+
}
+
+
for _, follow := range follows {
+
events = append(events, TimelineEvent{
+
Repo: nil,
+
Follow: &follow,
+
EventAt: follow.FollowedAt,
+
})
+
}
+
+
sort.Slice(events, func(i, j int) bool {
+
return events[i].EventAt.After(*events[j].EventAt)
+
})
+
+
return events, nil
+
}
+1
appview/pages/pages.go
···
type TimelineParams struct {
LoggedInUser *auth.User
+
Timeline []db.TimelineEvent
}
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
+37 -4
appview/pages/templates/timeline.html
···
-
{{define "title"}}timeline{{end}}
+
{{ define "title" }}timeline{{ end }}
+
+
{{ define "content" }}
+
<h1>Timeline</h1>
+
+
{{ range .Timeline }}
+
{{ if .Repo }}
+
<div class="border border-black p-4 m-2 bg-white w-1/2">
+
<div class="flex items-center">
+
<div class="text-sm text-gray-600">
+
{{ .Repo.Did }} created
+
</div>
+
<div class="px-3">{{ .Repo.Name }}</div>
+
</div>
+
+
<time class="text-sm text-gray-700"
+
>{{ .Repo.Created | timeFmt }}</time
+
>
+
</div>
+
{{ else if .Follow }}
+
<div class="border border-black p-4 m-2 bg-white w-1/2">
+
<div class="flex items-center">
+
<div class="text-sm text-gray-600">
+
{{ .Follow.UserDid }} followed
+
</div>
+
<div class="text-sm text-gray-800">
+
{{ .Follow.SubjectDid }}
+
</div>
+
</div>
-
{{define "content"}}
-
<h1>timeline</h1>
-
{{end}}
+
<time class="text-sm text-gray-700"
+
>{{ .Follow.FollowedAt | timeFmt }}</time
+
>
+
</div>
+
{{ end }}
+
{{ end }}
+
+
{{ end }}
+9
appview/state/state.go
···
func (s *State) Timeline(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
+
+
timeline, err := s.db.MakeTimeline()
+
if err != nil {
+
log.Println(err)
+
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
+
}
+
s.pages.Timeline(w, pages.TimelineParams{
LoggedInUser: user,
+
Timeline: timeline,
})
+
return
}