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/storage-new" "git.mstar.dev/mstar/linstrom/storage-new/dbgen" ) type ObjectNoteOut 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"` // FIXME: Uncomment once followers collection implemented 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 []string `json:"tags"` // FIXME: Change this to hashtag type // 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) switch err { case gorm.ErrRecordNotFound: webutils.ProblemDetailsStatusOnly(w, http.StatusNotFound) return case nil: note.Context = activitypub.BaseLdContext data, err := json.Marshal(note) if err != nil { log.Error().Err(err).Any("activity", note).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 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{ Id: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/note/" + id, Type: "Note", Published: note.CreatedAt, AttributedTo: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/user/" + note.CreatorId, Content: note.RawContent, // FIXME: Escape content Url: config.GlobalConfig.General.GetFullPublicUrl() + "/@" + note.Creator.Username + "/" + id, To: []string{ "https://www.w3.org/ns/activitystreams#Public", }, // FIXME: Replace with proper targets, not always public AtomUri: config.GlobalConfig.General.GetFullPublicUrl() + "/api/activitypub/object/" + id, } 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 } return data, nil }