mirror of https://gitlab.com/ngerakines/tavern.git
382 lines
9.8 KiB
Go
382 lines
9.8 KiB
Go
package web
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ngerakines/tavern/errors"
|
|
"github.com/ngerakines/tavern/fed"
|
|
"github.com/ngerakines/tavern/storage"
|
|
)
|
|
|
|
func (h handler) dashboardNetwork(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
ctx := c.Request.Context()
|
|
trans, transOK := c.Get("trans")
|
|
if !transOK {
|
|
panic("trans not found in context")
|
|
}
|
|
data := gin.H{
|
|
"flashes": getFlashes(session),
|
|
"Trans": trans,
|
|
}
|
|
|
|
user, err := h.storage.GetUserBySession(ctx, session)
|
|
if err != nil {
|
|
if errors.Is(err, errors.UserSessionNotFoundError{}) {
|
|
|
|
if err = appendFlashError(session, "Not Authenticated"); err != nil {
|
|
h.hardFail(c, errors.NewCannotSaveSessionError(err))
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url())
|
|
return
|
|
}
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
data["user"] = user
|
|
data["authenticated"] = true
|
|
|
|
var totalFollowers int
|
|
var followers []string
|
|
var pendingFollowers []string
|
|
|
|
err = storage.TransactionalStorage(ctx, h.storage, func(txStorage storage.Storage) error {
|
|
totalFollowers, err = txStorage.CountFollowers(ctx, user.ID)
|
|
followers, err = txStorage.ListAcceptedFollowers(ctx, user.ID, totalFollowers+1, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pendingFollowers, err = txStorage.ListPendingFollowers(ctx, user.ID, totalFollowers+1, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
data["followers"] = followers
|
|
data["pending_followers"] = pendingFollowers
|
|
|
|
totalFollowing, err := h.storage.CountFollowing(ctx, user.ID)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
following, err := h.storage.ListAcceptedFollowing(ctx, user.ID, totalFollowing+1, 0)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
pendingFollowing, err := h.storage.ListPendingFollowing(ctx, user.ID, totalFollowing+1, 0)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
data["following"] = following
|
|
data["pending_following"] = pendingFollowing
|
|
|
|
c.HTML(http.StatusOK, "network", data)
|
|
}
|
|
|
|
func (h handler) networkFollow(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
ctx := c.Request.Context()
|
|
|
|
user, err := h.storage.GetUserBySession(ctx, session)
|
|
if err != nil {
|
|
if errors.Is(err, errors.UserSessionNotFoundError{}) {
|
|
|
|
if err = appendFlashError(session, "Not Authenticated"); err != nil {
|
|
h.hardFail(c, errors.NewCannotSaveSessionError(err))
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url())
|
|
return
|
|
}
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
userActor, err := h.storage.GetActor(ctx, user.ActorID)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
actor, err := fed.GetOrFetchActor(ctx, h.storage, h.logger, h.httpClient, c.PostForm("actor"))
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
isFollowing, err := h.storage.IsFollowing(ctx, user.ID, actor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
if isFollowing {
|
|
c.Redirect(http.StatusFound, h.url("network"))
|
|
return
|
|
}
|
|
follow := storage.EmptyPayload()
|
|
activityID := storage.NewV4()
|
|
follow["@context"] = "https://www.w3.org/ns/activitystreams"
|
|
follow["actor"] = storage.NewActorID(user.Name, h.domain)
|
|
follow["id"] = fmt.Sprintf("https://%s/activity/%s", h.domain, activityID)
|
|
follow["object"] = actor.GetID()
|
|
follow["to"] = actor.GetID()
|
|
follow["type"] = "Follow"
|
|
follow["published"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
|
|
payload := follow.Bytes()
|
|
|
|
err = h.storage.CreatePendingFollowing(ctx, user.ID, actor.ID, follow)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
nc := fed.ActorClient{
|
|
HTTPClient: h.httpClient,
|
|
Logger: h.logger,
|
|
}
|
|
err = nc.SendToInbox(ctx, h.userActor(user, userActor), actor, payload)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url("network"))
|
|
}
|
|
|
|
func (h handler) networkUnfollow(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
ctx := c.Request.Context()
|
|
|
|
user, err := h.storage.GetUserBySession(ctx, session)
|
|
if err != nil {
|
|
if errors.Is(err, errors.UserSessionNotFoundError{}) {
|
|
|
|
if err = appendFlashError(session, "Not Authenticated"); err != nil {
|
|
h.hardFail(c, errors.NewCannotSaveSessionError(err))
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url())
|
|
return
|
|
}
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
userActor, err := h.storage.GetActor(ctx, user.ActorID)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
target := c.PostForm("actor")
|
|
targetActor, err := fed.GetOrFetchActor(ctx, h.storage, h.logger, h.httpClient, target)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
requestActivity, err := h.storage.ActivityForFollowing(ctx, user.ID, targetActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
to, ok := storage.JSONString(requestActivity, "to")
|
|
if !ok {
|
|
h.flashErrorOrFail(c, h.url("network"), fmt.Errorf("unable to determine recipient"))
|
|
return
|
|
}
|
|
|
|
delete(requestActivity, "@context")
|
|
|
|
undoFollow := storage.EmptyPayload()
|
|
activityID := storage.NewV4()
|
|
undoFollow["@context"] = "https://www.w3.org/ns/activitystreams"
|
|
undoFollow["actor"] = storage.NewActorID(user.Name, h.domain)
|
|
undoFollow["id"] = fmt.Sprintf("https://%s/activity/%s", h.domain, activityID)
|
|
undoFollow["object"] = requestActivity
|
|
undoFollow["to"] = to
|
|
undoFollow["type"] = "Undo"
|
|
undoFollow["published"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
|
|
payload := undoFollow.Bytes()
|
|
|
|
actor, err := fed.GetOrFetchActor(ctx, h.storage, h.logger, h.httpClient, to)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
err = h.storage.RemoveFollowing(ctx, user.ID, targetActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
nc := fed.ActorClient{
|
|
HTTPClient: h.httpClient,
|
|
Logger: h.logger,
|
|
}
|
|
err = nc.SendToInbox(ctx, h.userActor(user, userActor), actor, payload)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url("network"))
|
|
}
|
|
|
|
func (h handler) networkAccept(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
ctx := c.Request.Context()
|
|
|
|
user, err := h.storage.GetUserBySession(ctx, session)
|
|
if err != nil {
|
|
if errors.Is(err, errors.UserSessionNotFoundError{}) {
|
|
|
|
if err = appendFlashError(session, "Not Authenticated"); err != nil {
|
|
h.hardFail(c, errors.NewCannotSaveSessionError(err))
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url())
|
|
return
|
|
}
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
userActor, err := h.storage.GetActor(ctx, user.ActorID)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
targetActorID := c.PostForm("actor")
|
|
|
|
followerActor, err := fed.GetOrFetchActor(ctx, h.storage, h.logger, h.httpClient, targetActorID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
activity, err := h.storage.ActivityForFollower(ctx, user.ID, followerActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
response := storage.EmptyPayload()
|
|
acceptActivityID := storage.NewV4()
|
|
response["@context"] = "https://www.w3.org/ns/activitystreams"
|
|
response["actor"] = storage.NewActorID(user.Name, h.domain)
|
|
response["id"] = fmt.Sprintf("https://%s/activity/%s", h.domain, acceptActivityID)
|
|
response["object"] = activity
|
|
response["to"] = followerActor.GetID()
|
|
response["type"] = "Accept"
|
|
response["published"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
responsePayload := response.Bytes()
|
|
|
|
nc := fed.ActorClient{
|
|
HTTPClient: h.httpClient,
|
|
Logger: h.logger,
|
|
}
|
|
err = nc.SendToInbox(ctx, h.userActor(user, userActor), followerActor, responsePayload)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
err = h.storage.UpdateFollowerApproved(ctx, user.ID, followerActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url("network"))
|
|
}
|
|
|
|
func (h handler) networkReject(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
ctx := c.Request.Context()
|
|
|
|
user, err := h.storage.GetUserBySession(ctx, session)
|
|
if err != nil {
|
|
if errors.Is(err, errors.UserSessionNotFoundError{}) {
|
|
|
|
if err = appendFlashError(session, "Not Authenticated"); err != nil {
|
|
h.hardFail(c, errors.NewCannotSaveSessionError(err))
|
|
return
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url())
|
|
return
|
|
}
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
userActor, err := h.storage.GetActor(ctx, user.ActorID)
|
|
if err != nil {
|
|
h.hardFail(c, err)
|
|
return
|
|
}
|
|
|
|
targetActorID := c.PostForm("actor")
|
|
|
|
followerActor, err := fed.GetOrFetchActor(ctx, h.storage, h.logger, h.httpClient, targetActorID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
activity, err := h.storage.ActivityForFollower(ctx, user.ID, followerActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
response := storage.EmptyPayload()
|
|
acceptActivityID := storage.NewV4()
|
|
response["@context"] = "https://www.w3.org/ns/activitystreams"
|
|
response["actor"] = storage.NewActorID(user.Name, h.domain)
|
|
response["id"] = fmt.Sprintf("https://%s/activity/%s", h.domain, acceptActivityID)
|
|
response["object"] = activity
|
|
response["to"] = followerActor.GetID()
|
|
response["type"] = "Reject"
|
|
response["published"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
|
|
responsePayload := response.Bytes()
|
|
|
|
err = h.storage.RemoveFollower(ctx, user.ID, followerActor.ID)
|
|
if err != nil {
|
|
h.flashErrorOrFail(c, h.url("network"), err)
|
|
return
|
|
}
|
|
|
|
nc := fed.ActorClient{
|
|
HTTPClient: h.httpClient,
|
|
Logger: h.logger,
|
|
}
|
|
err = nc.SendToInbox(ctx, h.userActor(user, userActor), followerActor, responsePayload)
|
|
if err != nil {
|
|
h.logger.Warn("unable to unfollow actor",
|
|
zap.String("remote_actor", followerActor.ActorID),
|
|
zap.String("local_actor", userActor.ActorID))
|
|
}
|
|
|
|
c.Redirect(http.StatusFound, h.url("network"))
|
|
}
|