If the processing for an activity failed due to internal reasons (NOT malformed content), store it for a later attempt
This commit is contained in:
parent
604e25c451
commit
f727b30f32
2 changed files with 163 additions and 117 deletions
|
@ -1,6 +1,7 @@
|
||||||
package activitypub
|
package activitypub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/rs/zerolog/hlog"
|
"github.com/rs/zerolog/hlog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||||
|
@ -92,19 +94,20 @@ func userInbox(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Decide how to handle the handler failing for whatever reason
|
// TODO: Decide how to handle the handler failing for whatever reason
|
||||||
// Add object to unhandled message table and try again later?
|
// Add object to unhandled message table and try again later?
|
||||||
// Discard it? And how would a handler return that it failed?
|
// Discard it? And how would a handler return that it failed?
|
||||||
|
ok = true
|
||||||
switch objectType {
|
switch objectType {
|
||||||
case "Like":
|
case "Like":
|
||||||
handleLike(w, r, data)
|
ok = handleLike(w, r, data)
|
||||||
case "Undo":
|
case "Undo":
|
||||||
handleUndo(w, r, data)
|
ok = handleUndo(w, r, data)
|
||||||
case "Follow":
|
case "Follow":
|
||||||
handleFollow(w, r, data)
|
ok = handleFollow(w, r, data)
|
||||||
case "Accept":
|
case "Accept":
|
||||||
handleAccept(w, r, data)
|
ok = handleAccept(w, r, data)
|
||||||
case "Reject":
|
case "Reject":
|
||||||
handleReject(w, r, data)
|
ok = handleReject(w, r, data)
|
||||||
case "Create":
|
case "Create":
|
||||||
handleCreate(w, r, data)
|
ok = handleCreate(w, r, data)
|
||||||
default:
|
default:
|
||||||
log.Warn().
|
log.Warn().
|
||||||
Str("object-type", objectType).
|
Str("object-type", objectType).
|
||||||
|
@ -119,9 +122,23 @@ func userInbox(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, 500)
|
_ = webutils.ProblemDetailsStatusOnly(w, 500)
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
|
err = dbgen.UnhandledMessage.Create(&models.UnhandledMessage{
|
||||||
|
RawData: body,
|
||||||
|
ForUserId: userId,
|
||||||
|
GlobalInbox: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Bytes("body", body).
|
||||||
|
Str("user-id", userId).
|
||||||
|
Msg("Failed to store failed inbound message for later processing")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
activityId := object["id"].(string)
|
activityId := object["id"].(string)
|
||||||
likerUrl, ok := object["actor"].(string)
|
likerUrl, ok := object["actor"].(string)
|
||||||
|
@ -134,7 +151,7 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
// TODO: Account for case where object is embedded in like
|
// TODO: Account for case where object is embedded in like
|
||||||
targetUrl, ok := object["object"].(string)
|
targetUrl, ok := object["object"].(string)
|
||||||
|
@ -147,7 +164,7 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
other.IntoPointer(`Request data needs to contain a field "object" with a string value`),
|
other.IntoPointer(`Request data needs to contain a field "object" with a string value`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
targetIdMatches := objectIdRegex.FindStringSubmatch(targetUrl)
|
targetIdMatches := objectIdRegex.FindStringSubmatch(targetUrl)
|
||||||
if len(targetIdMatches) != 2 {
|
if len(targetIdMatches) != 2 {
|
||||||
|
@ -156,7 +173,7 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
Str("url", targetUrl).
|
Str("url", targetUrl).
|
||||||
Msg("Url didn't match id extractor regex")
|
Msg("Url didn't match id extractor regex")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
targetId := targetIdMatches[1]
|
targetId := targetIdMatches[1]
|
||||||
// Assume likes can only happen on notes for now
|
// Assume likes can only happen on notes for now
|
||||||
|
@ -173,11 +190,11 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
other.IntoPointer("There is no note with the target id"),
|
other.IntoPointer("There is no note with the target id"),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
default:
|
default:
|
||||||
log.Error().Err(err).Str("note-id", targetId).Msg("Failed to get note from db")
|
log.Error().Err(err).Str("note-id", targetId).Msg("Failed to get note from db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
// Import liker after verifying that target note is correct
|
// Import liker after verifying that target note is correct
|
||||||
liker, err := activitypub.ImportRemoteAccountByAPUrl(likerUrl)
|
liker, err := activitypub.ImportRemoteAccountByAPUrl(likerUrl)
|
||||||
|
@ -187,7 +204,7 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
Str("liker-url", likerUrl).
|
Str("liker-url", likerUrl).
|
||||||
Msg("Failed to import liking remote account")
|
Msg("Failed to import liking remote account")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
reaction := models.Reaction{
|
reaction := models.Reaction{
|
||||||
Note: *note,
|
Note: *note,
|
||||||
|
@ -204,6 +221,7 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
// TODO: Create corresponding activity too
|
// TODO: Create corresponding activity too
|
||||||
activity := models.Activity{
|
activity := models.Activity{
|
||||||
|
@ -217,15 +235,18 @@ func handleLike(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
log.Error().Err(err).Any("raw-reaction", reaction).Msg("Failed to store reaction in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to commit reaction transaction to db")
|
log.Error().Err(err).Msg("Failed to commit reaction transaction to db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
log.Debug().Msg("Received follow request")
|
log.Debug().Msg("Received follow request")
|
||||||
objectId, ok := object["id"].(string)
|
objectId, ok := object["id"].(string)
|
||||||
|
@ -238,7 +259,7 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "id" with a string value`),
|
other.IntoPointer(`Request data needs to contain a field "id" with a string value`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
actorApId, ok := object["actor"].(string)
|
actorApId, ok := object["actor"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -250,7 +271,7 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
other.IntoPointer(`Request data needs to contain a field "actor" with a string value`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
targetUrl, ok := object["object"].(string)
|
targetUrl, ok := object["object"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -262,7 +283,7 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "object" of type string`),
|
other.IntoPointer(`Request data needs to contain a field "object" of type string`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
followedMatch := objectIdRegex.FindStringSubmatch(targetUrl)
|
followedMatch := objectIdRegex.FindStringSubmatch(targetUrl)
|
||||||
if len(followedMatch) != 2 {
|
if len(followedMatch) != 2 {
|
||||||
|
@ -274,25 +295,25 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Object must be a link to a Linstrom AP user`),
|
other.IntoPointer(`Object must be a link to a Linstrom AP user`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
followedId := followedMatch[1]
|
followedId := followedMatch[1]
|
||||||
followed, err := dbgen.User.Where(dbgen.User.ID.Eq(followedId)).First()
|
followed, err := dbgen.User.Where(dbgen.User.ID.Eq(followedId)).First()
|
||||||
switch err {
|
switch err {
|
||||||
case gorm.ErrRecordNotFound:
|
case gorm.ErrRecordNotFound:
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||||
return
|
return true
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
log.Error().Err(err).Str("target-id", followedId).Msg("Failed to get account from db")
|
log.Error().Err(err).Str("target-id", followedId).Msg("Failed to get account from db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
follower, err := activitypub.ImportRemoteAccountByAPUrl(actorApId)
|
follower, err := activitypub.ImportRemoteAccountByAPUrl(actorApId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to import following account")
|
log.Error().Err(err).Msg("Failed to import following account")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
u2u := dbgen.UserToUserRelation
|
u2u := dbgen.UserToUserRelation
|
||||||
followRelations, err := u2u.Where(
|
followRelations, err := u2u.Where(
|
||||||
|
@ -310,10 +331,10 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("followed", followedId).
|
Str("followed", followedId).
|
||||||
Msg("Failed to count follow relations")
|
Msg("Failed to count follow relations")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if followRelations > 0 {
|
if followRelations > 0 {
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
tx := dbgen.Q.Begin()
|
tx := dbgen.Q.Begin()
|
||||||
req := models.UserToUserRelation{
|
req := models.UserToUserRelation{
|
||||||
|
@ -326,7 +347,7 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("follow-request", req).Msg("Failed to store follow request")
|
log.Error().Err(err).Any("follow-request", req).Msg("Failed to store follow request")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
activity := models.Activity{
|
activity := models.Activity{
|
||||||
Id: objectId,
|
Id: objectId,
|
||||||
|
@ -339,79 +360,31 @@ func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("activity", activity).Msg("Failed to store follow activity")
|
log.Error().Err(err).Any("activity", activity).Msg("Failed to store follow activity")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to commit follow activity transaction")
|
log.Error().Err(err).Msg("Failed to commit follow activity transaction")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
if !followed.RestrictedFollow {
|
if !followed.RestrictedFollow {
|
||||||
tx = dbgen.Q.Begin()
|
// TODO: Move this to a separate function
|
||||||
_, err = u2u.Where(u2u.ID.Eq(req.ID)).UpdateColumn(u2u.Relation, models.RelationFollow)
|
|
||||||
if err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
log.Error().Err(err).Msg("Failed to update follow to confirmed")
|
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
acceptActivity := models.Activity{
|
|
||||||
Id: shared.NewId(),
|
|
||||||
Type: string(models.ActivityAccept),
|
|
||||||
ObjectId: activity.Id,
|
|
||||||
ObjectType: uint32(models.ActivitystreamsActivityTargetActivity),
|
|
||||||
}
|
|
||||||
err = tx.Activity.Create(&acceptActivity)
|
|
||||||
if err != nil {
|
|
||||||
_ = tx.Rollback()
|
|
||||||
log.Error().Err(err).Msg("Failed to store accept activity in db")
|
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = tx.Commit()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to commit follow accept to db")
|
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
// TODO: Maybe move this part to a separate function
|
err := AcceptFollow(req.ID, followedId, followedId)
|
||||||
time.Sleep(time.Millisecond * 20)
|
|
||||||
webAccept, err := AcceptFromStorage(r.Context(), acceptActivity.Id)
|
|
||||||
if err != nil {
|
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.RequestSigned(
|
|
||||||
"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().
|
log.Error().
|
||||||
Int("status-code", res.StatusCode).
|
Err(err).
|
||||||
Bytes("body", body).
|
Uint64("follow-request-id", req.ID).
|
||||||
Msg("Post of accept failed")
|
Msg("Failed to auto-accept follow request")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARN: Untested as can't send follow activities yet
|
// WARN: Untested as can't send follow activities yet
|
||||||
func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
rawTarget, ok := object["object"]
|
rawTarget, ok := object["object"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -423,7 +396,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "object"`),
|
other.IntoPointer(`Request data needs to contain a field "object"`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
// FIXME: Also handle other undo cases, such as follows
|
// FIXME: Also handle other undo cases, such as follows
|
||||||
var targetObjectId string
|
var targetObjectId string
|
||||||
|
@ -443,7 +416,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Target object type must be a string with value "Follow"`),
|
other.IntoPointer(`Target object type must be a string with value "Follow"`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
targetObjectId, ok = target["id"].(string)
|
targetObjectId, ok = target["id"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -455,7 +428,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Missing id in undone object`),
|
other.IntoPointer(`Missing id in undone object`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
_ = webutils.ProblemDetails(
|
_ = webutils.ProblemDetails(
|
||||||
|
@ -466,7 +439,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
internalIdMatch := objectIdRegex.FindStringSubmatch(targetObjectId)
|
internalIdMatch := objectIdRegex.FindStringSubmatch(targetObjectId)
|
||||||
if len(internalIdMatch) != 2 {
|
if len(internalIdMatch) != 2 {
|
||||||
|
@ -478,14 +451,14 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data target object is not internal id`),
|
other.IntoPointer(`Request data target object is not internal id`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
internalId := internalIdMatch[1]
|
internalId := internalIdMatch[1]
|
||||||
followActivity, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(internalId)).First()
|
followActivity, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(internalId)).First()
|
||||||
switch err {
|
switch err {
|
||||||
case gorm.ErrRecordNotFound:
|
case gorm.ErrRecordNotFound:
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||||
return
|
return true
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
log.Error().
|
log.Error().
|
||||||
|
@ -493,7 +466,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to get target follow activity from db")
|
Msg("Failed to get target follow activity from db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
|
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
|
||||||
dbrel := dbgen.UserToUserRelation
|
dbrel := dbgen.UserToUserRelation
|
||||||
|
@ -504,7 +477,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
case gorm.ErrRecordNotFound:
|
case gorm.ErrRecordNotFound:
|
||||||
// No need to rollback, nothing was done
|
// No need to rollback, nothing was done
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||||
return
|
return true
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
|
@ -513,7 +486,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to update follow status to confirmed follow")
|
Msg("Failed to update follow status to confirmed follow")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
activity := models.Activity{
|
activity := models.Activity{
|
||||||
Id: object["id"].(string),
|
Id: object["id"].(string),
|
||||||
|
@ -529,7 +502,7 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to store accept activity in db")
|
Msg("Failed to store accept activity in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -538,12 +511,13 @@ func handleAccept(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to commit accept transaction")
|
Msg("Failed to commit accept transaction")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARN: Untested as can't send follow activities yet
|
// WARN: Untested as can't send follow activities yet
|
||||||
func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
rawTarget, ok := object["object"]
|
rawTarget, ok := object["object"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -555,7 +529,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "object"`),
|
other.IntoPointer(`Request data needs to contain a field "object"`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
// FIXME: Also handle other undo cases, such as follows
|
// FIXME: Also handle other undo cases, such as follows
|
||||||
var targetObjectId string
|
var targetObjectId string
|
||||||
|
@ -575,7 +549,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Target object type must be a string with value "Follow"`),
|
other.IntoPointer(`Target object type must be a string with value "Follow"`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
targetObjectId, ok = target["id"].(string)
|
targetObjectId, ok = target["id"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -587,7 +561,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Missing id in undone object`),
|
other.IntoPointer(`Missing id in undone object`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
_ = webutils.ProblemDetails(
|
_ = webutils.ProblemDetails(
|
||||||
|
@ -598,7 +572,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
other.IntoPointer(`Request data needs to contain a field "object" of type string or object`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
internalIdMatch := objectIdRegex.FindStringSubmatch(targetObjectId)
|
internalIdMatch := objectIdRegex.FindStringSubmatch(targetObjectId)
|
||||||
if len(internalIdMatch) != 2 {
|
if len(internalIdMatch) != 2 {
|
||||||
|
@ -610,14 +584,14 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer(`Request data target object is not internal id`),
|
other.IntoPointer(`Request data target object is not internal id`),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
internalId := internalIdMatch[1]
|
internalId := internalIdMatch[1]
|
||||||
followActivity, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(internalId)).First()
|
followActivity, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(internalId)).First()
|
||||||
switch err {
|
switch err {
|
||||||
case gorm.ErrRecordNotFound:
|
case gorm.ErrRecordNotFound:
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||||
return
|
return true
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
log.Error().
|
log.Error().
|
||||||
|
@ -625,7 +599,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to get target follow activity from db")
|
Msg("Failed to get target follow activity from db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
|
relationId := other.Must(strconv.ParseUint(followActivity.ObjectId, 10, 64))
|
||||||
dbrel := dbgen.UserToUserRelation
|
dbrel := dbgen.UserToUserRelation
|
||||||
|
@ -635,7 +609,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
case gorm.ErrRecordNotFound:
|
case gorm.ErrRecordNotFound:
|
||||||
// No need to rollback, nothing was done
|
// No need to rollback, nothing was done
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||||
return
|
return true
|
||||||
case nil:
|
case nil:
|
||||||
default:
|
default:
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
|
@ -644,7 +618,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to delete follow status")
|
Msg("Failed to delete follow status")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
_, err = tx.Activity.Where(
|
_, err = tx.Activity.Where(
|
||||||
dbgen.Activity.ObjectId.Eq(followActivity.Id),
|
dbgen.Activity.ObjectId.Eq(followActivity.Id),
|
||||||
|
@ -655,7 +629,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Msg("Failed to delete accept for later rejected follow")
|
log.Error().Err(err).Msg("Failed to delete accept for later rejected follow")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
activity := models.Activity{
|
activity := models.Activity{
|
||||||
Id: object["id"].(string),
|
Id: object["id"].(string),
|
||||||
|
@ -671,7 +645,7 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to store accept activity in db")
|
Msg("Failed to store accept activity in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -680,11 +654,12 @@ func handleReject(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("target-id", internalId).
|
Str("target-id", internalId).
|
||||||
Msg("Failed to commit accept transaction")
|
Msg("Failed to commit accept transaction")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
activity := ActivityCreate{}
|
activity := ActivityCreate{}
|
||||||
err := mapstructure.Decode(object, &activity)
|
err := mapstructure.Decode(object, &activity)
|
||||||
|
@ -694,7 +669,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Any("raw", object).
|
Any("raw", object).
|
||||||
Msg("Failed to marshal create activity to proper type")
|
Msg("Failed to marshal create activity to proper type")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
actingUser, err := activitypub.ImportRemoteAccountByAPUrl(activity.Actor)
|
actingUser, err := activitypub.ImportRemoteAccountByAPUrl(activity.Actor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -703,7 +678,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Str("actor", activity.Actor).
|
Str("actor", activity.Actor).
|
||||||
Msg("Failed to import remote actor for note")
|
Msg("Failed to import remote actor for note")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
switch val := activity.Object.(type) {
|
switch val := activity.Object.(type) {
|
||||||
case string:
|
case string:
|
||||||
|
@ -711,14 +686,14 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get local actor for importing targeted note")
|
log.Error().Err(err).Msg("Failed to get local actor for importing targeted note")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
_, err = activitypub.ImportRemoteNote(val, actor)
|
_, err = activitypub.ImportRemoteNote(val, actor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("note-url", val).Msg("Failed to import remote note that landed as id in the inbox")
|
log.Error().Err(err).Str("note-url", val).Msg("Failed to import remote note that landed as id in the inbox")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
return
|
return false
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
default:
|
default:
|
||||||
_ = webutils.ProblemDetails(
|
_ = webutils.ProblemDetails(
|
||||||
|
@ -729,7 +704,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer("Bad object data for create activity. Must be a struct or string"),
|
other.IntoPointer("Bad object data for create activity. Must be a struct or string"),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
obj := activity.Object.(map[string]any)
|
obj := activity.Object.(map[string]any)
|
||||||
// Dumb hack since published timestamp is still a string at this point
|
// Dumb hack since published timestamp is still a string at this point
|
||||||
|
@ -747,7 +722,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
Any("raw", activity.Object).
|
Any("raw", activity.Object).
|
||||||
Msg("Failed to unmarshal create object into note")
|
Msg("Failed to unmarshal create object into note")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
if objectNote.Type != "Note" {
|
if objectNote.Type != "Note" {
|
||||||
_ = webutils.ProblemDetails(
|
_ = webutils.ProblemDetails(
|
||||||
|
@ -758,7 +733,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
other.IntoPointer("Bad object data for create activity. object.type must be 'Note'"),
|
other.IntoPointer("Bad object data for create activity. object.type must be 'Note'"),
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
dbNote := models.Note{
|
dbNote := models.Note{
|
||||||
ID: objectNote.Id,
|
ID: objectNote.Id,
|
||||||
|
@ -788,7 +763,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("note", dbNote).Msg("Failed to create note in db")
|
log.Error().Err(err).Any("note", dbNote).Msg("Failed to create note in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
createActivity := models.Activity{
|
createActivity := models.Activity{
|
||||||
Type: string(models.ActivityCreate),
|
Type: string(models.ActivityCreate),
|
||||||
|
@ -801,12 +776,83 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
log.Error().Err(err).Any("note", dbNote).Msg("Failed to create note create activity in db")
|
log.Error().Err(err).Any("note", dbNote).Msg("Failed to create note create activity in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Any("note", dbNote).Msg("Failed to submit note creation in db")
|
log.Error().Err(err).Any("note", dbNote).Msg("Failed to submit note creation in db")
|
||||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func AcceptFollow(
|
||||||
|
ctx context.Context,
|
||||||
|
followRequestRelationId uint64,
|
||||||
|
followerId, followedId string,
|
||||||
|
) error {
|
||||||
|
u2u := dbgen.UserToUserRelation
|
||||||
|
a := dbgen.Activity
|
||||||
|
followRequestActivity, err := a.Where(a.ObjectId.Eq(fmt.Sprint(followRequestRelationId))).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
return other.Error("webaction", "failed to get follow activity", err)
|
||||||
|
}
|
||||||
|
follower, err := dbgen.User.GetById(followedId)
|
||||||
|
if err != nil {
|
||||||
|
return other.Error("webaction", "failed to get follower from db", err)
|
||||||
|
}
|
||||||
|
followed, err := dbgen.User.GetById(followedId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx := dbgen.Q.Begin()
|
||||||
|
_, err = u2u.Where(u2u.ID.Eq(followRequestRelationId)).
|
||||||
|
UpdateColumn(u2u.Relation, models.RelationFollow)
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
acceptActivity := models.Activity{
|
||||||
|
Id: shared.NewId(),
|
||||||
|
Type: string(models.ActivityAccept),
|
||||||
|
ObjectId: followRequestActivity.Id,
|
||||||
|
ObjectType: uint32(models.ActivitystreamsActivityTargetActivity),
|
||||||
|
}
|
||||||
|
err = tx.Activity.Create(&acceptActivity)
|
||||||
|
if err != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
webAccept, err := AcceptFromStorage(ctx, acceptActivity.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
webAccept.Context = activitypub.BaseLdContext
|
||||||
|
body, err := json.Marshal(webAccept)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res, _, err := webshared.RequestSigned(
|
||||||
|
"POST",
|
||||||
|
follower.RemoteInfo.InboxLink,
|
||||||
|
body,
|
||||||
|
followed,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res.StatusCode >= 400 {
|
||||||
|
body, _ = io.ReadAll(res.Body)
|
||||||
|
log.Error().
|
||||||
|
Int("status-code", res.StatusCode).
|
||||||
|
Bytes("body", body).
|
||||||
|
Msg("Post of accept failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) bool {
|
||||||
log := hlog.FromRequest(r)
|
log := hlog.FromRequest(r)
|
||||||
_ = object["id"].(string)
|
_ = object["id"].(string)
|
||||||
_, ok := object["actor"].(string)
|
_, ok := object["actor"].(string)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue