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) } }