diff --git a/internal/api/client/notification/notification.go b/internal/api/client/notification/notification.go
new file mode 100644
index 0000000..bc06b31
--- /dev/null
+++ b/internal/api/client/notification/notification.go
@@ -0,0 +1,66 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package notification
+
+import (
+ "net/http"
+
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/api"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/message"
+ "github.com/superseriousbusiness/gotosocial/internal/router"
+)
+
+const (
+ // IDKey is for notification UUIDs
+ IDKey = "id"
+ // BasePath is the base path for serving the notification API
+ BasePath = "/api/v1/notifications"
+ // BasePathWithID is just the base path with the ID key in it.
+ // Use this anywhere you need to know the ID of the notification being queried.
+ BasePathWithID = BasePath + "/:" + IDKey
+
+ // MaxIDKey is the url query for setting a max notification ID to return
+ MaxIDKey = "max_id"
+ // Limit key is for specifying maximum number of notifications to return.
+ LimitKey = "limit"
+)
+
+// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with notifications
+type Module struct {
+ config *config.Config
+ processor message.Processor
+ log *logrus.Logger
+}
+
+// New returns a new notification module
+func New(config *config.Config, processor message.Processor, log *logrus.Logger) api.ClientModule {
+ return &Module{
+ config: config,
+ processor: processor,
+ log: log,
+ }
+}
+
+// Route attaches all routes from this module to the given router
+func (m *Module) Route(r router.Router) error {
+ r.AttachHandler(http.MethodGet, BasePath, m.NotificationsGETHandler)
+ return nil
+}
diff --git a/internal/api/client/notification/notificationsget.go b/internal/api/client/notification/notificationsget.go
new file mode 100644
index 0000000..3e49708
--- /dev/null
+++ b/internal/api/client/notification/notificationsget.go
@@ -0,0 +1,72 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package notification
+
+import (
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (m *Module) NotificationsGETHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "NotificationsGETHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+ l.Debugf("entering function")
+
+ authed, err := oauth.Authed(c, true, true, true, true) // we don't really need an app here but we want everything else
+ if err != nil {
+ l.Errorf("error authing status faved by request: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "not authed"})
+ return
+ }
+
+ limit := 20
+ limitString := c.Query(LimitKey)
+ if limitString != "" {
+ i, err := strconv.ParseInt(limitString, 10, 64)
+ if err != nil {
+ l.Debugf("error parsing limit string: %s", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
+ return
+ }
+ limit = int(i)
+ }
+
+ maxID := ""
+ maxIDString := c.Query(MaxIDKey)
+ if maxIDString != "" {
+ maxID = maxIDString
+ }
+
+ notifs, errWithCode := m.processor.NotificationsGet(authed, limit, maxID)
+ if errWithCode != nil {
+ l.Debugf("error processing notifications get: %s", errWithCode.Error())
+ c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
+ return
+ }
+
+ c.JSON(http.StatusOK, notifs)
+}
diff --git a/internal/api/model/notification.go b/internal/api/model/notification.go
index c8d080e..2163251 100644
--- a/internal/api/model/notification.go
+++ b/internal/api/model/notification.go
@@ -41,5 +41,5 @@ type Notification struct {
// OPTIONAL
// Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.
- Status *Status `json:"status"`
+ Status *Status `json:"status,omitempty"`
}
diff --git a/internal/api/model/status.go b/internal/api/model/status.go
index 2456d1a..963ef4f 100644
--- a/internal/api/model/status.go
+++ b/internal/api/model/status.go
@@ -31,7 +31,7 @@ type Status struct {
// Is this status marked as sensitive content?
Sensitive bool `json:"sensitive"`
// Subject or summary line, below which status content is collapsed until expanded.
- SpoilerText string `json:"spoiler_text,omitempty"`
+ SpoilerText string `json:"spoiler_text"`
// Visibility of this status.
Visibility Visibility `json:"visibility"`
// Primary language of this status. (ISO 639 Part 1 two-letter language code)
diff --git a/internal/db/db.go b/internal/db/db.go
index 9ad8115..e71484a 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -284,6 +284,8 @@ type DB interface {
// It will use the given filters and try to return as many statuses up to the limit as possible.
GetHomeTimelineForAccount(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, error)
+ GetNotificationsForAccount(accountID string, limit int, maxID string) ([]*gtsmodel.Notification, error)
+
/*
USEFUL CONVERSION FUNCTIONS
*/
diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go
index 7f65055..9b6c7a1 100644
--- a/internal/db/pg/pg.go
+++ b/internal/db/pg/pg.go
@@ -1138,6 +1138,35 @@ func (ps *postgresService) GetHomeTimelineForAccount(accountID string, maxID str
return statuses, nil
}
+func (ps *postgresService) GetNotificationsForAccount(accountID string, limit int, maxID string) ([]*gtsmodel.Notification, error) {
+ notifications := []*gtsmodel.Notification{}
+
+ q := ps.conn.Model(¬ifications).Where("target_account_id = ?", accountID)
+
+
+ if maxID != "" {
+ n := >smodel.Notification{}
+ if err := ps.conn.Model(n).Where("id = ?", maxID).Select(); err != nil {
+ return nil, err
+ }
+ q = q.Where("created_at < ?", n.CreatedAt)
+ }
+
+ if limit != 0 {
+ q = q.Limit(limit)
+ }
+
+ q = q.Order("created_at DESC")
+
+ if err := q.Select(); err != nil {
+ if err != pg.ErrNoRows {
+ return nil, err
+ }
+
+ }
+ return notifications, nil
+}
+
/*
CONVERSION FUNCTIONS
*/
diff --git a/internal/federation/federating_db.go b/internal/federation/federating_db.go
index 6ae4dc0..dc29c84 100644
--- a/internal/federation/federating_db.go
+++ b/internal/federation/federating_db.go
@@ -496,6 +496,27 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
return fmt.Errorf("database error accepting follow request: %s", err)
}
}
+ case gtsmodel.ActivityStreamsLike:
+ like, ok := asType.(vocab.ActivityStreamsLike)
+ if !ok {
+ return errors.New("could not convert type to like")
+ }
+
+ fave, err := f.typeConverter.ASLikeToFave(like)
+ if err != nil {
+ return fmt.Errorf("could not convert Like to fave: %s", err)
+ }
+
+ if err := f.db.Put(fave); err != nil {
+ return fmt.Errorf("database error inserting fave: %s", err)
+ }
+
+ fromFederatorChan <- gtsmodel.FromFederator{
+ APObjectType: gtsmodel.ActivityStreamsLike,
+ APActivityType: gtsmodel.ActivityStreamsCreate,
+ GTSModel: fave,
+ ReceivingAccount: targetAcct,
+ }
}
return nil
}
diff --git a/internal/gotosocial/actions.go b/internal/gotosocial/actions.go
index 8e6c50a..e39cd09 100644
--- a/internal/gotosocial/actions.go
+++ b/internal/gotosocial/actions.go
@@ -37,6 +37,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/api/client/followrequest"
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
mediaModule "github.com/superseriousbusiness/gotosocial/internal/api/client/media"
+ "github.com/superseriousbusiness/gotosocial/internal/api/client/notification"
"github.com/superseriousbusiness/gotosocial/internal/api/client/status"
"github.com/superseriousbusiness/gotosocial/internal/api/client/timeline"
"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user"
@@ -73,6 +74,7 @@ var models []interface{} = []interface{}{
>smodel.User{},
>smodel.Emoji{},
>smodel.Instance{},
+ >smodel.Notification{},
&oauth.Token{},
&oauth.Client{},
}
@@ -118,6 +120,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
webfingerModule := webfinger.New(c, processor, log)
usersModule := user.New(c, processor, log)
timelineModule := timeline.New(c, processor, log)
+ notificationModule := notification.New(c, processor, log)
mm := mediaModule.New(c, processor, log)
fileServerModule := fileserver.New(c, processor, log)
adminModule := admin.New(c, processor, log)
@@ -141,6 +144,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
webfingerModule,
usersModule,
timelineModule,
+ notificationModule,
}
for _, m := range apis {
diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go
new file mode 100644
index 0000000..35e0ca1
--- /dev/null
+++ b/internal/gtsmodel/notification.go
@@ -0,0 +1,70 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+
+package gtsmodel
+
+import "time"
+
+// Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc.
+type Notification struct {
+ // ID of this notification in the database
+ ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull"`
+ // Type of this notification
+ NotificationType NotificationType `pg:",notnull"`
+ // Creation time of this notification
+ CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
+ // Which account does this notification target (ie., who will receive the notification?)
+ TargetAccountID string `pg:",notnull"`
+ // Which account performed the action that created this notification?
+ OriginAccountID string `pg:",notnull"`
+ // If the notification pertains to a status, what is the database ID of that status?
+ StatusID string
+ // Has this notification been read already?
+ Read bool
+
+ /*
+ NON-DATABASE fields
+ */
+
+ // gts model of the target account, won't be put in the database, it's just for convenience when passing the notification around.
+ GTSTargetAccount *Account `pg:"-"`
+ // gts model of the origin account, won't be put in the database, it's just for convenience when passing the notification around.
+ GTSOriginAccount *Account `pg:"-"`
+ // gts model of the relevant status, won't be put in the database, it's just for convenience when passing the notification around.
+ GTSStatus *Status `pg:"-"`
+}
+
+// NotificationType describes the reason/type of this notification.
+type NotificationType string
+
+const (
+ // NotificationFollow -- someone followed you
+ NotificationFollow NotificationType = "follow"
+ // NotificationFollowRequest -- someone requested to follow you
+ NotificationFollowRequest NotificationType = "follow_request"
+ // NotificationMention -- someone mentioned you in their status
+ NotificationMention NotificationType = "mention"
+ // NotificationReblog -- someone boosted one of your statuses
+ NotificationReblog NotificationType = "reblog"
+ // NotifiationFave -- someone faved/liked one of your statuses
+ NotificationFave NotificationType = "favourite"
+ // NotificationPoll -- a poll you voted in or created has ended
+ NotificationPoll NotificationType = "poll"
+ // NotificationStatus -- someone you enabled notifications for has posted a status.
+ NotificationStatus NotificationType = "status"
+)
diff --git a/internal/message/fromcommonprocess.go b/internal/message/fromcommonprocess.go
index 2403a8b..7822cfb 100644
--- a/internal/message/fromcommonprocess.go
+++ b/internal/message/fromcommonprocess.go
@@ -18,7 +18,11 @@
package message
-import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+import (
+ "fmt"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
func (p *processor) notifyStatus(status *gtsmodel.Status) error {
return nil
@@ -29,5 +33,17 @@ func (p *processor) notifyFollow(follow *gtsmodel.Follow) error {
}
func (p *processor) notifyFave(fave *gtsmodel.StatusFave) error {
- return nil
+
+ notif := >smodel.Notification{
+ NotificationType: gtsmodel.NotificationFave,
+ TargetAccountID: fave.TargetAccountID,
+ OriginAccountID: fave.AccountID,
+ StatusID: fave.StatusID,
+ }
+
+ if err := p.db.Put(notif); err != nil {
+ return fmt.Errorf("notifyFave: error putting fave in database: %s", err)
+ }
+
+ return nil
}
diff --git a/internal/message/fromfederatorprocess.go b/internal/message/fromfederatorprocess.go
index d3ebce4..a54868b 100644
--- a/internal/message/fromfederatorprocess.go
+++ b/internal/message/fromfederatorprocess.go
@@ -74,6 +74,16 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
}
+ case gtsmodel.ActivityStreamsLike:
+ // CREATE A FAVE
+ incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave)
+ if !ok {
+ return errors.New("like was not parseable as *gtsmodel.StatusFave")
+ }
+
+ if err := p.notifyFave(incomingFave); err != nil {
+ return err
+ }
}
case gtsmodel.ActivityStreamsUpdate:
// UPDATE
diff --git a/internal/message/notificationsprocess.go b/internal/message/notificationsprocess.go
new file mode 100644
index 0000000..64726b7
--- /dev/null
+++ b/internal/message/notificationsprocess.go
@@ -0,0 +1,24 @@
+package message
+
+import (
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (p *processor) NotificationsGet(authed *oauth.Auth, limit int, maxID string) ([]*apimodel.Notification, ErrorWithCode) {
+ notifs, err := p.db.GetNotificationsForAccount(authed.Account.ID, limit, maxID)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+
+ mastoNotifs := []*apimodel.Notification{}
+ for _, n := range notifs {
+ mastoNotif, err := p.tc.NotificationToMasto(n)
+ if err != nil {
+ return nil, NewErrorInternalError(err)
+ }
+ mastoNotifs = append(mastoNotifs, mastoNotif)
+ }
+
+ return mastoNotifs, nil
+}
diff --git a/internal/message/processor.go b/internal/message/processor.go
index bcd64d4..49a4f6f 100644
--- a/internal/message/processor.go
+++ b/internal/message/processor.go
@@ -106,6 +106,9 @@ type Processor interface {
// MediaUpdate handles the PUT of a media attachment with the given ID and form
MediaUpdate(authed *oauth.Auth, attachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, ErrorWithCode)
+ // NotificationsGet
+ NotificationsGet(authed *oauth.Auth, limit int, maxID string) ([]*apimodel.Notification, ErrorWithCode)
+
// StatusCreate processes the given form to create a new status, returning the api model representation of that status if it's OK.
StatusCreate(authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error)
// StatusDelete processes the delete of a given status, returning the deleted status if the delete goes through.
diff --git a/internal/typeutils/asinterfaces.go b/internal/typeutils/asinterfaces.go
index c31a37a..eea7fd7 100644
--- a/internal/typeutils/asinterfaces.go
+++ b/internal/typeutils/asinterfaces.go
@@ -102,6 +102,15 @@ type Followable interface {
withObject
}
+// Likeable represents the minimum interface for an activitystreams 'like' activity.
+type Likeable interface {
+ withJSONLDId
+ withTypeName
+
+ withActor
+ withObject
+}
+
type withJSONLDId interface {
GetJSONLDId() vocab.JSONLDIdProperty
}
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index dcc2674..0458292 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -380,6 +380,48 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e
return follow, nil
}
+func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) {
+ idProp := likeable.GetJSONLDId()
+ if idProp == nil || !idProp.IsIRI() {
+ return nil, errors.New("no id property set on like, or was not an iri")
+ }
+ uri := idProp.GetIRI().String()
+
+ origin, err := extractActor(likeable)
+ if err != nil {
+ return nil, errors.New("error extracting actor property from like")
+ }
+ originAccount := >smodel.Account{}
+ if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: origin.String()}}, originAccount); err != nil {
+ return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err)
+ }
+
+ target, err := extractObject(likeable)
+ if err != nil {
+ return nil, errors.New("error extracting object property from like")
+ }
+
+ targetStatus := >smodel.Status{}
+ if err := c.db.GetWhere([]db.Where{{Key: "uri", Value: target.String()}}, targetStatus); err != nil {
+ return nil, fmt.Errorf("error extracting status with uri %s from the database: %s", target.String(), err)
+ }
+
+ targetAccount := >smodel.Account{}
+ if err := c.db.GetByID(targetStatus.AccountID, targetAccount); err != nil {
+ return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err)
+ }
+
+ return >smodel.StatusFave{
+ TargetAccountID: targetAccount.ID,
+ StatusID: targetStatus.ID,
+ AccountID: originAccount.ID,
+ URI: uri,
+ GTSStatus: targetStatus,
+ GTSTargetAccount: targetAccount,
+ GTSFavingAccount: originAccount,
+ }, nil
+}
+
func isPublic(tos []*url.URL) bool {
for _, entry := range tos {
if strings.EqualFold(entry.String(), "https://www.w3.org/ns/activitystreams#Public") {
diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go
index 3ced209..4c1c828 100644
--- a/internal/typeutils/converter.go
+++ b/internal/typeutils/converter.go
@@ -84,6 +84,8 @@ type TypeConverter interface {
// RelationshipToMasto converts a gts relationship into its mastodon equivalent for serving in various places
RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relationship, error)
+ NotificationToMasto(n *gtsmodel.Notification) (*model.Notification, error)
+
/*
FRONTEND (mastodon) MODEL TO INTERNAL (gts) MODEL
*/
@@ -107,6 +109,8 @@ type TypeConverter interface {
ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error)
// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow.
ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error)
+ // ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave.
+ ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error)
/*
INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 7fbe9eb..1861fba 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -138,6 +138,9 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e
fields = append(fields, mField)
}
+ emojis := []model.Emoji{}
+ // TODO: account emojis
+
var acct string
if a.Domain != "" {
// this is a remote user
@@ -165,7 +168,7 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e
FollowingCount: followingCount,
StatusesCount: statusesCount,
LastStatusAt: lastStatusAt,
- Emojis: nil, // TODO: implement this
+ Emojis: emojis, // TODO: implement this
Fields: fields,
}, nil
}
@@ -594,3 +597,60 @@ func (c *converter) RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relati
Note: r.Note,
}, nil
}
+
+func (c *converter) NotificationToMasto(n *gtsmodel.Notification) (*model.Notification, error) {
+
+ if n.GTSTargetAccount == nil {
+ tAccount := >smodel.Account{}
+ if err := c.db.GetByID(n.TargetAccountID, tAccount); err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error getting target account with id %s from the db: %s", n.TargetAccountID, err)
+ }
+ n.GTSTargetAccount = tAccount
+ }
+
+ if n.GTSOriginAccount == nil {
+ ogAccount := >smodel.Account{}
+ if err := c.db.GetByID(n.OriginAccountID, ogAccount); err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error getting origin account with id %s from the db: %s", n.OriginAccountID, err)
+ }
+ n.GTSOriginAccount = ogAccount
+ }
+ mastoAccount, err := c.AccountToMastoPublic(n.GTSOriginAccount)
+ if err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error converting account to masto: %s", err)
+ }
+
+ var mastoStatus *model.Status
+ if n.StatusID != "" {
+ if n.GTSStatus == nil {
+ status := >smodel.Status{}
+ if err := c.db.GetByID(n.StatusID, status); err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error getting status with id %s from the db: %s", n.StatusID, err)
+ }
+ n.GTSStatus = status
+ }
+
+ var replyToAccount *gtsmodel.Account
+ if n.GTSStatus.InReplyToAccountID != "" {
+ r := >smodel.Account{}
+ if err := c.db.GetByID(n.GTSStatus.InReplyToAccountID, r); err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error getting replied to account with id %s from the db: %s", n.GTSStatus.InReplyToAccountID, err)
+ }
+ replyToAccount = r
+ }
+
+ var err error
+ mastoStatus, err = c.StatusToMasto(n.GTSStatus, n.GTSTargetAccount, n.GTSTargetAccount, nil, replyToAccount, nil)
+ if err != nil {
+ return nil, fmt.Errorf("NotificationToMasto: error converting status to masto: %s", err)
+ }
+ }
+
+ return &model.Notification{
+ ID: n.ID,
+ Type: string(n.NotificationType),
+ CreatedAt: n.CreatedAt.Format(time.RFC3339),
+ Account: mastoAccount,
+ Status: mastoStatus,
+ }, nil
+}