- Not tested yet - Undo needs to be extended to also handle undo of follow
This commit is contained in:
parent
7e60188fb5
commit
d84a693b22
6 changed files with 130 additions and 17 deletions
|
@ -33,7 +33,7 @@ func newReaction(db *gorm.DB, opts ...gen.DOOption) reaction {
|
|||
_reaction.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||
_reaction.NoteId = field.NewString(tableName, "note_id")
|
||||
_reaction.ReactorId = field.NewString(tableName, "reactor_id")
|
||||
_reaction.EmoteId = field.NewUint(tableName, "emote_id")
|
||||
_reaction.EmoteId = field.NewField(tableName, "emote_id")
|
||||
_reaction.Note = reactionBelongsToNote{
|
||||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
|
@ -430,7 +430,7 @@ type reaction struct {
|
|||
DeletedAt field.Field
|
||||
NoteId field.String
|
||||
ReactorId field.String
|
||||
EmoteId field.Uint
|
||||
EmoteId field.Field
|
||||
Note reactionBelongsToNote
|
||||
|
||||
Reactor reactionBelongsToReactor
|
||||
|
@ -458,7 +458,7 @@ func (r *reaction) updateTableName(table string) *reaction {
|
|||
r.DeletedAt = field.NewField(table, "deleted_at")
|
||||
r.NoteId = field.NewString(table, "note_id")
|
||||
r.ReactorId = field.NewString(table, "reactor_id")
|
||||
r.EmoteId = field.NewUint(table, "emote_id")
|
||||
r.EmoteId = field.NewField(table, "emote_id")
|
||||
|
||||
r.fillFieldMap()
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ func newUserToUserRelation(db *gorm.DB, opts ...gen.DOOption) userToUserRelation
|
|||
_userToUserRelation.ID = field.NewUint64(tableName, "id")
|
||||
_userToUserRelation.UserId = field.NewString(tableName, "user_id")
|
||||
_userToUserRelation.TargetUserId = field.NewString(tableName, "target_user_id")
|
||||
_userToUserRelation.Relation = field.NewField(tableName, "relation")
|
||||
_userToUserRelation.Relation = field.NewString(tableName, "relation")
|
||||
_userToUserRelation.User = userToUserRelationBelongsToUser{
|
||||
db: db.Session(&gorm.Session{}),
|
||||
|
||||
|
@ -222,7 +222,7 @@ type userToUserRelation struct {
|
|||
ID field.Uint64
|
||||
UserId field.String
|
||||
TargetUserId field.String
|
||||
Relation field.Field
|
||||
Relation field.String
|
||||
User userToUserRelationBelongsToUser
|
||||
|
||||
TargetUser userToUserRelationBelongsToTargetUser
|
||||
|
@ -245,7 +245,7 @@ func (u *userToUserRelation) updateTableName(table string) *userToUserRelation {
|
|||
u.ID = field.NewUint64(table, "id")
|
||||
u.UserId = field.NewString(table, "user_id")
|
||||
u.TargetUserId = field.NewString(table, "target_user_id")
|
||||
u.Relation = field.NewField(table, "relation")
|
||||
u.Relation = field.NewString(table, "relation")
|
||||
|
||||
u.fillFieldMap()
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ const (
|
|||
ActivitystreamsActivityTargetUser
|
||||
ActivitystreamsActivityTargetBoost
|
||||
ActivitystreamsActivityTargetReaction
|
||||
ActivitystreamsActivityTargetFollow
|
||||
)
|
||||
|
||||
func (n *ActivitystreamsActivityTargetType) Value() (driver.Value, error) {
|
||||
|
@ -29,4 +30,5 @@ var AllActivitystreamsActivityTargetTypes = []ActivitystreamsActivityTargetType{
|
|||
ActivitystreamsActivityTargetUser,
|
||||
ActivitystreamsActivityTargetBoost,
|
||||
ActivitystreamsActivityTargetReaction,
|
||||
ActivitystreamsActivityTargetFollow,
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ type RelationType string
|
|||
|
||||
const (
|
||||
RelationFollow RelationType = "follow" // X follows Y
|
||||
RelationFollowRequest RelationType = "follow-request" // X has requested to follow Y but is not yet following Y
|
||||
RelationMute RelationType = "mute" // X has Y muted (X doesn't see Y, but Y still X)
|
||||
RelationNoBoosts RelationType = "no-boosts" // X has Ys boosts muted
|
||||
RelationBlock RelationType = "block" // X has Y blocked (X doesn't see Y and Y doesn't see X)
|
||||
|
|
|
@ -9,5 +9,5 @@ type UserToUserRelation struct {
|
|||
UserId string
|
||||
TargetUser User // The user Y described in [RelationType]
|
||||
TargetUserId string
|
||||
Relation RelationType `gorm:"type:relation_type"`
|
||||
Relation string `gorm:"type:relation_type"`
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ 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"
|
||||
)
|
||||
|
@ -86,6 +87,8 @@ func userInbox(w http.ResponseWriter, r *http.Request) {
|
|||
handleLike(w, r, data)
|
||||
case "Undo":
|
||||
handleUndo(w, r, data)
|
||||
case "Follow":
|
||||
handleFollow(w, r, data)
|
||||
default:
|
||||
webutils.ProblemDetailsStatusOnly(w, 500)
|
||||
}
|
||||
|
@ -222,27 +225,28 @@ func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|||
)
|
||||
return
|
||||
}
|
||||
// TODO: I *think* undo is only actively used for likes, but could also be used for
|
||||
// undoing create's and thus removing notes
|
||||
var likeActivityId string
|
||||
// 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:
|
||||
likeActivityId = target
|
||||
targetObjectId = target
|
||||
case map[string]any:
|
||||
objType, ok := target["type"].(string)
|
||||
if !ok || objType != "Like" {
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Undone object is not of type like. If you receive this error for a valid undo for a different activity type,please contact the Linstrom developers with the activity type`),
|
||||
other.IntoPointer(`Target object does not have a type`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
likeActivityId, ok = target["id"].(string)
|
||||
targetObjectType = objType
|
||||
targetObjectId, ok = target["id"].(string)
|
||||
if !ok {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
|
@ -267,7 +271,7 @@ func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|||
}
|
||||
// TODO: For above todo, get target resource via likeActivityId and remove approprietly
|
||||
// Don't just assume it was a like being undone
|
||||
act, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(likeActivityId), dbgen.Activity.Type.Eq("like")).
|
||||
act, err := dbgen.Activity.Where(dbgen.Activity.Id.Eq(targetObjectId), dbgen.Activity.Type.Eq("like")).
|
||||
First()
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
|
@ -276,7 +280,7 @@ func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|||
default:
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("activity-id", likeActivityId).
|
||||
Str("activity-id", targetObjectId).
|
||||
Msg("Error while looking for find activity")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -290,7 +294,7 @@ func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|||
default:
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("activity-id", likeActivityId).
|
||||
Str("activity-id", targetObjectId).
|
||||
Msg("Error while looking for find activity")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -319,3 +323,109 @@ func handleUndo(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
|||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func handleFollow(w http.ResponseWriter, r *http.Request, object map[string]any) {
|
||||
log := hlog.FromRequest(r)
|
||||
log.Debug().Msg("Received follow request")
|
||||
_ = object["id"].(string)
|
||||
actorApId, 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
|
||||
}
|
||||
targetUrl, ok := object["object"].(string)
|
||||
if !ok {
|
||||
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`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
followedMatch := objectIdRegex.FindStringSubmatch(targetUrl)
|
||||
if len(followedMatch) != 2 {
|
||||
webutils.ProblemDetails(
|
||||
w,
|
||||
http.StatusBadRequest,
|
||||
"/errors/bad-request-data",
|
||||
"Bad activity data",
|
||||
other.IntoPointer(`Object must be a link to a Linstrom AP user`),
|
||||
nil,
|
||||
)
|
||||
return
|
||||
}
|
||||
followedId := followedMatch[1]
|
||||
followed, err := dbgen.User.Where(dbgen.User.ID.Eq(followedId)).First()
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
return
|
||||
case nil:
|
||||
default:
|
||||
log.Error().Err(err).Str("target-id", followedId).Msg("Failed to get account from db")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
follower, err := activitypub.ImportRemoteAccountByAPUrl(actorApId)
|
||||
u2u := dbgen.UserToUserRelation
|
||||
followRelations, err := u2u.Where(
|
||||
u2u.UserId.Eq(follower.ID),
|
||||
u2u.TargetUserId.Eq(followed.ID),
|
||||
u2u.Or(
|
||||
u2u.Relation.Eq(models.RelationFollow),
|
||||
u2u.Relation.Eq(models.RelationFollowRequest),
|
||||
),
|
||||
).Count()
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("follower", follower.ID).
|
||||
Str("followed", followedId).
|
||||
Msg("Failed to count follow relations")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if followRelations > 0 {
|
||||
return
|
||||
}
|
||||
tx := dbgen.Q.Begin()
|
||||
req := models.UserToUserRelation{
|
||||
Relation: string(models.RelationFollowRequest),
|
||||
TargetUserId: followed.ID,
|
||||
UserId: follower.ID,
|
||||
}
|
||||
err = tx.UserToUserRelation.Create(&req)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Error().Err(err).Any("follow-request", req).Msg("Failed to store follow request")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
activity := models.Activity{
|
||||
Id: shared.NewId(),
|
||||
Type: string(models.ActivityFollow),
|
||||
ObjectId: fmt.Sprint(req.ID),
|
||||
ObjectType: uint32(models.ActivitystreamsActivityTargetFollow),
|
||||
}
|
||||
err = tx.Activity.Create(&activity)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
log.Error().Err(err).Any("activity", activity).Msg("Failed to store follow activity")
|
||||
webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to commit follow activity transaction")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue