tavern/web/handler_feed.go

308 lines
6.9 KiB
Go

package web
import (
"fmt"
"math"
"net/http"
"net/url"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
"github.com/ngerakines/tavern/errors"
"github.com/ngerakines/tavern/storage"
)
func (h handler) loggedIn(c *gin.Context) (gin.H, *storage.User, sessions.Session, bool) {
session := sessions.Default(c)
ctx := c.Request.Context()
trans, transOK := c.Get("trans")
if !transOK {
panic(errors.NewTranslatorNotFoundError(nil))
}
data := gin.H{
"flashes": getFlashes(session),
"Trans": trans,
}
user, err := h.storage.GetUserBySession(ctx, session)
if err != nil {
if errors.Is(err, errors.UserSessionNotFoundError{}) {
h.flashErrorOrFail(c, h.url(), errors.NewAuthenticationRequiredError(err))
return nil, nil, nil, false
}
h.hardFail(c, err)
return nil, nil, nil, false
}
data["user"] = user
data["authenticated"] = true
return data, user, session, true
}
func (h handler) saveSession(c *gin.Context, session sessions.Session) bool {
if err := session.Save(); err != nil {
h.hardFail(c, err)
return false
}
return true
}
func (h handler) viewFeed(c *gin.Context) {
data, user, session, cont := h.loggedIn(c)
if !cont {
return
}
ctx := c.Request.Context()
data["feed_view"] = "recent"
data["feed_link"] = "feed_recent"
userActorID := storage.NewActorID(user.Name, h.domain)
page := intParam(c, "page", 1)
limit := 20
total, err := h.storage.RowCount(ctx, `SELECT COUNT(*) FROM user_feed WHERE user_id = $1`, user.ID)
if err != nil {
h.hardFail(c, err)
return
}
uf, err := h.storage.PaginateUserFeed(ctx, user.ID, limit, (page-1)*limit)
if err != nil {
h.hardFail(c, err)
return
}
pageCount := math.Ceil(float64(total) / float64(limit))
data["page_count"] = int(pageCount)
data["page"] = page
data["limit"] = limit
var objectEventIDs []uuid.UUID
for _, ufi := range uf {
objectEventIDs = append(objectEventIDs, ufi.ObjectEventID)
}
pairs, err := h.storage.ObjectPairsByObjectEventRowIDs(ctx, objectEventIDs)
if err != nil {
h.hardFail(c, err)
return
}
pairsMap := make(map[uuid.UUID]storage.ObjectEventPair)
actorIDs := make([]string, 0)
objectIDs := make([]string, 0)
for _, pair := range pairs {
pairsMap[pair.ObjectEventRowID] = pair
actorIDs = append(actorIDs, pair.ActorID)
objectIDs = append(objectIDs, pair.ObjectID)
}
announcements, err := h.storage.UserAnnouncementsByObject(ctx, string(userActorID), objectIDs)
if err != nil {
h.hardFail(c, err)
return
}
data["announcements"] = announcements
vf := &viewFeed{}
err = vf.populate(uf, pairsMap)
if err != nil {
h.hardFail(c, err)
return
}
data["feed"] = vf.feed
actors, err := h.storage.ActorPayloadsByActorID(ctx, append(actorIDs, vf.actorIDs...))
if err != nil {
h.hardFail(c, err)
return
}
allActors := h.gatherActors(actors)
var pages []int
for i := page - 3; i <= page+3; i++ {
if i > 0 && i <= int(pageCount) {
pages = append(pages, i)
}
}
data["pages"] = pages
data["actors"] = actorLookup{h.domain, allActors}
if cont = h.saveSession(c, session); !cont {
return
}
c.HTML(http.StatusOK, "feed", data)
}
func (h handler) viewMyFeed(c *gin.Context) {
data, user, session, cont := h.loggedIn(c)
if !cont {
return
}
ctx := c.Request.Context()
data["feed_view"] = "mine"
data["feed_link"] = "feed_mine"
userActorID := storage.NewActorID(user.Name, h.domain)
page := intParam(c, "page", 1)
limit := 20
total, err := h.storage.RowCount(ctx, `SELECT COUNT(*) FROM user_activities WHERE user_id = $1`, user.ID)
if err != nil {
h.hardFail(c, err)
return
}
uf, err := h.storage.PaginateUserActivity(ctx, user.ID, limit, (page-1)*limit)
if err != nil {
h.hardFail(c, err)
return
}
pageCount := math.Ceil(float64(total) / float64(limit))
data["page_count"] = int(pageCount)
data["page"] = page
data["limit"] = limit
var objectEventIDs []uuid.UUID
for _, ufi := range uf {
objectEventIDs = append(objectEventIDs, ufi.ObjectEventID)
}
pairs, err := h.storage.ObjectPairsByObjectEventRowIDs(ctx, objectEventIDs)
if err != nil {
h.hardFail(c, err)
return
}
pairsMap := make(map[uuid.UUID]storage.ObjectEventPair)
actorIDs := make([]string, 0)
objectIDs := make([]string, 0)
for _, pair := range pairs {
pairsMap[pair.ObjectEventRowID] = pair
actorIDs = append(actorIDs, pair.ActorID)
objectIDs = append(objectIDs, pair.ObjectID)
}
announcements, err := h.storage.UserAnnouncementsByObject(ctx, string(userActorID), objectIDs)
if err != nil {
h.hardFail(c, err)
return
}
data["announcements"] = announcements
vf := &viewFeed{}
err = vf.populate(uf, pairsMap)
if err != nil {
h.hardFail(c, err)
return
}
data["feed"] = vf.feed
actors, err := h.storage.ActorPayloadsByActorID(ctx, append(actorIDs, vf.actorIDs...))
if err != nil {
h.hardFail(c, err)
return
}
allActors := h.gatherActors(actors)
var pages []int
for i := page - 3; i <= page+3; i++ {
if i > 0 && i <= int(pageCount) {
pages = append(pages, i)
}
}
data["pages"] = pages
data["actors"] = actorLookup{h.domain, allActors}
if cont = h.saveSession(c, session); !cont {
return
}
c.HTML(http.StatusOK, "feed", data)
}
func (h handler) gatherActors(actors []storage.Payload) map[string]map[string]string {
results := make(map[string]map[string]string)
for _, actor := range actors {
actorID, _ := storage.JSONString(actor, "id")
u, err := url.Parse(actorID)
if err != nil {
continue
}
domain := u.Hostname()
summary := make(map[string]string)
preferredUsername, ok := storage.JSONString(actor, "preferredUsername")
if !ok {
preferredUsername = "unknown"
}
name, hasName := storage.JSONString(actor, "name")
if !hasName {
name = preferredUsername
}
summary["preferred_username"] = preferredUsername
summary["name"] = name
summary["domain"] = domain
summary["at"] = fmt.Sprintf("%s@%s", preferredUsername, domain)
summary["icon"] = fmt.Sprintf("https://%s/avatar/png/%s/%s", h.domain, domain, preferredUsername)
results[actorID] = summary
}
return results
}
type actorLookup struct {
domain string
actors map[string]map[string]string
}
func (al actorLookup) Lookup(focus string, actorID string) string {
switch focus {
case "icon":
actor, found := al.actors[actorID]
if found {
icon, found := actor["icon"]
if found {
return icon
}
username, foundA := actor["name"]
actorDomain, foundB := actor["domain"]
if foundA && foundB {
return fmt.Sprintf("https://%s/avatar/png/%s/%s", al.domain, actorDomain, username)
}
}
return fmt.Sprintf("https://%s/avatar/png/unknown/unknown", al.domain)
case "name":
actor, found := al.actors[actorID]
if found {
if displayName, ok := actor["name"]; ok {
return displayName
}
if at, ok := actor["at"]; ok {
return at
}
if preferredUsername, ok := actor["preferred_username"]; ok {
return preferredUsername
}
}
return actorID
default:
return fmt.Sprintf("unknown key: %s", focus)
}
}