Follow accept works and messags are pushed as expected
Some checks failed
/ docker (push) Failing after 2m50s

This commit is contained in:
Melody Becker 2025-05-10 11:18:28 +02:00
parent 9a3a330b1d
commit ff6a730e58
Signed by: mstar
SSH key fingerprint: SHA256:vkXfS9FG2pVNVfvDrzd1VW9n8VJzqqdKQGljxxX8uK8
10 changed files with 482 additions and 19 deletions

View file

@ -8,6 +8,7 @@ import (
"net/http"
"regexp"
"strconv"
"time"
webutils "git.mstar.dev/mstar/goutils/http"
"git.mstar.dev/mstar/goutils/other"
@ -15,8 +16,10 @@ import (
"gorm.io/gorm"
"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/models"
webshared "git.mstar.dev/mstar/linstrom/web/shared"
)
var objectIdRegex = regexp.MustCompile(
@ -316,8 +319,54 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
if err != nil {
log.Error().Err(err).Msg("Failed to commit follow activity transaction")
}
if !followed.RestrictedFollow {
// FIXME: Handle errors
tx = dbgen.Q.Begin()
_, err = u2u.Where(u2u.ID.Eq(req.ID)).UpdateColumn(u2u.Relation, models.RelationFollow)
acceptActivity := models.Activity{
Id: shared.NewId(),
Type: string(models.ActivityAccept),
ObjectId: activity.Id,
ObjectType: uint32(models.ActivitystreamsActivityTargetActivity),
}
err = tx.Activity.Create(&acceptActivity)
tx.Commit()
go func() {
// FIXME: Clean this entire mess up
time.Sleep(time.Millisecond * 20)
webAccept, err := AcceptFromStorage(r.Context(), acceptActivity.Id)
if err != nil {
log.Error().Err(err).Msg("Failed to get accept from db")
return
}
webAccept.Context = activitypub.BaseLdContext
body, err := json.Marshal(webAccept)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal accept")
return
}
res, err := webshared.RequestSignedCavage(
"POST",
follower.RemoteInfo.InboxLink,
body,
followed,
)
if err != nil {
log.Error().Err(err).Msg("Failed to send accept")
return
}
if res.StatusCode >= 400 {
body, _ = io.ReadAll(res.Body)
log.Error().
Int("status-code", res.StatusCode).
Bytes("body", body).
Msg("Post of accept failed")
}
}()
}
}
// WARN: Untested as can't send follow activities yet
func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any) {
log := hlog.FromRequest(r)
rawTarget, ok := object["object"]
@ -404,9 +453,124 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
}
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
dbrel := dbgen.UserToUserRelation
_, err = dbrel.Where(dbrel.ID.Eq(relationId)).
tx := dbgen.Q.Begin()
_, err = tx.UserToUserRelation.Where(dbrel.ID.Eq(relationId)).
UpdateColumn(dbrel.Relation, models.RelationFollow)
switch err {
case gorm.ErrRecordNotFound:
// No need to rollback, nothing was done
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
return
case nil:
default:
_ = tx.Rollback()
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to update follow status to confirmed follow")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
activity := models.Activity{
Id: object["id"].(string),
Type: string(models.ActivityAccept),
ObjectType: uint32(models.ActivitystreamsActivityTargetActivity),
ObjectId: followActivity.Id,
}
err = tx.Activity.Create(&activity)
if err != nil {
err = tx.Rollback()
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to store accept activity in db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
err = tx.Commit()
if err != nil {
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to commit accept transaction")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
}
// WARN: Untested as can't send follow activities yet
func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any) {
log := hlog.FromRequest(r)
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
// 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)
// TODO: Ensure accept is only used for follows
if !ok || objType != "Follow" {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Target object type must be a string with value "Follow"`),
nil,
)
return
}
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
}
internalIdMatch := objectIdRegex.FindStringSubmatch(targetObjectId)
if len(internalIdMatch) != 2 {
webutils.ProblemDetails(
w,
http.StatusBadRequest,
"/errors/bad-request-data",
"Bad activity data",
other.IntoPointer(`Request data target object is not internal id`),
nil,
)
return
}
internalId := internalIdMatch[1]
followActivity, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(internalId)).First()
switch err {
case gorm.ErrRecordNotFound:
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
return
@ -419,6 +583,59 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
dbrel := dbgen.UserToUserRelation
tx := dbgen.Q.Begin()
_, err = tx.UserToUserRelation.Where(dbrel.ID.Eq(relationId)).Delete()
switch err {
case gorm.ErrRecordNotFound:
// No need to rollback, nothing was done
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
return
case nil:
default:
_ = tx.Rollback()
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to delete follow status")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
_, err = tx.Activity.Where(
dbgen.Activity.ObjectId.Eq(followActivity.Id),
dbgen.Activity.Type.Eq("Accept"),
).
Delete()
if err != nil {
_ = tx.Rollback()
log.Error().Err(err).Msg("Failed to delete accept for later rejected follow")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
activity := models.Activity{
Id: object["id"].(string),
Type: string(models.ActivityAccept),
ObjectType: uint32(models.ActivitystreamsActivityTargetActivity),
ObjectId: followActivity.Id,
}
err = tx.Activity.Create(&activity)
if err != nil {
err = tx.Rollback()
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to store accept activity in db")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
err = tx.Commit()
if err != nil {
log.Error().
Err(err).
Str("target-id", internalId).
Msg("Failed to commit accept transaction")
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
return
}
}
func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any) {}