mirror of https://gitlab.com/ngerakines/tavern.git
308 lines
6.9 KiB
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)
|
|
}
|
|
}
|