Move translators db->ap to separate module
This commit is contained in:
parent
cfe5047433
commit
d86ad370df
16 changed files with 456 additions and 423 deletions
48
activitypub/translators/accept.go
Normal file
48
activitypub/translators/accept.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package translators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"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 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
|
||||
}
|
||||
var outId string
|
||||
if strings.HasPrefix(id, "http") {
|
||||
outId = id
|
||||
} else {
|
||||
outId = fmt.Sprintf("%s/api/activitypub/activity/accept/%s", config.GlobalConfig.General.GetFullPublicUrl(), id)
|
||||
}
|
||||
return &ActivityAcceptOut{
|
||||
Id: outId,
|
||||
Actor: follow.Object.(string),
|
||||
Type: "Accept",
|
||||
Object: follow,
|
||||
}, nil
|
||||
}
|
23
activitypub/translators/collection.go
Normal file
23
activitypub/translators/collection.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package translators
|
||||
|
||||
// Used for both unordered and ordered
|
||||
type CollectionOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Items []any `json:"items,omitempty"`
|
||||
Id string `json:"id"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
First string `json:"first"`
|
||||
}
|
||||
|
||||
// Used for both unordered and ordered
|
||||
type CollectionPageOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
PartOf string `json:"partOf"`
|
||||
Next string `json:"next,omitempty"`
|
||||
Previous string `json:"prev,omitempty"`
|
||||
Items []any `json:"items"`
|
||||
}
|
58
activitypub/translators/create.go
Normal file
58
activitypub/translators/create.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package translators
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type ActivityCreate struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Actor string `json:"actor"`
|
||||
Object any `json:"object"`
|
||||
}
|
||||
|
||||
// Find a create activity from the db and format it for activitypub reads
|
||||
// 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) (*ActivityCreate, error) {
|
||||
// log := log.Ctx(ctx)
|
||||
a := dbgen.Activity
|
||||
activity, err := a.Where(a.Type.Eq(string(models.ActivityCreate))).
|
||||
Where(a.Id.Eq(id)).
|
||||
First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch models.ActivitystreamsActivityTargetType(activity.ObjectType) {
|
||||
case models.ActivitystreamsActivityTargetNote:
|
||||
note, err := NoteFromStorage(ctx, activity.ObjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := ActivityCreate{
|
||||
Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/create/" + id,
|
||||
Type: "Create",
|
||||
Actor: note.AttributedTo,
|
||||
Object: note,
|
||||
}
|
||||
return &out, nil
|
||||
case models.ActivitystreamsActivityTargetBoost:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetReaction:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetActivity:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetUser:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetUnknown:
|
||||
panic("Not implemented")
|
||||
default:
|
||||
panic("Not implemented")
|
||||
}
|
||||
}
|
61
activitypub/translators/follow.go
Normal file
61
activitypub/translators/follow.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package translators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"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 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: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/activity/follow/" + id,
|
||||
Type: "Follow",
|
||||
}
|
||||
if follower.RemoteInfo != nil {
|
||||
out.Actor = follower.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Actor = activitypub.UserIdToApUrl(follower.ID)
|
||||
}
|
||||
if followed.RemoteInfo != nil {
|
||||
out.Object = followed.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Object = activitypub.UserIdToApUrl(followed.ID)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
113
activitypub/translators/note.go
Normal file
113
activitypub/translators/note.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package translators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Type string `json:"type"`
|
||||
Href string `json:"href"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ObjectNote struct {
|
||||
// Context should be set, if needed, by the endpoint handler
|
||||
Context any `json:"@context,omitempty"`
|
||||
|
||||
// Attributes below set from storage
|
||||
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Summary *string `json:"summary"`
|
||||
InReplyTo *string `json:"inReplyTo"`
|
||||
Published time.Time `json:"published"`
|
||||
Url string `json:"url"`
|
||||
AttributedTo string `json:"attributedTo"`
|
||||
To []string `json:"to"`
|
||||
CC []string `json:"cc"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
AtomUri string `json:"atomUri"`
|
||||
InReplyToAtomUri *string `json:"inReplyToAtomUri"`
|
||||
// Conversation string `json:"conversation"` // FIXME: Uncomment once understood what this field wants
|
||||
Content string `json:"content"`
|
||||
// ContentMap map[string]string `json:"content_map"` // TODO: Uncomment once/if support for multiple languages available
|
||||
// Attachments []string `json:"attachments"` // FIXME: Change this to document type
|
||||
Tags []Tag `json:"tag"`
|
||||
// Replies any `json:"replies"` // FIXME: Change this to collection type embedding first page
|
||||
// Likes any `json:"likes"` // FIXME: Change this to collection
|
||||
// Shares any `json:"shares"` // FIXME: Change this to collection, is boosts
|
||||
}
|
||||
|
||||
func NoteFromStorage(ctx context.Context, id string) (*ObjectNote, error) {
|
||||
note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(id)).
|
||||
Preload(dbgen.Note.Creator).
|
||||
Preload(dbgen.Note.PingRelations).
|
||||
Preload(dbgen.Note.Tags).
|
||||
First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Check access level, requires acting user to be included in function signature
|
||||
publicUrlPrefix := config.GlobalConfig.General.GetFullPublicUrl()
|
||||
data := &ObjectNote{
|
||||
Id: publicUrlPrefix + "/api/activitypub/note/" + id,
|
||||
Type: "Note",
|
||||
Published: note.CreatedAt,
|
||||
AttributedTo: publicUrlPrefix + "/api/activitypub/user/" + note.CreatorId,
|
||||
Content: note.RawContent, // FIXME: Escape content
|
||||
Url: publicUrlPrefix + "/@" + note.Creator.Username + "/" + id,
|
||||
AtomUri: publicUrlPrefix + "/api/activitypub/object/" + id,
|
||||
Tags: []Tag{},
|
||||
}
|
||||
switch note.AccessLevel {
|
||||
case models.NOTE_TARGET_PUBLIC:
|
||||
data.To = []string{
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
}
|
||||
data.CC = []string{
|
||||
fmt.Sprintf("%s/api/activitypub/user/%s/followers", publicUrlPrefix, note.CreatorId),
|
||||
}
|
||||
case models.NOTE_TARGET_HOME:
|
||||
return nil, fmt.Errorf("home access level not implemented")
|
||||
case models.NOTE_TARGET_FOLLOWERS:
|
||||
return nil, fmt.Errorf("followers access level not implemented")
|
||||
case models.NOTE_TARGET_DM:
|
||||
return nil, fmt.Errorf("dm access level not implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown access level %v", note.AccessLevel)
|
||||
}
|
||||
if note.RepliesTo.Valid {
|
||||
data.InReplyTo = ¬e.RepliesTo.String
|
||||
data.InReplyToAtomUri = ¬e.RepliesTo.String
|
||||
}
|
||||
if note.ContentWarning.Valid {
|
||||
data.Summary = ¬e.ContentWarning.String
|
||||
data.Sensitive = true
|
||||
}
|
||||
for _, ping := range note.PingRelations {
|
||||
target, err := dbgen.User.GetById(ping.PingTargetId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Tags = append(data.Tags, Tag{
|
||||
Type: "Mention",
|
||||
Href: webshared.UserPublicUrl(target.ID),
|
||||
Name: target.Username,
|
||||
})
|
||||
}
|
||||
for _, tag := range note.Tags {
|
||||
data.Tags = append(data.Tags, Tag{
|
||||
Type: "Hashtag",
|
||||
Href: tag.TagUrl,
|
||||
Name: tag.Tag,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
}
|
126
activitypub/translators/user.go
Normal file
126
activitypub/translators/user.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
package translators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.mstar.dev/mstar/goutils/sliceutils"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"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"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
type UserKey struct {
|
||||
Id string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Pem string `json:"publicKeyPem"`
|
||||
}
|
||||
type Media struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
type User struct {
|
||||
Context []any `json:"@context"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
Outboux string `json:"outbox"`
|
||||
PublicKey UserKey `json:"publicKey"`
|
||||
Published time.Time `json:"published"`
|
||||
DisplayName string `json:"name"`
|
||||
Description *string `json:"summary,omitempty"`
|
||||
PublicUrl string `json:"url"`
|
||||
Icon *Media `json:"icon,omitempty"`
|
||||
Banner *Media `json:"image,omitempty"`
|
||||
Discoverable bool `json:"discoverable"`
|
||||
Location *string `json:"vcard:Address,omitempty"`
|
||||
Birthday *string `json:"vcard:bday,omitempty"`
|
||||
SpeakAsCat bool `json:"speakAsCat"`
|
||||
IsCat bool `json:"isCat"`
|
||||
RestrictedFollow bool `json:"manuallyApprovesFollowers"`
|
||||
Following string `json:"following"`
|
||||
Followers string `json:"followers"`
|
||||
}
|
||||
|
||||
func UserFromStorage(ctx context.Context, id string) (*User, error) {
|
||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(id)).
|
||||
Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
|
||||
Preload(dbgen.User.BeingTypes).
|
||||
First()
|
||||
|
||||
err = storage.EnsureLocalUserIdHasLinks(id)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to create links for local user")
|
||||
}
|
||||
apUrl := activitypub.UserIdToApUrl(user.ID)
|
||||
var keyBytes string
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
keyBytes = shared.KeyBytesToPem(user.PublicKeyEd, true)
|
||||
} else {
|
||||
keyBytes = shared.KeyBytesToPem(user.PublicKeyRsa, false)
|
||||
}
|
||||
data := User{
|
||||
Id: apUrl,
|
||||
Type: "Person",
|
||||
PreferredUsername: user.Username,
|
||||
Inbox: apUrl + "/inbox",
|
||||
Outboux: apUrl + "/outbox",
|
||||
PublicKey: UserKey{
|
||||
Id: apUrl + "#main-key",
|
||||
Owner: apUrl,
|
||||
Pem: keyBytes,
|
||||
},
|
||||
Published: user.CreatedAt,
|
||||
DisplayName: user.DisplayName,
|
||||
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
|
||||
Discoverable: user.Indexable,
|
||||
RestrictedFollow: user.RestrictedFollow,
|
||||
Following: apUrl + "/following",
|
||||
Followers: apUrl + "/followers",
|
||||
}
|
||||
if user.Description != "" {
|
||||
data.Description = &user.Description
|
||||
}
|
||||
if user.Icon != nil {
|
||||
log.Debug().Msg("icon found")
|
||||
data.Icon = &Media{
|
||||
Type: "Image",
|
||||
Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
|
||||
user.Icon.Location,
|
||||
),
|
||||
MediaType: user.Icon.Type,
|
||||
}
|
||||
}
|
||||
if user.Banner != nil {
|
||||
log.Debug().Msg("icon banner")
|
||||
data.Banner = &Media{
|
||||
Type: "Image",
|
||||
Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
|
||||
user.Banner.Location,
|
||||
),
|
||||
MediaType: user.Banner.Type,
|
||||
}
|
||||
}
|
||||
if sliceutils.ContainsFunc(user.BeingTypes, func(t models.UserToBeing) bool {
|
||||
return t.Being == string(models.BEING_CAT)
|
||||
}) {
|
||||
data.IsCat = true
|
||||
// data.SpeakAsCat = true // TODO: Move to check of separate field in db model
|
||||
}
|
||||
if user.Location.Valid {
|
||||
data.Location = &user.Location.String
|
||||
}
|
||||
if user.Birthday.Valid {
|
||||
data.Birthday = &user.Birthday.String
|
||||
// data.Birthday = other.IntoPointer(user.Birthday.Time.Format("2006-Jan-02")) //YYYY-Month-DD
|
||||
}
|
||||
return &data, nil
|
||||
}
|
|
@ -5,6 +5,7 @@ var AllTypes = []any{
|
|||
&Activity{},
|
||||
&Collection{},
|
||||
&Emote{},
|
||||
&FailedOutboundRequest{},
|
||||
&Feed{},
|
||||
&MediaMetadata{},
|
||||
&Note{},
|
||||
|
|
|
@ -13,11 +13,11 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -148,7 +148,7 @@ func postAs(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
log.Debug().Strs("links", links).Send()
|
||||
act, err := webap.CreateFromStorage(r.Context(), activity.Id)
|
||||
act, err := translators.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)
|
||||
|
|
|
@ -19,11 +19,11 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -377,7 +377,7 @@ func requestFollow(w http.ResponseWriter, r *http.Request) {
|
|||
log.Error().Err(err).Msg("Failed to get target user with remote links")
|
||||
return
|
||||
}
|
||||
activity, err := webap.FollowFromStorage(context.Background(), activity.Id)
|
||||
activity, err := translators.FollowFromStorage(context.Background(), activity.Id)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to retrieve and format follow request")
|
||||
return
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
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/config"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"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)
|
||||
activity, err := translators.CreateFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -52,32 +40,3 @@ func activityAccept(w http.ResponseWriter, r *http.Request) {
|
|||
_ = 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
|
||||
}
|
||||
var outId string
|
||||
if strings.HasPrefix(id, "http") {
|
||||
outId = id
|
||||
} else {
|
||||
outId = fmt.Sprintf("%s/api/activitypub/activity/accept/%s", config.GlobalConfig.General.GetFullPublicUrl(), id)
|
||||
}
|
||||
return &ActivityAcceptOut{
|
||||
Id: outId,
|
||||
Actor: follow.Object.(string),
|
||||
Type: "Accept",
|
||||
Object: follow,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -11,24 +10,14 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
)
|
||||
|
||||
type ActivityCreate struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Actor string `json:"actor"`
|
||||
Object any `json:"object"`
|
||||
}
|
||||
|
||||
func activityCreate(w http.ResponseWriter, r *http.Request) {
|
||||
log := hlog.FromRequest(r)
|
||||
id := r.PathValue("id")
|
||||
activity, err := CreateFromStorage(r.Context(), id)
|
||||
activity, err := translators.CreateFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -51,44 +40,3 @@ func activityCreate(w http.ResponseWriter, r *http.Request) {
|
|||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// Find a create activity from the db and format it for activitypub reads
|
||||
// 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) (*ActivityCreate, error) {
|
||||
// log := log.Ctx(ctx)
|
||||
a := dbgen.Activity
|
||||
activity, err := a.Where(a.Type.Eq(string(models.ActivityCreate))).
|
||||
Where(a.Id.Eq(id)).
|
||||
First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch models.ActivitystreamsActivityTargetType(activity.ObjectType) {
|
||||
case models.ActivitystreamsActivityTargetNote:
|
||||
note, err := NoteFromStorage(ctx, activity.ObjectId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := ActivityCreate{
|
||||
Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/create/" + id,
|
||||
Type: "Create",
|
||||
Actor: note.AttributedTo,
|
||||
Object: note,
|
||||
}
|
||||
return &out, nil
|
||||
case models.ActivitystreamsActivityTargetBoost:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetReaction:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetActivity:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetUser:
|
||||
panic("Not implemented")
|
||||
case models.ActivitystreamsActivityTargetUnknown:
|
||||
panic("Not implemented")
|
||||
default:
|
||||
panic("Not implemented")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
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/config"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"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)
|
||||
activity, err := translators.FollowFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -52,45 +40,3 @@ func activityFollow(w http.ResponseWriter, r *http.Request) {
|
|||
_ = 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: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/activity/follow/" + id,
|
||||
Type: "Follow",
|
||||
}
|
||||
if follower.RemoteInfo != nil {
|
||||
out.Actor = follower.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Actor = activitypub.UserIdToApUrl(follower.ID)
|
||||
}
|
||||
if followed.RemoteInfo != nil {
|
||||
out.Object = followed.RemoteInfo.ApLink
|
||||
} else {
|
||||
out.Object = activitypub.UserIdToApUrl(followed.ID)
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
|
|
@ -2,28 +2,6 @@ package activitypub
|
|||
|
||||
import "net/http"
|
||||
|
||||
// Used for both unordered and ordered
|
||||
type collectionOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Items []any `json:"items,omitempty"`
|
||||
Id string `json:"id"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
First string `json:"first"`
|
||||
}
|
||||
|
||||
// Used for both unordered and ordered
|
||||
type collectionPageOut struct {
|
||||
Context any `json:"@context,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Id string `json:"id"`
|
||||
PartOf string `json:"partOf"`
|
||||
Next string `json:"next,omitempty"`
|
||||
Previous string `json:"prev,omitempty"`
|
||||
Items []any `json:"items"`
|
||||
}
|
||||
|
||||
// Unordered collections handler
|
||||
func collections(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/shared"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
|
@ -682,7 +683,7 @@ func handleReject(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)
|
||||
activity := ActivityCreate{}
|
||||
activity := translators.ActivityCreate{}
|
||||
err := mapstructure.Decode(object, &activity)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -735,7 +736,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request, object map[string]any)
|
|||
} else {
|
||||
obj["published"] = tmpTime
|
||||
}
|
||||
objectNote := ObjectNote{}
|
||||
objectNote := translators.ObjectNote{}
|
||||
err = mapstructure.Decode(obj, &objectNote)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
|
@ -850,7 +851,7 @@ func AcceptFollow(
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
webAccept, err := AcceptFromStorage(ctx, acceptActivity.Id)
|
||||
webAccept, err := translators.AcceptFromStorage(ctx, acceptActivity.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,62 +1,23 @@
|
|||
package activitypub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
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/config"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Type string `json:"type"`
|
||||
Href string `json:"href"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ObjectNote struct {
|
||||
// Context should be set, if needed, by the endpoint handler
|
||||
Context any `json:"@context,omitempty"`
|
||||
|
||||
// Attributes below set from storage
|
||||
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Summary *string `json:"summary"`
|
||||
InReplyTo *string `json:"inReplyTo"`
|
||||
Published time.Time `json:"published"`
|
||||
Url string `json:"url"`
|
||||
AttributedTo string `json:"attributedTo"`
|
||||
To []string `json:"to"`
|
||||
CC []string `json:"cc"`
|
||||
Sensitive bool `json:"sensitive"`
|
||||
AtomUri string `json:"atomUri"`
|
||||
InReplyToAtomUri *string `json:"inReplyToAtomUri"`
|
||||
// Conversation string `json:"conversation"` // FIXME: Uncomment once understood what this field wants
|
||||
Content string `json:"content"`
|
||||
// ContentMap map[string]string `json:"content_map"` // TODO: Uncomment once/if support for multiple languages available
|
||||
// Attachments []string `json:"attachments"` // FIXME: Change this to document type
|
||||
Tags []Tag `json:"tag"`
|
||||
// Replies any `json:"replies"` // FIXME: Change this to collection type embedding first page
|
||||
// Likes any `json:"likes"` // FIXME: Change this to collection
|
||||
// Shares any `json:"shares"` // FIXME: Change this to collection, is boosts
|
||||
}
|
||||
|
||||
func objectNote(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
log := hlog.FromRequest(r)
|
||||
note, err := NoteFromStorage(r.Context(), id)
|
||||
note, err := translators.NoteFromStorage(r.Context(), id)
|
||||
switch err {
|
||||
case gorm.ErrRecordNotFound:
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound)
|
||||
|
@ -80,70 +41,3 @@ func objectNote(w http.ResponseWriter, r *http.Request) {
|
|||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func NoteFromStorage(ctx context.Context, id string) (*ObjectNote, error) {
|
||||
note, err := dbgen.Note.Where(dbgen.Note.ID.Eq(id)).
|
||||
Preload(dbgen.Note.Creator).
|
||||
Preload(dbgen.Note.PingRelations).
|
||||
Preload(dbgen.Note.Tags).
|
||||
First()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Check access level, requires acting user to be included in function signature
|
||||
publicUrlPrefix := config.GlobalConfig.General.GetFullPublicUrl()
|
||||
data := &ObjectNote{
|
||||
Id: publicUrlPrefix + "/api/activitypub/note/" + id,
|
||||
Type: "Note",
|
||||
Published: note.CreatedAt,
|
||||
AttributedTo: publicUrlPrefix + "/api/activitypub/user/" + note.CreatorId,
|
||||
Content: note.RawContent, // FIXME: Escape content
|
||||
Url: publicUrlPrefix + "/@" + note.Creator.Username + "/" + id,
|
||||
AtomUri: publicUrlPrefix + "/api/activitypub/object/" + id,
|
||||
Tags: []Tag{},
|
||||
}
|
||||
switch note.AccessLevel {
|
||||
case models.NOTE_TARGET_PUBLIC:
|
||||
data.To = []string{
|
||||
"https://www.w3.org/ns/activitystreams#Public",
|
||||
}
|
||||
data.CC = []string{
|
||||
fmt.Sprintf("%s/api/activitypub/user/%s/followers", publicUrlPrefix, note.CreatorId),
|
||||
}
|
||||
case models.NOTE_TARGET_HOME:
|
||||
return nil, fmt.Errorf("home access level not implemented")
|
||||
case models.NOTE_TARGET_FOLLOWERS:
|
||||
return nil, fmt.Errorf("followers access level not implemented")
|
||||
case models.NOTE_TARGET_DM:
|
||||
return nil, fmt.Errorf("dm access level not implemented")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown access level %v", note.AccessLevel)
|
||||
}
|
||||
if note.RepliesTo.Valid {
|
||||
data.InReplyTo = ¬e.RepliesTo.String
|
||||
data.InReplyToAtomUri = ¬e.RepliesTo.String
|
||||
}
|
||||
if note.ContentWarning.Valid {
|
||||
data.Summary = ¬e.ContentWarning.String
|
||||
data.Sensitive = true
|
||||
}
|
||||
for _, ping := range note.PingRelations {
|
||||
target, err := dbgen.User.GetById(ping.PingTargetId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Tags = append(data.Tags, Tag{
|
||||
Type: "Mention",
|
||||
Href: webshared.UserPublicUrl(target.ID),
|
||||
Name: target.Username,
|
||||
})
|
||||
}
|
||||
for _, tag := range note.Tags {
|
||||
data.Tags = append(data.Tags, Tag{
|
||||
Type: "Hashtag",
|
||||
Href: tag.TagUrl,
|
||||
Name: tag.Tag,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
webutils "git.mstar.dev/mstar/goutils/http"
|
||||
"git.mstar.dev/mstar/goutils/other"
|
||||
|
@ -14,143 +13,21 @@ import (
|
|||
"gorm.io/gorm"
|
||||
|
||||
"git.mstar.dev/mstar/linstrom/activitypub"
|
||||
"git.mstar.dev/mstar/linstrom/config"
|
||||
"git.mstar.dev/mstar/linstrom/shared"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new"
|
||||
"git.mstar.dev/mstar/linstrom/activitypub/translators"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/dbgen"
|
||||
"git.mstar.dev/mstar/linstrom/storage-new/models"
|
||||
webshared "git.mstar.dev/mstar/linstrom/web/shared"
|
||||
)
|
||||
|
||||
func users(w http.ResponseWriter, r *http.Request) {
|
||||
type OutboundKey struct {
|
||||
Id string `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Pem string `json:"publicKeyPem"`
|
||||
}
|
||||
type OutboundMedia struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
MediaType string `json:"mediaType"`
|
||||
}
|
||||
type Outbound struct {
|
||||
Context []any `json:"@context"`
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
PreferredUsername string `json:"preferredUsername"`
|
||||
Inbox string `json:"inbox"`
|
||||
Outboux string `json:"outbox"`
|
||||
PublicKey OutboundKey `json:"publicKey"`
|
||||
Published time.Time `json:"published"`
|
||||
DisplayName string `json:"name"`
|
||||
Description *string `json:"summary,omitempty"`
|
||||
PublicUrl string `json:"url"`
|
||||
Icon *OutboundMedia `json:"icon,omitempty"`
|
||||
Banner *OutboundMedia `json:"image,omitempty"`
|
||||
Discoverable bool `json:"discoverable"`
|
||||
Location *string `json:"vcard:Address,omitempty"`
|
||||
Birthday *string `json:"vcard:bday,omitempty"`
|
||||
SpeakAsCat bool `json:"speakAsCat"`
|
||||
IsCat bool `json:"isCat"`
|
||||
RestrictedFollow bool `json:"manuallyApprovesFollowers"`
|
||||
Following string `json:"following"`
|
||||
Followers string `json:"followers"`
|
||||
}
|
||||
log := hlog.FromRequest(r)
|
||||
userId := r.PathValue("id")
|
||||
user, err := dbgen.User.Where(dbgen.User.ID.Eq(userId)).
|
||||
Preload(dbgen.User.Icon).Preload(dbgen.User.Banner).
|
||||
Preload(dbgen.User.BeingTypes).
|
||||
First()
|
||||
|
||||
user, err := translators.UserFromStorage(r.Context(), userId)
|
||||
if err != nil {
|
||||
_ = webutils.ProblemDetails(
|
||||
w,
|
||||
500,
|
||||
"/errors/db-failure",
|
||||
"internal database failure",
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if storage.HandleReconnectError(err) {
|
||||
log.Warn().Msg("Connection to db lost. Reconnect attempt started")
|
||||
} else {
|
||||
log.Error().Err(err).Msg("Failed to get total user count from db")
|
||||
}
|
||||
log.Error().Err(err).Msg("Failed to get user from db")
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// FIXME: Remove this later
|
||||
// (or rather move to dedicated module in storage for old migration stuff),
|
||||
// temporary fix for old data. User creation locations are fixed already
|
||||
err = storage.EnsureLocalUserIdHasLinks(userId)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to create links for local user")
|
||||
}
|
||||
|
||||
apUrl := activitypub.UserIdToApUrl(user.ID)
|
||||
var keyBytes string
|
||||
if config.GlobalConfig.Experimental.UseEd25519Keys {
|
||||
keyBytes = shared.KeyBytesToPem(user.PublicKeyEd, true)
|
||||
} else {
|
||||
keyBytes = shared.KeyBytesToPem(user.PublicKeyRsa, false)
|
||||
}
|
||||
data := Outbound{
|
||||
Context: activitypub.BaseLdContext,
|
||||
Id: apUrl,
|
||||
Type: "Person",
|
||||
PreferredUsername: user.Username,
|
||||
Inbox: apUrl + "/inbox",
|
||||
Outboux: apUrl + "/outbox",
|
||||
PublicKey: OutboundKey{
|
||||
Id: apUrl + "#main-key",
|
||||
Owner: apUrl,
|
||||
Pem: keyBytes,
|
||||
},
|
||||
Published: user.CreatedAt,
|
||||
DisplayName: user.DisplayName,
|
||||
PublicUrl: config.GlobalConfig.General.GetFullPublicUrl() + "/user/" + user.Username,
|
||||
Discoverable: user.Indexable,
|
||||
RestrictedFollow: user.RestrictedFollow,
|
||||
Following: apUrl + "/following",
|
||||
Followers: apUrl + "/followers",
|
||||
}
|
||||
if user.Description != "" {
|
||||
data.Description = &user.Description
|
||||
}
|
||||
if user.Icon != nil {
|
||||
log.Debug().Msg("icon found")
|
||||
data.Icon = &OutboundMedia{
|
||||
Type: "Image",
|
||||
Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
|
||||
user.Icon.Location,
|
||||
),
|
||||
MediaType: user.Icon.Type,
|
||||
}
|
||||
}
|
||||
if user.Banner != nil {
|
||||
log.Debug().Msg("icon banner")
|
||||
data.Banner = &OutboundMedia{
|
||||
Type: "Image",
|
||||
Url: config.GlobalConfig.General.GetFullPublicUrl() + webshared.EnsurePublicUrl(
|
||||
user.Banner.Location,
|
||||
),
|
||||
MediaType: user.Banner.Type,
|
||||
}
|
||||
}
|
||||
if sliceutils.ContainsFunc(user.BeingTypes, func(t models.UserToBeing) bool {
|
||||
return t.Being == string(models.BEING_CAT)
|
||||
}) {
|
||||
data.IsCat = true
|
||||
// data.SpeakAsCat = true // TODO: Move to check of separate field in db model
|
||||
}
|
||||
if user.Location.Valid {
|
||||
data.Location = &user.Location.String
|
||||
}
|
||||
if user.Birthday.Valid {
|
||||
data.Birthday = &user.Birthday.String
|
||||
// data.Birthday = other.IntoPointer(user.Birthday.Time.Format("2006-Jan-02")) //YYYY-Month-DD
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(data)
|
||||
encoded, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal response")
|
||||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
|
@ -183,7 +60,7 @@ func userFollowing(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if pageNrStr == "" {
|
||||
col := collectionOut{
|
||||
col := translators.CollectionOut{
|
||||
Context: "https://www.w3.org/ns/activitystreams",
|
||||
Type: "OrderedCollection",
|
||||
Id: apUrl + "/following",
|
||||
|
@ -222,7 +99,7 @@ func userFollowing(w http.ResponseWriter, r *http.Request) {
|
|||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
page := collectionPageOut{
|
||||
page := translators.CollectionPageOut{
|
||||
Context: "https://www.w3.org/ns/activitystreams",
|
||||
Type: "OrderedCollectionPage",
|
||||
Id: fmt.Sprintf("%s/following?page=%d", apUrl, pageNr),
|
||||
|
@ -270,7 +147,7 @@ func userFollowers(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if pageNrStr == "" {
|
||||
col := collectionOut{
|
||||
col := translators.CollectionOut{
|
||||
Context: activitypub.BaseLdContext,
|
||||
Type: "OrderedCollection",
|
||||
Id: apUrl + "/followers",
|
||||
|
@ -310,7 +187,7 @@ func userFollowers(w http.ResponseWriter, r *http.Request) {
|
|||
_ = webutils.ProblemDetailsStatusOnly(w, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
page := collectionPageOut{
|
||||
page := translators.CollectionPageOut{
|
||||
Context: activitypub.BaseLdContext,
|
||||
Type: "OrderedCollectionPage",
|
||||
Id: fmt.Sprintf("%s/followers?page=%d", apUrl, pageNr),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue