linstrom/web/public/api/activitypub/inboxUndo.go
mstar 27b7d342b7
Fix more compiler errors, logic error in main
- Forgot to also update the undo handler for the inbox
- Fixed main not ever returning if the debug server was not enabled
2025-06-11 15:55:52 +02:00

219 lines
6 KiB
Go

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) bool {
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 true
}
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 true
}
// 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 true
}
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 true
}
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 true
}
switch targetObjectType {
case "Like":
return undoLike(w, r, object, targetObjectId)
case "Follow":
return undoFollow(w, r, object, targetObjectId)
default:
log.Error().
Str("undo-target-type", targetObjectType).
Msg("Unknown/unimplemented undo target type")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
}
func undoLike(w http.ResponseWriter, r *http.Request, object map[string]any, targetId string) bool {
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 true
case nil:
default:
log.Error().
Err(err).
Str("activity-id", targetId).
Msg("Error while looking for like activity")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
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 true
case nil:
default:
log.Error().
Err(err).
Str("activity-id", targetId).
Msg("Error while looking for find activity")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
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 false
}
_, 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 false
}
err = tx.Commit()
if err != nil {
log.Error().Err(err).Msg("Failed to delete reaction and activity")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
return true
}
func undoFollow(
w http.ResponseWriter,
r *http.Request,
object map[string]any,
targetId string,
) bool {
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 true
case nil:
default:
log.Error().
Err(err).
Str("activity-id", targetId).
Msg("Error while looking for follow activity")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
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 false
}
// 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 false
}
_, 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 false
}
err = tx.Commit()
if err != nil {
log.Error().Err(err).Msg("Failed to delete reaction and activity")
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return false
}
return true
}