Follow accept works and messags are pushed as expected
Some checks failed
/ docker (push) Failing after 2m50s
Some checks failed
/ docker (push) Failing after 2m50s
This commit is contained in:
parent
9a3a330b1d
commit
ff6a730e58
10 changed files with 482 additions and 19 deletions
|
@ -50,6 +50,7 @@ func main() {
|
|||
g.ApplyInterface(func(models.IUser) {}, models.User{})
|
||||
g.ApplyInterface(func(models.IAccessToken) {}, models.AccessToken{})
|
||||
g.ApplyInterface(func(models.INote) {}, models.Note{})
|
||||
g.ApplyInterface(func(models.IUserToUserRelation) {}, models.UserToUserRelation{})
|
||||
|
||||
log.Info().Msg("Extra features applied, starting generation")
|
||||
g.Execute()
|
||||
|
|
|
@ -7,6 +7,7 @@ package dbgen
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
"gorm.io/gorm"
|
||||
|
@ -587,6 +588,30 @@ type IUserToUserRelationDo interface {
|
|||
Returning(value interface{}, columns ...string) IUserToUserRelationDo
|
||||
UnderlyingDB() *gorm.DB
|
||||
schema.Tabler
|
||||
|
||||
GetFollowersForId(id string) (result []string, err error)
|
||||
}
|
||||
|
||||
// SELECT u.inbox_link
|
||||
// FROM user_to_user_relations r
|
||||
// LEFT JOIN user_remote_links u
|
||||
// ON r.user_id = u.user_id
|
||||
// WHERE
|
||||
//
|
||||
// r.target_user_id = @id AND
|
||||
// r.relation = 'follow'
|
||||
func (u userToUserRelationDo) GetFollowersForId(id string) (result []string, err error) {
|
||||
var params []interface{}
|
||||
|
||||
var generateSQL strings.Builder
|
||||
params = append(params, id)
|
||||
generateSQL.WriteString("SELECT u.inbox_link FROM user_to_user_relations r LEFT JOIN user_remote_links u ON r.user_id = u.user_id WHERE r.target_user_id = ? AND r.relation = 'follow' ")
|
||||
|
||||
var executeSQL *gorm.DB
|
||||
executeSQL = u.UnderlyingDB().Raw(generateSQL.String(), params...).Find(&result) // ignore_security_alert
|
||||
err = executeSQL.Error
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (u userToUserRelationDo) Debug() IUserToUserRelationDo {
|
||||
|
|
|
@ -11,3 +11,14 @@ type UserToUserRelation struct {
|
|||
TargetUserId string
|
||||
Relation string // `gorm:"type:relation_type"`
|
||||
}
|
||||
|
||||
type IUserToUserRelation interface {
|
||||
// SELECT u.inbox_link
|
||||
// FROM user_to_user_relations r
|
||||
// LEFT JOIN user_remote_links u
|
||||
// ON r.user_id = u.user_id
|
||||
// WHERE
|
||||
// r.target_user_id = @id AND
|
||||
// r.relation = 'follow'
|
||||
GetFollowersForId(id string) ([]string, error)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[general]
|
||||
protocol = "https"
|
||||
domain = "serveo.net"
|
||||
subdomain = "b2f4e7c5596220d4c4957b24f6954220"
|
||||
domain = "lhr.life"
|
||||
subdomain = "d0deb4c7b3ee95"
|
||||
private_port = 8080
|
||||
public_port = 443
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
|
@ -11,10 +12,12 @@ import (
|
|||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/shared"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webap "git.mstar.dev/mstar/linstrom/web/public/api/activitypub"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
|
@ -66,7 +69,8 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
AccessLevel: models.NOTE_TARGET_PUBLIC,
|
||||
OriginId: 1,
|
||||
}
|
||||
err = n.Select(
|
||||
tx := dbgen.Q.Begin()
|
||||
err = tx.Note.Select(
|
||||
n.ID,
|
||||
n.CreatorId,
|
||||
n.RawContent,
|
||||
|
@ -78,6 +82,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
n.OriginId,
|
||||
).Create(¬e)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("username", data.Username).
|
||||
|
@ -92,11 +97,51 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
ObjectId: note.ID,
|
||||
ObjectType: uint32(models.ActivitystreamsActivityTargetNote),
|
||||
}
|
||||
a := dbgen.Activity
|
||||
err = a.Create(&activity)
|
||||
err = tx.Activity.Create(&activity)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
log.Error().Err(err).Msg("Failed to create activity for new note")
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to commit note creation")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
u2u := dbgen.UserToUserRelation
|
||||
links, err := u2u.GetFollowersForId(user.ID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get follower inbox links for user")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Debug().Strs("links", links).Send()
|
||||
act, err := webap.CreateFromStorage(r.Context(), activity.Id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to fetch and format new note")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
act.Context = activitypub.BaseLdContext
|
||||
outData, err := json.Marshal(act)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal new note")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, link := range links {
|
||||
log.Debug().Str("target-inbox", link).Msg("Sending message to")
|
||||
go func() {
|
||||
res, err := webshared.RequestSignedCavage("POST", link, outData, user)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("link", link).Msg("Failed to send create to target inbox")
|
||||
}
|
||||
if res.StatusCode >= 400 {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
log.Warn().Int("status-code", res.StatusCode).Bytes("body", body).Msg("Bad reply")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func notesFrom(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -1,5 +1,75 @@
|
|||
package activitypub
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
func activityAccept(w http.ResponseWriter, r *http.Request) {}
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type ActivityAcceptOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Actor string `json:"actor"`
|
||||
Object any `json:"object"`
|
||||
}
|
||||
|
||||
func activityAccept(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
id := r.PathValue("id")
|
||||
activity, err := CreateFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
case nil:
|
||||
activity.Context = activitypub.BaseLdContext
|
||||
data, err := json.Marshal(activity)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("activity", activity).Msg("Failed to marshal create activity")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/activity+json")
|
||||
fmt.Fprint(w, string(data))
|
||||
default:
|
||||
if storage.HandleReconnectError(err) {
|
||||
log.Error().Err(err).Msg("Connection failed, restart attempt started")
|
||||
} else {
|
||||
log.Error().Err(err).Msg("Failed to get create activity from db")
|
||||
}
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func AcceptFromStorage(ctx context.Context, id string) (*ActivityAcceptOut, error) {
|
||||
a := dbgen.Activity
|
||||
activity, err := a.Where(a.Id.Eq(id), a.Type.Eq(string(models.ActivityAccept))).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// switch activity.ObjectType {
|
||||
// case models.ActivitystreamsActivityTargetFollow:
|
||||
// default:
|
||||
// return nil, errors.New("unknown activity target type")
|
||||
// }
|
||||
follow, err := FollowFromStorage(ctx, activity.ObjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ActivityAcceptOut{
|
||||
Id: id,
|
||||
Actor: follow.Object.(string),
|
||||
Type: "Accept",
|
||||
Object: follow,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type activityCreateOut struct {
|
||||
type ActivityCreateOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
|
@ -28,7 +28,7 @@ type activityCreateOut struct {
|
|||
func activityCreate(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
id := r.PathValue("id")
|
||||
activity, err := createFromStorage(r.Context(), id)
|
||||
activity, err := CreateFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -56,7 +56,7 @@ func activityCreate(w http.ResponseWriter, r *http.Request) {
|
|||
// Does not set the context for the activity, in case the activity is embedded
|
||||
// in another activity or object. That's the responsibility of the handler
|
||||
// getting the final result
|
||||
func createFromStorage(ctx context.Context, id string) (*activityCreateOut, error) {
|
||||
func CreateFromStorage(ctx context.Context, id string) (*ActivityCreateOut, error) {
|
||||
// log := log.Ctx(ctx)
|
||||
a := dbgen.Activity
|
||||
activity, err := a.Where(a.Type.Eq(string(models.ActivityCreate))).
|
||||
|
@ -67,11 +67,11 @@ func createFromStorage(ctx context.Context, id string) (*activityCreateOut, erro
|
|||
}
|
||||
switch models.ActivitystreamsActivityTargetType(activity.ObjectType) {
|
||||
case models.ActivitystreamsActivityTargetNote:
|
||||
note, err := noteFromStorage(ctx, activity.ObjectId)
|
||||
note, err := NoteFromStorage(ctx, activity.ObjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := activityCreateOut{
|
||||
out := ActivityCreateOut{
|
||||
Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/create/" + id,
|
||||
Type: "Create",
|
||||
Actor: note.AttributedTo,
|
||||
|
|
|
@ -1 +1,95 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type ActivityFollowOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Actor string `json:"actor"`
|
||||
Object any `json:"object"`
|
||||
}
|
||||
|
||||
func activityFollow(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
id := r.PathValue("id")
|
||||
activity, err := FollowFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
case nil:
|
||||
activity.Context = activitypub.BaseLdContext
|
||||
data, err := json.Marshal(activity)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Any("activity", activity).Msg("Failed to marshal create activity")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Content-Type", "application/activity+json")
|
||||
fmt.Fprint(w, string(data))
|
||||
default:
|
||||
if storage.HandleReconnectError(err) {
|
||||
log.Error().Err(err).Msg("Connection failed, restart attempt started")
|
||||
} else {
|
||||
log.Error().Err(err).Msg("Failed to get create activity from db")
|
||||
}
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func FollowFromStorage(ctx context.Context, id string) (*ActivityFollowOut, error) {
|
||||
ac := dbgen.Activity
|
||||
u2u := dbgen.UserToUserRelation
|
||||
u := dbgen.User
|
||||
// log := log.Ctx(ctx)
|
||||
activity, err := ac.Where(ac.Id.Eq(id), ac.Type.Eq(string(models.ActivityFollow))).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
followId, err := strconv.ParseUint(activity.ObjectId, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relation, err := u2u.Where(u2u.ID.Eq(followId)).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
follower, err := u.Where(u.ID.Eq(relation.UserId)).Preload(u.RemoteInfo).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
followed, err := u.Where(u.ID.Eq(relation.TargetUserId)).Preload(u.RemoteInfo).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := ActivityFollowOut{
|
||||
Id: id,
|
||||
Type: "Follow",
|
||||
}
|
||||
if follower.RemoteInfo != nil {
|
||||
out.Actor = follower.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Actor = userIdToApUrl(follower.ID)
|
||||
}
|
||||
if followed.RemoteInfo != nil {
|
||||
out.Object = followed.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Object = userIdToApUrl(followed.ID)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
)
|
||||
|
||||
type objectNoteOut struct {
|
||||
type ObjectNoteOut struct {
|
||||
// Context should be set, if needed, by the endpoint handler
|
||||
Context any `json:"@context,omitempty"`
|
||||
|
||||
|
@ -48,7 +48,7 @@ type objectNoteOut struct {
|
|||
func objectNote(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
log := hlog.FromRequest(r)
|
||||
note, err := noteFromStorage(r.Context(), id)
|
||||
note, err := NoteFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -73,12 +73,12 @@ func objectNote(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func noteFromStorage(ctx context.Context, id string) (*objectNoteOut, error) {
|
||||
func NoteFromStorage(ctx context.Context, id string) (*ObjectNoteOut, error) {
|
||||
note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(id)).Preload(dbgen.Note.Creator).First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &objectNoteOut{
|
||||
data := &ObjectNoteOut{
|
||||
Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/note/" + id,
|
||||
Type: "Note",
|
||||
Published: note.CreatedAt,
|
||||
|
|
Loading…
Reference in a new issue