diff --git a/web/public/api/activitypub/inbox.go b/web/public/api/activitypub/inbox.go index ddb5e89..906cc6a 100644 --- a/web/public/api/activitypub/inbox.go +++ b/web/public/api/activitypub/inbox.go @@ -7,6 +7,7 @@ import ( "io" "net/http" "regexp" + "strconv" webutils "git.mstar.dev/mstar/goutils/http" "git.mstar.dev/mstar/goutils/other" @@ -83,6 +84,8 @@ func userInbox(w http.ResponseWriter, r *http.Request) { switch objectType { case "Like": handleLike(w, r, data) + case "Undo": + handleUndo(w, r, data) default: webutils.ProblemDetailsStatusOnly(w, 500) } @@ -191,3 +194,128 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) { webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError) } } + +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 + } + // TODO: I *think* undo is only actively used for likes, but could also be used for + // undoing create's and thus removing notes + var likeActivityId string + // I *think* the spec says that this must be an object. Not sure though + switch target := rawTarget.(type) { + case string: + likeActivityId = target + case map[string]any: + objType, ok := target["type"].(string) + if !ok || objType != "Like" { + webutils.ProblemDetails( + w, + http.StatusBadRequest, + "/errors/bad-request-data", + "Bad activity data", + other.IntoPointer(`Undone object is not of type like. If you receive this error for a valid undo for a different activity type,please contact the Linstrom developers with the activity type`), + nil, + ) + return + } + likeActivityId, 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(likeActivityId), dbgen.Activity.Type.Eq("like")). + First() + switch err { + case gorm.ErrRecordNotFound: + return + case nil: + default: + log.Error(). + Err(err). + Str("activity-id", likeActivityId). + 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", likeActivityId). + 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) + } +}