Fix follow activity insertion, impl undo follow
Some checks failed
/ docker (push) Failing after 2m59s
Some checks failed
/ docker (push) Failing after 2m59s
- Follow created a new id instead of using the Id of the activity for the db activity
This commit is contained in:
parent
d84a693b22
commit
e1051e81ac
3 changed files with 225 additions and 133 deletions
|
@ -9,5 +9,5 @@ type UserToUserRelation struct {
|
||||||
UserId string
|
UserId string
|
||||||
TargetUser User // The user Y described in [RelationType]
|
TargetUser User // The user Y described in [RelationType]
|
||||||
TargetUserId string
|
TargetUserId string
|
||||||
Relation string `gorm:"type:relation_type"`
|
Relation string // `gorm:"type:relation_type"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
webutils "git.mstar.dev/mstar/goutils/http"
|
webutils "git.mstar.dev/mstar/goutils/http"
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
|
@ -15,7 +14,6 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||||
"git.mstar.dev/mstar/linstrom/shared"
|
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||||
)
|
)
|
||||||
|
@ -198,136 +196,21 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|
||||||
log := hlog.FromRequest(r)
|
|
||||||
_ = object["id"].(string)
|
|
||||||
_, ok := object["actor"].(string)
|
|
||||||
if !ok {
|
|
||||||
webutils.ProblemDetails(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"/errors/bad-request-data",
|
|
||||||
"Bad activity data",
|
|
||||||
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rawTarget, ok := object["object"]
|
|
||||||
if !ok {
|
|
||||||
webutils.ProblemDetails(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"/errors/bad-request-data",
|
|
||||||
"Bad activity data",
|
|
||||||
other.IntoPointer(`Request data needs to contain a field "object"`),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// FIXME: Also handle other undo cases, such as follows
|
|
||||||
var targetObjectId string
|
|
||||||
var targetObjectType string
|
|
||||||
// I *think* the spec says that this must be an object. Not sure though
|
|
||||||
switch target := rawTarget.(type) {
|
|
||||||
case string:
|
|
||||||
targetObjectId = target
|
|
||||||
case map[string]any:
|
|
||||||
objType, ok := target["type"].(string)
|
|
||||||
if !ok {
|
|
||||||
webutils.ProblemDetails(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"/errors/bad-request-data",
|
|
||||||
"Bad activity data",
|
|
||||||
other.IntoPointer(`Target object does not have a type`),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
targetObjectType = objType
|
|
||||||
targetObjectId, ok = target["id"].(string)
|
|
||||||
if !ok {
|
|
||||||
webutils.ProblemDetails(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"/errors/bad-request-data",
|
|
||||||
"Bad activity data",
|
|
||||||
other.IntoPointer(`Missing id in undone object`),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
webutils.ProblemDetails(
|
|
||||||
w,
|
|
||||||
http.StatusBadRequest,
|
|
||||||
"/errors/bad-request-data",
|
|
||||||
"Bad activity data",
|
|
||||||
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// TODO: For above todo, get target resource via likeActivityId and remove approprietly
|
|
||||||
// Don't just assume it was a like being undone
|
|
||||||
act, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(targetObjectId), dbgen.Activity.Type.Eq("like")).
|
|
||||||
First()
|
|
||||||
switch err {
|
|
||||||
case gorm.ErrRecordNotFound:
|
|
||||||
return
|
|
||||||
case nil:
|
|
||||||
default:
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Str("activity-id", targetObjectId).
|
|
||||||
Msg("Error while looking for find activity")
|
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reactionId := uint(other.Must(strconv.ParseUint(act.ObjectId, 10, 64)))
|
|
||||||
reaction, err := dbgen.Reaction.Where(dbgen.Reaction.ID.Eq(reactionId)).First()
|
|
||||||
switch err {
|
|
||||||
case gorm.ErrRecordNotFound:
|
|
||||||
return
|
|
||||||
case nil:
|
|
||||||
default:
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Str("activity-id", targetObjectId).
|
|
||||||
Msg("Error while looking for find activity")
|
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tx := dbgen.Q.Begin()
|
|
||||||
_, err = tx.Activity.Where(dbgen.Activity.Id.Eq(act.Id)).Delete()
|
|
||||||
if err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
log.Error().Err(err).Str("activity-id", act.Id).Msg("Failed to delete activity on undo")
|
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = tx.Reaction.Where(dbgen.Reaction.ID.Eq(reaction.ID)).Delete()
|
|
||||||
if err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
log.Error().
|
|
||||||
Err(err).
|
|
||||||
Uint("reaction-id", reaction.ID).
|
|
||||||
Msg("Failed to delete reaction on undo")
|
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to delete reaction and activity")
|
|
||||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
log.Debug().Msg("Received follow request")
|
log.Debug().Msg("Received follow request")
|
||||||
_ = object["id"].(string)
|
objectId, ok := object["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Request data needs to contain a field "id" with a string value`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
actorApId, ok := object["actor"].(string)
|
actorApId, ok := object["actor"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
webutils.ProblemDetails(
|
webutils.ProblemDetails(
|
||||||
|
@ -382,8 +265,8 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
u2u.UserId.Eq(follower.ID),
|
u2u.UserId.Eq(follower.ID),
|
||||||
u2u.TargetUserId.Eq(followed.ID),
|
u2u.TargetUserId.Eq(followed.ID),
|
||||||
u2u.Or(
|
u2u.Or(
|
||||||
u2u.Relation.Eq(models.RelationFollow),
|
u2u.Relation.Eq(string(models.RelationFollow)),
|
||||||
u2u.Relation.Eq(models.RelationFollowRequest),
|
u2u.Relation.Eq(string(models.RelationFollowRequest)),
|
||||||
),
|
),
|
||||||
).Count()
|
).Count()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -412,7 +295,7 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
activity := models.Activity{
|
activity := models.Activity{
|
||||||
Id: shared.NewId(),
|
Id: objectId,
|
||||||
Type: string(models.ActivityFollow),
|
Type: string(models.ActivityFollow),
|
||||||
ObjectId: fmt.Sprint(req.ID),
|
ObjectId: fmt.Sprint(req.ID),
|
||||||
ObjectType: uint32(models.ActivitystreamsActivityTargetFollow),
|
ObjectType: uint32(models.ActivitystreamsActivityTargetFollow),
|
||||||
|
|
209
web/public/api/activitypub/inboxUndo.go
Normal file
209
web/public/api/activitypub/inboxUndo.go
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
package activitypub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
webutils "git.mstar.dev/mstar/goutils/http"
|
||||||
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
_ = object["id"].(string)
|
||||||
|
_, ok := object["actor"].(string)
|
||||||
|
if !ok {
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawTarget, ok := object["object"]
|
||||||
|
if !ok {
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Request data needs to contain a field "object"`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// FIXME: Also handle other undo cases, such as follows
|
||||||
|
var targetObjectId string
|
||||||
|
var targetObjectType string
|
||||||
|
// I *think* the spec says that this must be an object. Not sure though
|
||||||
|
switch target := rawTarget.(type) {
|
||||||
|
case string:
|
||||||
|
targetObjectId = target
|
||||||
|
case map[string]any:
|
||||||
|
objType, ok := target["type"].(string)
|
||||||
|
if !ok {
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Target object does not have a type`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetObjectType = objType
|
||||||
|
targetObjectId, ok = target["id"].(string)
|
||||||
|
if !ok {
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Missing id in undone object`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
webutils.ProblemDetails(
|
||||||
|
w,
|
||||||
|
http.StatusBadRequest,
|
||||||
|
"/errors/bad-request-data",
|
||||||
|
"Bad activity data",
|
||||||
|
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch targetObjectType {
|
||||||
|
case "Like":
|
||||||
|
undoLike(w, r, object, targetObjectId)
|
||||||
|
case "Follow":
|
||||||
|
undoFollow(w, r, object, targetObjectId)
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Str("undo-target-type", targetObjectType).
|
||||||
|
Msg("Unknown/unimplemented undo target type")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func undoLike(w http.ResponseWriter, r *http.Request, object map[string]any, targetId string) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
act, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(targetId), dbgen.Activity.Type.Eq("like")).
|
||||||
|
First()
|
||||||
|
switch err {
|
||||||
|
case gorm.ErrRecordNotFound:
|
||||||
|
return
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", targetId).
|
||||||
|
Msg("Error while looking for like activity")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reactionId := uint(other.Must(strconv.ParseUint(act.ObjectId, 10, 64)))
|
||||||
|
reaction, err := dbgen.Reaction.Where(dbgen.Reaction.ID.Eq(reactionId)).First()
|
||||||
|
switch err {
|
||||||
|
case gorm.ErrRecordNotFound:
|
||||||
|
return
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", targetId).
|
||||||
|
Msg("Error while looking for find activity")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx := dbgen.Q.Begin()
|
||||||
|
_, err = tx.Activity.Where(dbgen.Activity.Id.Eq(act.Id)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
log.Error().Err(err).Str("activity-id", act.Id).Msg("Failed to delete activity on undo")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = tx.Reaction.Where(dbgen.Reaction.ID.Eq(reaction.ID)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Uint("reaction-id", reaction.ID).
|
||||||
|
Msg("Failed to delete reaction on undo")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to delete reaction and activity")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func undoFollow(w http.ResponseWriter, r *http.Request, object map[string]any, targetId string) {
|
||||||
|
log := hlog.FromRequest(r)
|
||||||
|
act, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(targetId), dbgen.Activity.Type.Eq("follow")).
|
||||||
|
First()
|
||||||
|
switch err {
|
||||||
|
case gorm.ErrRecordNotFound:
|
||||||
|
return
|
||||||
|
case nil:
|
||||||
|
default:
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", targetId).
|
||||||
|
Msg("Error while looking for follow activity")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
relationId := other.Must(strconv.ParseUint(act.ObjectId, 10, 64))
|
||||||
|
tx := dbgen.Q.Begin()
|
||||||
|
_, err = tx.Activity.Where(dbgen.Activity.Id.Eq(act.Id)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", act.Id).
|
||||||
|
Msg("Failed to delete follow activity on undo")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Delete all activities that reference the follow activity (so accept/reject if exists)
|
||||||
|
_, err = tx.Activity.Where(dbgen.Activity.ObjectId.Eq(act.Id)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", act.Id).
|
||||||
|
Msg("Failed to delete accept/reject activity for follow on undo")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = tx.UserToUserRelation.Where(dbgen.UserToUserRelation.ID.Eq(relationId)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("activity-id", act.Id).
|
||||||
|
Uint64("relation-id", relationId).
|
||||||
|
Msg("Failed to delete user-to-user relation for follow on undo")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to delete reaction and activity")
|
||||||
|
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue