tavern/web/handler_network.go

406 lines
11 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/common"
"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
}
groups, err := h.storage.FilterGroupsByActorID(ctx, append(following, pendingFollowing...))
if err != nil {
h.hardFail(c, err)
return
}
followingActors := common.FilterStrings(following, common.StringsExcludeFF(groups))
pendingFollowingActors := common.FilterStrings(pendingFollowing, common.StringsExcludeFF(groups))
data["following"] = followingActors
data["pending_following"] = pendingFollowingActors
data["groups"] = common.FilterStrings(groups, common.StringsIncludeFF(following))
data["pending_groups"] = common.FilterStrings(groups, common.StringsIncludeFF(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
}
follow := storage.EmptyPayload()
activityID := common.ActivityURL(h.domain, storage.NewV4())
follow["@context"] = "https://www.w3.org/ns/activitystreams"
follow["actor"] = storage.NewActorID(user.Name, h.domain)
follow["id"] = 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()
if !isFollowing {
err = h.storage.CreatePendingFollowing(ctx, user.ID, actor.ID, follow)
if err != nil {
h.flashErrorOrFail(c, h.url("network"), err)
return
}
}
if h.publisherClient != nil {
err = h.publisherClient.Send(ctx, actor.GetInbox(), userActor.GetKeyID(), user.PrivateKey, activityID, string(payload))
if err != nil {
h.logger.Error("failed sending to actor", zap.String("target", actor.GetID()), zap.String("activity", activityID), zap.Error(err))
}
} else {
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 := common.ActivityURL(h.domain, storage.NewV4())
undoFollow["@context"] = "https://www.w3.org/ns/activitystreams"
undoFollow["actor"] = storage.NewActorID(user.Name, h.domain)
undoFollow["id"] = activityID
undoFollow["object"] = requestActivity
undoFollow["to"] = to
undoFollow["type"] = "Undo"
undoFollow["published"] = time.Now().UTC().Format("2006-01-02T15:04:05Z")
payload := undoFollow.Bytes()
err = h.storage.RemoveFollowing(ctx, user.ID, targetActor.ID)
if err != nil {
h.flashErrorOrFail(c, h.url("network"), err)
return
}
if h.publisherClient != nil {
err = h.publisherClient.Send(ctx, targetActor.GetInbox(), userActor.GetKeyID(), user.PrivateKey, activityID, string(payload))
if err != nil {
h.logger.Error("failed sending to actor", zap.String("target", targetActor.GetID()), zap.String("activity", activityID), zap.Error(err))
}
} else {
nc := fed.ActorClient{
HTTPClient: h.httpClient,
Logger: h.logger,
}
err = nc.SendToInbox(ctx, h.userActor(user, userActor), targetActor, 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
}
err = h.storage.UpdateFollowerApproved(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
}
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"))
}