diff --git a/PROGRESS.md b/PROGRESS.md
index 013ad080a..18bcedfa3 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -74,9 +74,9 @@
* [x] /api/v1/statuses/:id DELETE (Delete a status)
* [ ] /api/v1/statuses/:id/context GET (View statuses above and below status ID)
* [ ] /api/v1/statuses/:id/reblogged_by GET (See who has reblogged a status)
- * [ ] /api/v1/statuses/:id/favourited_by GET (See who has faved a status)
- * [ ] /api/v1/statuses/:id/favourite POST (Fave a status)
- * [ ] /api/v1/statuses/:id/favourite POST (Unfave a status)
+ * [x] /api/v1/statuses/:id/favourited_by GET (See who has faved a status)
+ * [x] /api/v1/statuses/:id/favourite POST (Fave a status)
+ * [x] /api/v1/statuses/:id/unfavourite POST (Unfave a status)
* [ ] /api/v1/statuses/:id/reblog POST (Reblog a status)
* [ ] /api/v1/statuses/:id/unreblog POST (Undo a reblog)
* [ ] /api/v1/statuses/:id/bookmark POST (Bookmark a status)
diff --git a/internal/apimodule/account/account.go b/internal/apimodule/account/account.go
index a94169eb2..f4a47f6a2 100644
--- a/internal/apimodule/account/account.go
+++ b/internal/apimodule/account/account.go
@@ -69,6 +69,7 @@ func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler
func (m *accountModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, basePath, m.accountCreatePOSTHandler)
r.AttachHandler(http.MethodGet, basePathWithID, m.muxHandler)
+ r.AttachHandler(http.MethodPatch, basePathWithID, m.muxHandler)
return nil
}
@@ -94,11 +95,16 @@ func (m *accountModule) CreateTables(db db.DB) error {
func (m *accountModule) muxHandler(c *gin.Context) {
ru := c.Request.RequestURI
- if strings.HasPrefix(ru, verifyPath) {
- m.accountVerifyGETHandler(c)
- } else if strings.HasPrefix(ru, updateCredentialsPath) {
- m.accountUpdateCredentialsPATCHHandler(c)
- } else {
- m.accountGETHandler(c)
+ switch c.Request.Method {
+ case http.MethodGet:
+ if strings.HasPrefix(ru, verifyPath) {
+ m.accountVerifyGETHandler(c)
+ } else {
+ m.accountGETHandler(c)
+ }
+ case http.MethodPatch:
+ if strings.HasPrefix(ru, updateCredentialsPath) {
+ m.accountUpdateCredentialsPATCHHandler(c)
+ }
}
}
diff --git a/internal/apimodule/fileserver/servefile.go b/internal/apimodule/fileserver/servefile.go
index 573ad0694..0421c5095 100644
--- a/internal/apimodule/fileserver/servefile.go
+++ b/internal/apimodule/fileserver/servefile.go
@@ -156,6 +156,8 @@ func (m *FileServer) serveAttachment(c *gin.Context, accountID string, mediaType
return
}
+ l.Errorf("about to serve content length: %d attachment bytes is: %d", int64(contentLength), int64(len(attachmentBytes)))
+
// finally we can return with all the information we derived above
c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(attachmentBytes), map[string]string{})
}
diff --git a/internal/apimodule/status/status.go b/internal/apimodule/status/status.go
index 37e6ee268..2ecd009bf 100644
--- a/internal/apimodule/status/status.go
+++ b/internal/apimodule/status/status.go
@@ -36,21 +36,28 @@ import (
)
const (
- IDKey = "id"
- BasePath = "/api/v1/statuses"
- BasePathWithID = BasePath + "/:" + IDKey
- ContextPath = BasePath + "/context"
- RebloggedPath = BasePath + "/reblogged_by"
- FavouritedPath = BasePath + "/favourited_by"
- FavouritePath = BasePath + "/favourite"
- ReblogPath = BasePath + "/reblog"
- UnreblogPath = BasePath + "/unreblog"
- BookmarkPath = BasePath + "/bookmark"
- UnbookmarkPath = BasePath + "/unbookmark"
- MutePath = BasePath + "/mute"
- UnmutePath = BasePath + "/unmute"
- PinPath = BasePath + "/pin"
- UnpinPath = BasePath + "/unpin"
+ IDKey = "id"
+ BasePath = "/api/v1/statuses"
+ BasePathWithID = BasePath + "/:" + IDKey
+
+ ContextPath = BasePathWithID + "/context"
+
+ FavouritedPath = BasePathWithID + "/favourited_by"
+ FavouritePath = BasePathWithID + "/favourite"
+ UnfavouritePath = BasePathWithID + "/unfavourite"
+
+ RebloggedPath = BasePathWithID + "/reblogged_by"
+ ReblogPath = BasePathWithID + "/reblog"
+ UnreblogPath = BasePathWithID + "/unreblog"
+
+ BookmarkPath = BasePathWithID + "/bookmark"
+ UnbookmarkPath = BasePathWithID + "/unbookmark"
+
+ MutePath = BasePathWithID + "/mute"
+ UnmutePath = BasePathWithID + "/unmute"
+
+ PinPath = BasePathWithID + "/pin"
+ UnpinPath = BasePathWithID + "/unpin"
)
type StatusModule struct {
@@ -77,8 +84,13 @@ func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, masto
// Route attaches all routes from this module to the given router
func (m *StatusModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler)
+ r.AttachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler)
+
+ r.AttachHandler(http.MethodPost, FavouritePath, m.StatusFavePOSTHandler)
+ r.AttachHandler(http.MethodPost, UnfavouritePath, m.StatusFavePOSTHandler)
+
+
r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler)
- r.AttachHandler(http.MethodDelete, BasePathWithID, m.muxHandler)
return nil
}
@@ -113,16 +125,15 @@ func (m *StatusModule) CreateTables(db db.DB) error {
func (m *StatusModule) muxHandler(c *gin.Context) {
m.log.Debug("entering mux handler")
ru := c.Request.RequestURI
- if c.Request.Method == http.MethodGet {
+
+ switch c.Request.Method {
+ case http.MethodGet:
if strings.HasPrefix(ru, ContextPath) {
// TODO
- } else if strings.HasPrefix(ru, RebloggedPath) {
- // TODO
+ } else if strings.HasPrefix(ru, FavouritedPath) {
+ m.StatusFavedByGETHandler(c)
} else {
m.StatusGETHandler(c)
}
}
- if c.Request.Method == http.MethodDelete {
- m.StatusDELETEHandler(c)
- }
}
diff --git a/internal/apimodule/status/statusfave.go b/internal/apimodule/status/statusfave.go
new file mode 100644
index 000000000..de475b905
--- /dev/null
+++ b/internal/apimodule/status/statusfave.go
@@ -0,0 +1,136 @@
+/*
+ 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 status
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/distributor"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (m *StatusModule) StatusFavePOSTHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "StatusFavePOSTHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+ l.Debugf("entering function")
+
+ authed, err := oauth.MustAuth(c, true, false, true, true) // we don't really need an app here but we want everything else
+ if err != nil {
+ l.Debug("not authed so can't fave status")
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
+ return
+ }
+
+ targetStatusID := c.Param(IDKey)
+ if targetStatusID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
+ return
+ }
+
+ l.Tracef("going to search for target status %s", targetStatusID)
+ targetStatus := >smodel.Status{}
+ if err := m.db.GetByID(targetStatusID, targetStatus); err != nil {
+ l.Errorf("error fetching status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Tracef("going to search for target account %s", targetStatus.AccountID)
+ targetAccount := >smodel.Account{}
+ if err := m.db.GetByID(targetStatus.AccountID, targetAccount); err != nil {
+ l.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to get relevant accounts")
+ relevantAccounts, err := m.db.PullRelevantAccountsFromStatus(targetStatus)
+ if err != nil {
+ l.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to see if status is visible")
+ visible, err := m.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that
+ if err != nil {
+ l.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ if !visible {
+ l.Trace("status is not visible")
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ // is the status faveable?
+ if !targetStatus.VisibilityAdvanced.Likeable {
+ l.Debug("status is not faveable")
+ c.JSON(http.StatusForbidden, gin.H{"error": fmt.Sprintf("status %s not faveable", targetStatusID)})
+ return
+ }
+
+ // it's visible! it's faveable! so let's fave the FUCK out of it
+ fave, err := m.db.FaveStatus(targetStatus, authed.Account.ID)
+ if err != nil {
+ l.Debugf("error faveing status: %s", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ var boostOfStatus *gtsmodel.Status
+ if targetStatus.BoostOfID != "" {
+ boostOfStatus = >smodel.Status{}
+ if err := m.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil {
+ l.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+ }
+
+ mastoStatus, err := m.mastoConverter.StatusToMasto(targetStatus, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus)
+ if err != nil {
+ l.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ // if the targeted status was already faved, faved will be nil
+ // only put the fave in the distributor if something actually changed
+ if fave != nil {
+ fave.FavedStatus = targetStatus // attach the status pointer to the fave for easy retrieval in the distributor
+ m.distributor.FromClientAPI() <- distributor.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsNote, // status is a note
+ APActivityType: gtsmodel.ActivityStreamsLike, // we're creating a like/fave on the note
+ Activity: fave, // pass the fave along for processing
+ }
+ }
+
+ c.JSON(http.StatusOK, mastoStatus)
+}
diff --git a/internal/apimodule/status/statusfavedby.go b/internal/apimodule/status/statusfavedby.go
new file mode 100644
index 000000000..df08ad814
--- /dev/null
+++ b/internal/apimodule/status/statusfavedby.go
@@ -0,0 +1,128 @@
+/*
+ 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 status
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (m *StatusModule) StatusFavedByGETHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "statusGETHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+ l.Debugf("entering function")
+
+ var requestingAccount *gtsmodel.Account
+ authed, err := oauth.MustAuth(c, true, false, true, true) // we don't really need an app here but we want everything else
+ if err != nil {
+ l.Debug("not authed but will continue to serve anyway if public status")
+ requestingAccount = nil
+ } else {
+ requestingAccount = authed.Account
+ }
+
+ targetStatusID := c.Param(IDKey)
+ if targetStatusID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
+ return
+ }
+
+ l.Tracef("going to search for target status %s", targetStatusID)
+ targetStatus := >smodel.Status{}
+ if err := m.db.GetByID(targetStatusID, targetStatus); err != nil {
+ l.Errorf("error fetching status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Tracef("going to search for target account %s", targetStatus.AccountID)
+ targetAccount := >smodel.Account{}
+ if err := m.db.GetByID(targetStatus.AccountID, targetAccount); err != nil {
+ l.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to get relevant accounts")
+ relevantAccounts, err := m.db.PullRelevantAccountsFromStatus(targetStatus)
+ if err != nil {
+ l.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to see if status is visible")
+ visible, err := m.db.StatusVisible(targetStatus, targetAccount, requestingAccount, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that
+ if err != nil {
+ l.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ if !visible {
+ l.Trace("status is not visible")
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ // get ALL accounts that faved a status -- doesn't take account of blocks and mutes and stuff
+ favingAccounts, err := m.db.WhoFavedStatus(targetStatus)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error":err.Error()})
+ return
+ }
+
+ // filter the list so the user doesn't see accounts they blocked or which blocked them
+ filteredAccounts := []*gtsmodel.Account{}
+ for _, acc := range favingAccounts {
+ blocked, err := m.db.Blocked(authed.Account.ID, acc.ID)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error":err.Error()})
+ return
+ }
+ if !blocked {
+ filteredAccounts = append(filteredAccounts, acc)
+ }
+ }
+
+ // TODO: filter other things here? suspended? muted? silenced?
+
+ // now we can return the masto representation of those accounts
+ mastoAccounts := []*mastotypes.Account{}
+ for _, acc := range filteredAccounts {
+ mastoAccount, err := m.mastoConverter.AccountToMastoPublic(acc)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error":err.Error()})
+ return
+ }
+ mastoAccounts = append(mastoAccounts, mastoAccount)
+ }
+
+ c.JSON(http.StatusOK, mastoAccounts)
+}
diff --git a/internal/apimodule/status/statusunfave.go b/internal/apimodule/status/statusunfave.go
new file mode 100644
index 000000000..61ffd8e4c
--- /dev/null
+++ b/internal/apimodule/status/statusunfave.go
@@ -0,0 +1,136 @@
+/*
+ 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 status
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/distributor"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+)
+
+func (m *StatusModule) StatusUnfavePOSTHandler(c *gin.Context) {
+ l := m.log.WithFields(logrus.Fields{
+ "func": "StatusUnfavePOSTHandler",
+ "request_uri": c.Request.RequestURI,
+ "user_agent": c.Request.UserAgent(),
+ "origin_ip": c.ClientIP(),
+ })
+ l.Debugf("entering function")
+
+ authed, err := oauth.MustAuth(c, true, false, true, true) // we don't really need an app here but we want everything else
+ if err != nil {
+ l.Debug("not authed so can't unfave status")
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
+ return
+ }
+
+ targetStatusID := c.Param(IDKey)
+ if targetStatusID == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
+ return
+ }
+
+ l.Tracef("going to search for target status %s", targetStatusID)
+ targetStatus := >smodel.Status{}
+ if err := m.db.GetByID(targetStatusID, targetStatus); err != nil {
+ l.Errorf("error fetching status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Tracef("going to search for target account %s", targetStatus.AccountID)
+ targetAccount := >smodel.Account{}
+ if err := m.db.GetByID(targetStatus.AccountID, targetAccount); err != nil {
+ l.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to get relevant accounts")
+ relevantAccounts, err := m.db.PullRelevantAccountsFromStatus(targetStatus)
+ if err != nil {
+ l.Errorf("error fetching related accounts for status %s: %s", targetStatusID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ l.Trace("going to see if status is visible")
+ visible, err := m.db.StatusVisible(targetStatus, targetAccount, authed.Account, relevantAccounts) // requestingAccount might well be nil here, but StatusVisible knows how to take care of that
+ if err != nil {
+ l.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ if !visible {
+ l.Trace("status is not visible")
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ // is the status faveable?
+ if !targetStatus.VisibilityAdvanced.Likeable {
+ l.Debug("status is not faveable")
+ c.JSON(http.StatusForbidden, gin.H{"error": fmt.Sprintf("status %s not faveable so therefore not unfave-able", targetStatusID)})
+ return
+ }
+
+ // it's visible! it's faveable! so let's unfave the FUCK out of it
+ fave, err := m.db.UnfaveStatus(targetStatus, authed.Account.ID)
+ if err != nil {
+ l.Debugf("error unfaveing status: %s", err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ var boostOfStatus *gtsmodel.Status
+ if targetStatus.BoostOfID != "" {
+ boostOfStatus = >smodel.Status{}
+ if err := m.db.GetByID(targetStatus.BoostOfID, boostOfStatus); err != nil {
+ l.Errorf("error fetching boosted status %s: %s", targetStatus.BoostOfID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+ }
+
+ mastoStatus, err := m.mastoConverter.StatusToMasto(targetStatus, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostOfStatus)
+ if err != nil {
+ l.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("status %s not found", targetStatusID)})
+ return
+ }
+
+ // fave might be nil if this status wasn't faved in the first place
+ // we only want to pass the message to the distributor if something actually changed
+ if fave != nil {
+ fave.FavedStatus = targetStatus // attach the status pointer to the fave for easy retrieval in the distributor
+ m.distributor.FromClientAPI() <- distributor.FromClientAPI{
+ APObjectType: gtsmodel.ActivityStreamsNote, // status is a note
+ APActivityType: gtsmodel.ActivityStreamsUndo, // undo the fave
+ Activity: fave, // pass the undone fave along
+ }
+ }
+
+ c.JSON(http.StatusOK, mastoStatus)
+}
diff --git a/internal/apimodule/status/test/statusfave_test.go b/internal/apimodule/status/test/statusfave_test.go
new file mode 100644
index 000000000..aa2a2ecb3
--- /dev/null
+++ b/internal/apimodule/status/test/statusfave_test.go
@@ -0,0 +1,208 @@
+/*
+ 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 status
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/apimodule/status"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/distributor"
+ "github.com/superseriousbusiness/gotosocial/internal/mastotypes"
+ mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/storage"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type StatusFaveTestSuite struct {
+ // standard suite interfaces
+ suite.Suite
+ config *config.Config
+ db db.DB
+ log *logrus.Logger
+ storage storage.Storage
+ mastoConverter mastotypes.Converter
+ mediaHandler media.MediaHandler
+ oauthServer oauth.Server
+ distributor distributor.Distributor
+
+ // standard suite models
+ testTokens map[string]*oauth.Token
+ testClients map[string]*oauth.Client
+ testApplications map[string]*gtsmodel.Application
+ testUsers map[string]*gtsmodel.User
+ testAccounts map[string]*gtsmodel.Account
+ testAttachments map[string]*gtsmodel.MediaAttachment
+ testStatuses map[string]*gtsmodel.Status
+
+ // module being tested
+ statusModule *status.StatusModule
+}
+
+/*
+ TEST INFRASTRUCTURE
+*/
+
+// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
+func (suite *StatusFaveTestSuite) SetupSuite() {
+ // setup standard items
+ suite.config = testrig.NewTestConfig()
+ suite.db = testrig.NewTestDB()
+ suite.log = testrig.NewTestLog()
+ suite.storage = testrig.NewTestStorage()
+ suite.mastoConverter = testrig.NewTestMastoConverter(suite.db)
+ suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
+ suite.oauthServer = testrig.NewTestOauthServer(suite.db)
+ suite.distributor = testrig.NewTestDistributor()
+
+ // setup module being tested
+ suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
+}
+
+func (suite *StatusFaveTestSuite) TearDownSuite() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+func (suite *StatusFaveTestSuite) SetupTest() {
+ testrig.StandardDBSetup(suite.db)
+ testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
+ suite.testTokens = testrig.NewTestTokens()
+ suite.testClients = testrig.NewTestClients()
+ suite.testApplications = testrig.NewTestApplications()
+ suite.testUsers = testrig.NewTestUsers()
+ suite.testAccounts = testrig.NewTestAccounts()
+ suite.testAttachments = testrig.NewTestAttachments()
+ suite.testStatuses = testrig.NewTestStatuses()
+}
+
+// TearDownTest drops tables to make sure there's no data in the db
+func (suite *StatusFaveTestSuite) TearDownTest() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+/*
+ ACTUAL TESTS
+*/
+
+// fave a status
+func (suite *StatusFaveTestSuite) TestPostFave() {
+
+ t := suite.testTokens["local_account_1"]
+ oauthToken := oauth.PGTokenToOauthToken(t)
+
+
+ targetStatus := suite.testStatuses["admin_account_status_2"]
+
+ // setup
+ recorder := httptest.NewRecorder()
+ ctx, _ := gin.CreateTestContext(recorder)
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+ ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+ ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+ ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+ ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+
+ // normally the router would populate these params from the path values,
+ // but because we're calling the function directly, we need to set them manually.
+ ctx.Params = gin.Params{
+ gin.Param{
+ Key: status.IDKey,
+ Value: targetStatus.ID,
+ },
+ }
+
+ suite.statusModule.StatusFavePOSTHandler(ctx)
+
+ // check response
+ suite.EqualValues(http.StatusOK, recorder.Code)
+
+ result := recorder.Result()
+ defer result.Body.Close()
+ b, err := ioutil.ReadAll(result.Body)
+ assert.NoError(suite.T(), err)
+
+ statusReply := &mastomodel.Status{}
+ err = json.Unmarshal(b, statusReply)
+ assert.NoError(suite.T(), err)
+
+ assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)
+ assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)
+ assert.True(suite.T(), statusReply.Sensitive)
+ assert.Equal(suite.T(), mastomodel.VisibilityPublic, statusReply.Visibility)
+ assert.True(suite.T(), statusReply.Favourited)
+ assert.Equal(suite.T(), 1, statusReply.FavouritesCount)
+}
+
+// try to fave a status that's not faveable
+func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
+
+ t := suite.testTokens["local_account_1"]
+ oauthToken := oauth.PGTokenToOauthToken(t)
+
+ targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable
+
+ // setup
+ recorder := httptest.NewRecorder()
+ ctx, _ := gin.CreateTestContext(recorder)
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+ ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+ ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+ ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+ ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+
+ // normally the router would populate these params from the path values,
+ // but because we're calling the function directly, we need to set them manually.
+ ctx.Params = gin.Params{
+ gin.Param{
+ Key: status.IDKey,
+ Value: targetStatus.ID,
+ },
+ }
+
+ suite.statusModule.StatusFavePOSTHandler(ctx)
+
+ // check response
+ suite.EqualValues(http.StatusForbidden, recorder.Code) // we 403 unlikeable statuses
+
+ result := recorder.Result()
+ defer result.Body.Close()
+ b, err := ioutil.ReadAll(result.Body)
+ assert.NoError(suite.T(), err)
+ assert.Equal(suite.T(), fmt.Sprintf(`{"error":"status %s not faveable"}`, targetStatus.ID), string(b))
+}
+
+func TestStatusFaveTestSuite(t *testing.T) {
+ suite.Run(t, new(StatusFaveTestSuite))
+}
diff --git a/internal/apimodule/status/test/statusfavedby_test.go b/internal/apimodule/status/test/statusfavedby_test.go
new file mode 100644
index 000000000..83f66562b
--- /dev/null
+++ b/internal/apimodule/status/test/statusfavedby_test.go
@@ -0,0 +1,159 @@
+/*
+ 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 status
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/apimodule/status"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/distributor"
+ "github.com/superseriousbusiness/gotosocial/internal/mastotypes"
+ mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/storage"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type StatusFavedByTestSuite struct {
+ // standard suite interfaces
+ suite.Suite
+ config *config.Config
+ db db.DB
+ log *logrus.Logger
+ storage storage.Storage
+ mastoConverter mastotypes.Converter
+ mediaHandler media.MediaHandler
+ oauthServer oauth.Server
+ distributor distributor.Distributor
+
+ // standard suite models
+ testTokens map[string]*oauth.Token
+ testClients map[string]*oauth.Client
+ testApplications map[string]*gtsmodel.Application
+ testUsers map[string]*gtsmodel.User
+ testAccounts map[string]*gtsmodel.Account
+ testAttachments map[string]*gtsmodel.MediaAttachment
+ testStatuses map[string]*gtsmodel.Status
+
+ // module being tested
+ statusModule *status.StatusModule
+}
+
+// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
+func (suite *StatusFavedByTestSuite) SetupSuite() {
+ // setup standard items
+ suite.config = testrig.NewTestConfig()
+ suite.db = testrig.NewTestDB()
+ suite.log = testrig.NewTestLog()
+ suite.storage = testrig.NewTestStorage()
+ suite.mastoConverter = testrig.NewTestMastoConverter(suite.db)
+ suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
+ suite.oauthServer = testrig.NewTestOauthServer(suite.db)
+ suite.distributor = testrig.NewTestDistributor()
+
+ // setup module being tested
+ suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
+}
+
+func (suite *StatusFavedByTestSuite) TearDownSuite() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+func (suite *StatusFavedByTestSuite) SetupTest() {
+ testrig.StandardDBSetup(suite.db)
+ testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
+ suite.testTokens = testrig.NewTestTokens()
+ suite.testClients = testrig.NewTestClients()
+ suite.testApplications = testrig.NewTestApplications()
+ suite.testUsers = testrig.NewTestUsers()
+ suite.testAccounts = testrig.NewTestAccounts()
+ suite.testAttachments = testrig.NewTestAttachments()
+ suite.testStatuses = testrig.NewTestStatuses()
+}
+
+// TearDownTest drops tables to make sure there's no data in the db
+func (suite *StatusFavedByTestSuite) TearDownTest() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+/*
+ ACTUAL TESTS
+*/
+
+func (suite *StatusFavedByTestSuite) TestGetFavedBy() {
+ t := suite.testTokens["local_account_2"]
+ oauthToken := oauth.PGTokenToOauthToken(t)
+
+ targetStatus := suite.testStatuses["admin_account_status_1"] // this status is faved by local_account_1
+
+ // setup
+ recorder := httptest.NewRecorder()
+ ctx, _ := gin.CreateTestContext(recorder)
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_2"])
+ ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+ ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_2"])
+ ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_2"])
+ ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.FavouritedPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+
+ // normally the router would populate these params from the path values,
+ // but because we're calling the function directly, we need to set them manually.
+ ctx.Params = gin.Params{
+ gin.Param{
+ Key: status.IDKey,
+ Value: targetStatus.ID,
+ },
+ }
+
+ suite.statusModule.StatusFavedByGETHandler(ctx)
+
+ // check response
+ suite.EqualValues(http.StatusOK, recorder.Code)
+
+ result := recorder.Result()
+ defer result.Body.Close()
+ b, err := ioutil.ReadAll(result.Body)
+ assert.NoError(suite.T(), err)
+
+ accts := []mastomodel.Account{}
+ err = json.Unmarshal(b, &accts)
+ assert.NoError(suite.T(), err)
+
+ assert.Len(suite.T(), accts, 1)
+ assert.Equal(suite.T(), "the_mighty_zork", accts[0].Username)
+}
+
+func TestStatusFavedByTestSuite(t *testing.T) {
+ suite.Run(t, new(StatusFavedByTestSuite))
+}
diff --git a/internal/apimodule/status/test/statusunfave_test.go b/internal/apimodule/status/test/statusunfave_test.go
new file mode 100644
index 000000000..81276a1ed
--- /dev/null
+++ b/internal/apimodule/status/test/statusunfave_test.go
@@ -0,0 +1,219 @@
+/*
+ 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 status
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/apimodule/status"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/distributor"
+ "github.com/superseriousbusiness/gotosocial/internal/mastotypes"
+ mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
+ "github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ "github.com/superseriousbusiness/gotosocial/internal/storage"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type StatusUnfaveTestSuite struct {
+ // standard suite interfaces
+ suite.Suite
+ config *config.Config
+ db db.DB
+ log *logrus.Logger
+ storage storage.Storage
+ mastoConverter mastotypes.Converter
+ mediaHandler media.MediaHandler
+ oauthServer oauth.Server
+ distributor distributor.Distributor
+
+ // standard suite models
+ testTokens map[string]*oauth.Token
+ testClients map[string]*oauth.Client
+ testApplications map[string]*gtsmodel.Application
+ testUsers map[string]*gtsmodel.User
+ testAccounts map[string]*gtsmodel.Account
+ testAttachments map[string]*gtsmodel.MediaAttachment
+ testStatuses map[string]*gtsmodel.Status
+
+ // module being tested
+ statusModule *status.StatusModule
+}
+
+/*
+ TEST INFRASTRUCTURE
+*/
+
+// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
+func (suite *StatusUnfaveTestSuite) SetupSuite() {
+ // setup standard items
+ suite.config = testrig.NewTestConfig()
+ suite.db = testrig.NewTestDB()
+ suite.log = testrig.NewTestLog()
+ suite.storage = testrig.NewTestStorage()
+ suite.mastoConverter = testrig.NewTestMastoConverter(suite.db)
+ suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
+ suite.oauthServer = testrig.NewTestOauthServer(suite.db)
+ suite.distributor = testrig.NewTestDistributor()
+
+ // setup module being tested
+ suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
+}
+
+func (suite *StatusUnfaveTestSuite) TearDownSuite() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+func (suite *StatusUnfaveTestSuite) SetupTest() {
+ testrig.StandardDBSetup(suite.db)
+ testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
+ suite.testTokens = testrig.NewTestTokens()
+ suite.testClients = testrig.NewTestClients()
+ suite.testApplications = testrig.NewTestApplications()
+ suite.testUsers = testrig.NewTestUsers()
+ suite.testAccounts = testrig.NewTestAccounts()
+ suite.testAttachments = testrig.NewTestAttachments()
+ suite.testStatuses = testrig.NewTestStatuses()
+}
+
+// TearDownTest drops tables to make sure there's no data in the db
+func (suite *StatusUnfaveTestSuite) TearDownTest() {
+ testrig.StandardDBTeardown(suite.db)
+ testrig.StandardStorageTeardown(suite.storage)
+}
+
+/*
+ ACTUAL TESTS
+*/
+
+// unfave a status
+func (suite *StatusUnfaveTestSuite) TestPostUnfave() {
+
+ t := suite.testTokens["local_account_1"]
+ oauthToken := oauth.PGTokenToOauthToken(t)
+
+ // this is the status we wanna unfave: in the testrig it's already faved by this account
+ targetStatus := suite.testStatuses["admin_account_status_1"]
+
+ // setup
+ recorder := httptest.NewRecorder()
+ ctx, _ := gin.CreateTestContext(recorder)
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+ ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+ ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+ ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+ ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+
+ // normally the router would populate these params from the path values,
+ // but because we're calling the function directly, we need to set them manually.
+ ctx.Params = gin.Params{
+ gin.Param{
+ Key: status.IDKey,
+ Value: targetStatus.ID,
+ },
+ }
+
+ suite.statusModule.StatusUnfavePOSTHandler(ctx)
+
+ // check response
+ suite.EqualValues(http.StatusOK, recorder.Code)
+
+ result := recorder.Result()
+ defer result.Body.Close()
+ b, err := ioutil.ReadAll(result.Body)
+ assert.NoError(suite.T(), err)
+
+ statusReply := &mastomodel.Status{}
+ err = json.Unmarshal(b, statusReply)
+ assert.NoError(suite.T(), err)
+
+ assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)
+ assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)
+ assert.False(suite.T(), statusReply.Sensitive)
+ assert.Equal(suite.T(), mastomodel.VisibilityPublic, statusReply.Visibility)
+ assert.False(suite.T(), statusReply.Favourited)
+ assert.Equal(suite.T(), 0, statusReply.FavouritesCount)
+}
+
+// try to unfave a status that's already not faved
+func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {
+
+ t := suite.testTokens["local_account_1"]
+ oauthToken := oauth.PGTokenToOauthToken(t)
+
+ // this is the status we wanna unfave: in the testrig it's not faved by this account
+ targetStatus := suite.testStatuses["admin_account_status_2"]
+
+ // setup
+ recorder := httptest.NewRecorder()
+ ctx, _ := gin.CreateTestContext(recorder)
+ ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+ ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+ ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+ ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+ ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(status.UnfavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+
+ // normally the router would populate these params from the path values,
+ // but because we're calling the function directly, we need to set them manually.
+ ctx.Params = gin.Params{
+ gin.Param{
+ Key: status.IDKey,
+ Value: targetStatus.ID,
+ },
+ }
+
+ suite.statusModule.StatusUnfavePOSTHandler(ctx)
+
+ // check response
+ suite.EqualValues(http.StatusOK, recorder.Code)
+
+ result := recorder.Result()
+ defer result.Body.Close()
+ b, err := ioutil.ReadAll(result.Body)
+ assert.NoError(suite.T(), err)
+
+ statusReply := &mastomodel.Status{}
+ err = json.Unmarshal(b, statusReply)
+ assert.NoError(suite.T(), err)
+
+ assert.Equal(suite.T(), targetStatus.ContentWarning, statusReply.SpoilerText)
+ assert.Equal(suite.T(), targetStatus.Content, statusReply.Content)
+ assert.True(suite.T(), statusReply.Sensitive)
+ assert.Equal(suite.T(), mastomodel.VisibilityPublic, statusReply.Visibility)
+ assert.False(suite.T(), statusReply.Favourited)
+ assert.Equal(suite.T(), 0, statusReply.FavouritesCount)
+}
+
+func TestStatusUnfaveTestSuite(t *testing.T) {
+ suite.Run(t, new(StatusUnfaveTestSuite))
+}
diff --git a/internal/db/db.go b/internal/db/db.go
index 106780f5d..69ad7b822 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -235,6 +235,18 @@ type DB interface {
// StatusPinnedBy checks if a given status has been pinned by a given account ID
StatusPinnedBy(status *gtsmodel.Status, accountID string) (bool, error)
+ // FaveStatus faves the given status, using accountID as the faver.
+ // The returned fave will be nil if the status was already faved.
+ FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error)
+
+ // UnfaveStatus unfaves the given status, using accountID as the unfaver (sure, that's a word).
+ // The returned fave will be nil if the status was already not faved.
+ UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error)
+
+ // WhoFavedStatus returns a slice of accounts who faved the given status.
+ // This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user.
+ WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error)
+
/*
USEFUL CONVERSION FUNCTIONS
*/
diff --git a/internal/db/gtsmodel/account.go b/internal/db/gtsmodel/account.go
index 82f5b5c6f..4bf5a9d33 100644
--- a/internal/db/gtsmodel/account.go
+++ b/internal/db/gtsmodel/account.go
@@ -44,26 +44,10 @@ type Account struct {
ACCOUNT METADATA
*/
- // File name of the avatar on local storage
- AvatarFileName string
- // Gif? png? jpeg?
- AvatarContentType string
- // Size of the avatar in bytes
- AvatarFileSize int
- // When was the avatar last updated?
- AvatarUpdatedAt time.Time `pg:"type:timestamp"`
- // Where can the avatar be retrieved?
- AvatarRemoteURL string
- // File name of the header on local storage
- HeaderFileName string
- // Gif? png? jpeg?
- HeaderContentType string
- // Size of the header in bytes
- HeaderFileSize int
- // When was the header last updated?
- HeaderUpdatedAt time.Time `pg:"type:timestamp"`
- // Where can the header be retrieved?
- HeaderRemoteURL string
+ // ID of the avatar as a media attachment
+ AvatarMediaAttachmentID string
+ // ID of the header as a media attachment
+ HeaderMediaAttachmentID string
// DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
DisplayName string
// a key/value map of fields that this account has added to their profile
diff --git a/internal/db/gtsmodel/statusfave.go b/internal/db/gtsmodel/statusfave.go
index 852998387..9fb92b931 100644
--- a/internal/db/gtsmodel/statusfave.go
+++ b/internal/db/gtsmodel/statusfave.go
@@ -23,13 +23,16 @@ import "time"
// StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account
type StatusFave struct {
// id of this fave in the database
- ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"`
+ ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"`
// when was this fave created
- CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
+ CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
// id of the account that created ('did') the fave
- AccountID string `pg:",notnull"`
+ AccountID string `pg:",notnull"`
// id the account owning the faved status
- TargetAccountID string `pg:",notnull"`
+ TargetAccountID string `pg:",notnull"`
// database id of the status that has been 'faved'
- StatusID string `pg:",notnull"`
+ StatusID string `pg:",notnull"`
+
+ // FavedStatus is the status being interacted with. It won't be put or retrieved from the db, it's just for conveniently passing a pointer around.
+ FavedStatus *Status `pg:"-"`
}
diff --git a/internal/db/pg.go b/internal/db/pg.go
index f2caa813d..610061324 100644
--- a/internal/db/pg.go
+++ b/internal/db/pg.go
@@ -497,12 +497,44 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
}
func (ps *postgresService) SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error {
- _, err := ps.conn.Model(mediaAttachment).Insert()
- return err
+ if mediaAttachment.Avatar && mediaAttachment.Header {
+ return errors.New("one media attachment cannot be both header and avatar")
+ }
+
+ var headerOrAVI string
+ if mediaAttachment.Avatar {
+ headerOrAVI = "avatar"
+ } else if mediaAttachment.Header {
+ headerOrAVI = "header"
+ } else {
+ return errors.New("given media attachment was neither a header nor an avatar")
+ }
+
+ // TODO: there are probably more side effects here that need to be handled
+ if _, err := ps.conn.Model(mediaAttachment).OnConflict("(id) DO UPDATE").Insert(); err != nil {
+ return err
+ }
+
+ if _, err := ps.conn.Model(>smodel.Account{}).Set(fmt.Sprintf("%s_media_attachment_id = ?", headerOrAVI), mediaAttachment.ID).Where("id = ?", accountID).Update(); err != nil {
+ return err
+ }
+ return nil
}
func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error {
- if err := ps.conn.Model(header).Where("account_id = ?", accountID).Where("header = ?", true).Select(); err != nil {
+ acct := >smodel.Account{}
+ if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil {
+ if err == pg.ErrNoRows {
+ return ErrNoEntries{}
+ }
+ return err
+ }
+
+ if acct.HeaderMediaAttachmentID == "" {
+ return ErrNoEntries{}
+ }
+
+ if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil {
if err == pg.ErrNoRows {
return ErrNoEntries{}
}
@@ -512,7 +544,19 @@ func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachmen
}
func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error {
- if err := ps.conn.Model(avatar).Where("account_id = ?", accountID).Where("avatar = ?", true).Select(); err != nil {
+ acct := >smodel.Account{}
+ if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil {
+ if err == pg.ErrNoRows {
+ return ErrNoEntries{}
+ }
+ return err
+ }
+
+ if acct.AvatarMediaAttachmentID == "" {
+ return ErrNoEntries{}
+ }
+
+ if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil {
if err == pg.ErrNoRows {
return ErrNoEntries{}
}
@@ -806,6 +850,79 @@ func (ps *postgresService) StatusPinnedBy(status *gtsmodel.Status, accountID str
return ps.conn.Model(>smodel.StatusPin{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
}
+func (ps *postgresService) FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) {
+ // first check if a fave already exists, we can just return if so
+ existingFave := >smodel.StatusFave{}
+ err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select()
+ if err == nil {
+ // fave already exists so just return nothing at all
+ return nil, nil
+ }
+
+ // an error occurred so it might exist or not, we don't know
+ if err != pg.ErrNoRows {
+ return nil, err
+ }
+
+ // it doesn't exist so create it
+ newFave := >smodel.StatusFave{
+ AccountID: accountID,
+ TargetAccountID: status.AccountID,
+ StatusID: status.ID,
+ }
+ if _, err = ps.conn.Model(newFave).Insert(); err != nil {
+ return nil, err
+ }
+
+ return newFave, nil
+}
+
+func (ps *postgresService) UnfaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) {
+ // if a fave doesn't exist, we don't need to do anything
+ existingFave := >smodel.StatusFave{}
+ err := ps.conn.Model(existingFave).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Select()
+ // the fave doesn't exist so return nothing at all
+ if err == pg.ErrNoRows {
+ return nil, nil
+ }
+
+ // an error occurred so it might exist or not, we don't know
+ if err != nil && err != pg.ErrNoRows {
+ return nil, err
+ }
+
+ // the fave exists so remove it
+ if _, err = ps.conn.Model(>smodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Delete(); err != nil {
+ return nil, err
+ }
+
+ return existingFave, nil
+}
+
+func (ps *postgresService) WhoFavedStatus(status *gtsmodel.Status) ([]*gtsmodel.Account, error) {
+ accounts := []*gtsmodel.Account{}
+
+ faves := []*gtsmodel.StatusFave{}
+ if err := ps.conn.Model(&faves).Where("status_id = ?", status.ID).Select(); err != nil {
+ if err == pg.ErrNoRows {
+ return accounts, nil // no rows just means nobody has faved this status, so that's fine
+ }
+ return nil, err // an actual error has occurred
+ }
+
+ for _, f := range faves {
+ acc := >smodel.Account{}
+ if err := ps.conn.Model(acc).Where("id = ?", f.AccountID).Select(); err != nil {
+ if err == pg.ErrNoRows {
+ continue // the account doesn't exist for some reason??? but this isn't the place to worry about that so just skip it
+ }
+ return nil, err // an actual error has occurred
+ }
+ accounts = append(accounts, acc)
+ }
+ return accounts, nil
+}
+
/*
CONVERSION FUNCTIONS
*/
diff --git a/internal/mastotypes/converter.go b/internal/mastotypes/converter.go
index 46e7796e7..af78b5f15 100644
--- a/internal/mastotypes/converter.go
+++ b/internal/mastotypes/converter.go
@@ -170,8 +170,8 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Accou
return nil, fmt.Errorf("error getting avatar: %s", err)
}
}
- aviURL := avi.File.Path
- aviURLStatic := avi.Thumbnail.Path
+ aviURL := avi.URL
+ aviURLStatic := avi.Thumbnail.URL
header := >smodel.MediaAttachment{}
if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil {
@@ -179,8 +179,8 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Accou
return nil, fmt.Errorf("error getting header: %s", err)
}
}
- headerURL := header.File.Path
- headerURLStatic := header.Thumbnail.Path
+ headerURL := header.URL
+ headerURLStatic := header.Thumbnail.URL
// get the fields set on this account
fields := []mastotypes.Field{}
diff --git a/testrig/db.go b/testrig/db.go
index 6f61b3763..5974eae69 100644
--- a/testrig/db.go
+++ b/testrig/db.go
@@ -123,6 +123,12 @@ func StandardDBSetup(db db.DB) {
}
}
+ for _, v := range NewTestFaves() {
+ if err := db.Put(v); err != nil {
+ panic(err)
+ }
+ }
+
if err := db.CreateInstanceAccount(); err != nil {
panic(err)
}
diff --git a/testrig/media/zork-original.jpeg b/testrig/media/zork-original.jpeg
new file mode 100644
index 000000000..7d8bc1fc7
Binary files /dev/null and b/testrig/media/zork-original.jpeg differ
diff --git a/testrig/media/zork-small.jpeg b/testrig/media/zork-small.jpeg
new file mode 100644
index 000000000..60be12564
Binary files /dev/null and b/testrig/media/zork-small.jpeg differ
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index 829c40455..f028bbd8d 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -41,6 +41,16 @@ func NewTestTokens() map[string]*oauth.Token {
AccessCreateAt: time.Now(),
AccessExpiresAt: time.Now().Add(72 * time.Hour),
},
+ "local_account_2": {
+ ID: "b04cae99-39b5-4610-a425-dc6b91c78a72",
+ ClientID: "a4f6a2ea-a32b-4600-8853-72fc4ad98a1f",
+ UserID: "d120bd97-866f-4a05-9690-a1294b9934c3",
+ RedirectURI: "http://localhost:8080",
+ Scope: "read write follow push",
+ Access: "PIPINALKNNNFNF98717NAMNAMNFKIJKJ881818KJKJAKJJJA",
+ AccessCreateAt: time.Now(),
+ AccessExpiresAt: time.Now().Add(72 * time.Hour),
+ },
}
return tokens
}
@@ -243,184 +253,152 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Username: "localhost:8080",
},
"unconfirmed_account": {
- ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d",
- Username: "weed_lord420",
- AvatarFileName: "",
- AvatarContentType: "",
- AvatarFileSize: 0,
- AvatarUpdatedAt: time.Time{},
- AvatarRemoteURL: "",
- HeaderFileName: "",
- HeaderContentType: "",
- HeaderFileSize: 0,
- HeaderUpdatedAt: time.Time{},
- HeaderRemoteURL: "",
- DisplayName: "",
- Fields: []gtsmodel.Field{},
- Note: "",
- Memorial: false,
- MovedToAccountID: "",
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- Bot: false,
- Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
- Locked: false,
- Discoverable: false,
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: false,
- Language: "en",
- URI: "http://localhost:8080/users/weed_lord420",
- URL: "http://localhost:8080/@weed_lord420",
- LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/weed_lord420/inbox",
- OutboxURL: "http://localhost:8080/users/weed_lord420/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/weed_lord420/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
- ActorType: gtsmodel.ActivityStreamsPerson,
- AlsoKnownAs: "",
- PrivateKey: &rsa.PrivateKey{},
- PublicKey: &rsa.PublicKey{},
- SensitizedAt: time.Time{},
- SilencedAt: time.Time{},
- SuspendedAt: time.Time{},
- HideCollections: false,
- SuspensionOrigin: "",
+ ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d",
+ Username: "weed_lord420",
+ AvatarMediaAttachmentID: "",
+ HeaderMediaAttachmentID: "",
+ DisplayName: "",
+ Fields: []gtsmodel.Field{},
+ Note: "",
+ Memorial: false,
+ MovedToAccountID: "",
+ CreatedAt: time.Now(),
+ UpdatedAt: time.Now(),
+ Bot: false,
+ Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
+ Locked: false,
+ Discoverable: false,
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: false,
+ Language: "en",
+ URI: "http://localhost:8080/users/weed_lord420",
+ URL: "http://localhost:8080/@weed_lord420",
+ LastWebfingeredAt: time.Time{},
+ InboxURL: "http://localhost:8080/users/weed_lord420/inbox",
+ OutboxURL: "http://localhost:8080/users/weed_lord420/outbox",
+ SharedInboxURL: "",
+ FollowersURL: "http://localhost:8080/users/weed_lord420/followers",
+ FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
+ ActorType: gtsmodel.ActivityStreamsPerson,
+ AlsoKnownAs: "",
+ PrivateKey: &rsa.PrivateKey{},
+ PublicKey: &rsa.PublicKey{},
+ SensitizedAt: time.Time{},
+ SilencedAt: time.Time{},
+ SuspendedAt: time.Time{},
+ HideCollections: false,
+ SuspensionOrigin: "",
},
"admin_account": {
- ID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
- Username: "admin",
- AvatarFileName: "",
- AvatarContentType: "",
- AvatarFileSize: 0,
- AvatarUpdatedAt: time.Time{},
- AvatarRemoteURL: "",
- HeaderFileName: "",
- HeaderContentType: "",
- HeaderFileSize: 0,
- HeaderUpdatedAt: time.Time{},
- HeaderRemoteURL: "",
- DisplayName: "",
- Fields: []gtsmodel.Field{},
- Note: "",
- Memorial: false,
- MovedToAccountID: "",
- CreatedAt: time.Now().Add(-72 * time.Hour),
- UpdatedAt: time.Now().Add(-72 * time.Hour),
- Bot: false,
- Reason: "",
- Locked: false,
- Discoverable: true,
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: false,
- Language: "en",
- URI: "http://localhost:8080/users/admin",
- URL: "http://localhost:8080/@admin",
- LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/admin/inbox",
- OutboxURL: "http://localhost:8080/users/admin/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/admin/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
- ActorType: gtsmodel.ActivityStreamsPerson,
- AlsoKnownAs: "",
- PrivateKey: &rsa.PrivateKey{},
- PublicKey: &rsa.PublicKey{},
- SensitizedAt: time.Time{},
- SilencedAt: time.Time{},
- SuspendedAt: time.Time{},
- HideCollections: false,
- SuspensionOrigin: "",
+ ID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
+ Username: "admin",
+ AvatarMediaAttachmentID: "",
+ HeaderMediaAttachmentID: "",
+ DisplayName: "",
+ Fields: []gtsmodel.Field{},
+ Note: "",
+ Memorial: false,
+ MovedToAccountID: "",
+ CreatedAt: time.Now().Add(-72 * time.Hour),
+ UpdatedAt: time.Now().Add(-72 * time.Hour),
+ Bot: false,
+ Reason: "",
+ Locked: false,
+ Discoverable: true,
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: false,
+ Language: "en",
+ URI: "http://localhost:8080/users/admin",
+ URL: "http://localhost:8080/@admin",
+ LastWebfingeredAt: time.Time{},
+ InboxURL: "http://localhost:8080/users/admin/inbox",
+ OutboxURL: "http://localhost:8080/users/admin/outbox",
+ SharedInboxURL: "",
+ FollowersURL: "http://localhost:8080/users/admin/followers",
+ FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
+ ActorType: gtsmodel.ActivityStreamsPerson,
+ AlsoKnownAs: "",
+ PrivateKey: &rsa.PrivateKey{},
+ PublicKey: &rsa.PublicKey{},
+ SensitizedAt: time.Time{},
+ SilencedAt: time.Time{},
+ SuspendedAt: time.Time{},
+ HideCollections: false,
+ SuspensionOrigin: "",
},
"local_account_1": {
- ID: "580072df-4d03-4684-a412-89fd6f7d77e6",
- Username: "the_mighty_zork",
- AvatarFileName: "http://localhost:8080/fileserver/media/580072df-4d03-4684-a412-89fd6f7d77e6/avatar/original/75cfbe52-a5fb-451b-8f5a-b023229dce8d.jpeg",
- AvatarContentType: "image/jpeg",
- AvatarFileSize: 0,
- AvatarUpdatedAt: time.Time{},
- AvatarRemoteURL: "",
- HeaderFileName: "http://localhost:8080/fileserver/media/580072df-4d03-4684-a412-89fd6f7d77e6/header/original/9651c1ed-c288-4063-a95c-c7f8ff2a633f.jpeg",
- HeaderContentType: "image/jpeg",
- HeaderFileSize: 0,
- HeaderUpdatedAt: time.Time{},
- HeaderRemoteURL: "",
- DisplayName: "original zork (he/they)",
- Fields: []gtsmodel.Field{},
- Note: "hey yo this is my profile!",
- Memorial: false,
- MovedToAccountID: "",
- CreatedAt: time.Now().Add(-48 * time.Hour),
- UpdatedAt: time.Now().Add(-48 * time.Hour),
- Bot: false,
- Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
- Locked: false,
- Discoverable: true,
- Privacy: gtsmodel.VisibilityPublic,
- Sensitive: false,
- Language: "en",
- URI: "http://localhost:8080/users/the_mighty_zork",
- URL: "http://localhost:8080/@the_mighty_zork",
- LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/the_mighty_zork/inbox",
- OutboxURL: "http://localhost:8080/users/the_mighty_zork/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
- ActorType: gtsmodel.ActivityStreamsPerson,
- AlsoKnownAs: "",
- PrivateKey: &rsa.PrivateKey{},
- PublicKey: &rsa.PublicKey{},
- SensitizedAt: time.Time{},
- SilencedAt: time.Time{},
- SuspendedAt: time.Time{},
- HideCollections: false,
- SuspensionOrigin: "",
+ ID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ Username: "the_mighty_zork",
+ AvatarMediaAttachmentID: "a849906f-8b8e-4b43-ac2f-6979ccbcd442",
+ HeaderMediaAttachmentID: "",
+ DisplayName: "original zork (he/they)",
+ Fields: []gtsmodel.Field{},
+ Note: "hey yo this is my profile!",
+ Memorial: false,
+ MovedToAccountID: "",
+ CreatedAt: time.Now().Add(-48 * time.Hour),
+ UpdatedAt: time.Now().Add(-48 * time.Hour),
+ Bot: false,
+ Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
+ Locked: false,
+ Discoverable: true,
+ Privacy: gtsmodel.VisibilityPublic,
+ Sensitive: false,
+ Language: "en",
+ URI: "http://localhost:8080/users/the_mighty_zork",
+ URL: "http://localhost:8080/@the_mighty_zork",
+ LastWebfingeredAt: time.Time{},
+ InboxURL: "http://localhost:8080/users/the_mighty_zork/inbox",
+ OutboxURL: "http://localhost:8080/users/the_mighty_zork/outbox",
+ SharedInboxURL: "",
+ FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers",
+ FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
+ ActorType: gtsmodel.ActivityStreamsPerson,
+ AlsoKnownAs: "",
+ PrivateKey: &rsa.PrivateKey{},
+ PublicKey: &rsa.PublicKey{},
+ SensitizedAt: time.Time{},
+ SilencedAt: time.Time{},
+ SuspendedAt: time.Time{},
+ HideCollections: false,
+ SuspensionOrigin: "",
},
"local_account_2": {
- ID: "eecaad73-5703-426d-9312-276641daa31e",
- Username: "1happyturtle",
- AvatarFileName: "http://localhost:8080/fileserver/media/eecaad73-5703-426d-9312-276641daa31e/avatar/original/d5e7c265-91a6-4d84-8c27-7e1efe5720da.jpeg",
- AvatarContentType: "image/jpeg",
- AvatarFileSize: 0,
- AvatarUpdatedAt: time.Time{},
- AvatarRemoteURL: "",
- HeaderFileName: "http://localhost:8080/fileserver/media/eecaad73-5703-426d-9312-276641daa31e/header/original/e75d4117-21b6-4315-a428-eb3944235996.jpeg",
- HeaderContentType: "image/jpeg",
- HeaderFileSize: 0,
- HeaderUpdatedAt: time.Time{},
- HeaderRemoteURL: "",
- DisplayName: "happy little turtle :3",
- Fields: []gtsmodel.Field{},
- Note: "i post about things that concern me",
- Memorial: false,
- MovedToAccountID: "",
- CreatedAt: time.Now().Add(-190 * time.Hour),
- UpdatedAt: time.Now().Add(-36 * time.Hour),
- Bot: false,
- Reason: "",
- Locked: true,
- Discoverable: false,
- Privacy: gtsmodel.VisibilityFollowersOnly,
- Sensitive: false,
- Language: "en",
- URI: "http://localhost:8080/users/1happyturtle",
- URL: "http://localhost:8080/@1happyturtle",
- LastWebfingeredAt: time.Time{},
- InboxURL: "http://localhost:8080/users/1happyturtle/inbox",
- OutboxURL: "http://localhost:8080/users/1happyturtle/outbox",
- SharedInboxURL: "",
- FollowersURL: "http://localhost:8080/users/1happyturtle/followers",
- FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
- ActorType: gtsmodel.ActivityStreamsPerson,
- AlsoKnownAs: "",
- PrivateKey: &rsa.PrivateKey{},
- PublicKey: &rsa.PublicKey{},
- SensitizedAt: time.Time{},
- SilencedAt: time.Time{},
- SuspendedAt: time.Time{},
- HideCollections: false,
- SuspensionOrigin: "",
+ ID: "eecaad73-5703-426d-9312-276641daa31e",
+ Username: "1happyturtle",
+ AvatarMediaAttachmentID: "",
+ HeaderMediaAttachmentID: "",
+ DisplayName: "happy little turtle :3",
+ Fields: []gtsmodel.Field{},
+ Note: "i post about things that concern me",
+ Memorial: false,
+ MovedToAccountID: "",
+ CreatedAt: time.Now().Add(-190 * time.Hour),
+ UpdatedAt: time.Now().Add(-36 * time.Hour),
+ Bot: false,
+ Reason: "",
+ Locked: true,
+ Discoverable: false,
+ Privacy: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: false,
+ Language: "en",
+ URI: "http://localhost:8080/users/1happyturtle",
+ URL: "http://localhost:8080/@1happyturtle",
+ LastWebfingeredAt: time.Time{},
+ InboxURL: "http://localhost:8080/users/1happyturtle/inbox",
+ OutboxURL: "http://localhost:8080/users/1happyturtle/outbox",
+ SharedInboxURL: "",
+ FollowersURL: "http://localhost:8080/users/1happyturtle/followers",
+ FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
+ ActorType: gtsmodel.ActivityStreamsPerson,
+ AlsoKnownAs: "",
+ PrivateKey: &rsa.PrivateKey{},
+ PublicKey: &rsa.PublicKey{},
+ SensitizedAt: time.Time{},
+ SilencedAt: time.Time{},
+ SuspendedAt: time.Time{},
+ HideCollections: false,
+ SuspensionOrigin: "",
},
"remote_account_1": {
ID: "c2c6e647-e2a9-4286-883b-e4a188186664",
@@ -643,9 +621,58 @@ func NewTestAttachments() map[string]*gtsmodel.MediaAttachment {
Avatar: false,
Header: false,
},
+ "local_account_1_avatar": {
+ ID: "a849906f-8b8e-4b43-ac2f-6979ccbcd442",
+ StatusID: "", // this attachment isn't connected to a status
+ URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/avatar/original/a849906f-8b8e-4b43-ac2f-6979ccbcd442.jpeg",
+ RemoteURL: "",
+ CreatedAt: time.Now().Add(47 * time.Hour),
+ UpdatedAt: time.Now().Add(47 * time.Hour),
+ Type: gtsmodel.FileTypeImage,
+ FileMeta: gtsmodel.FileMeta{
+ Original: gtsmodel.Original{
+ Width: 1092,
+ Height: 1800,
+ Size: 1965600,
+ Aspect: 0.6066666666666667,
+ },
+ Small: gtsmodel.Small{
+ Width: 155,
+ Height: 256,
+ Size: 39680,
+ Aspect: 0.60546875,
+ },
+ Focus: gtsmodel.Focus{
+ X: 0,
+ Y: 0,
+ },
+ },
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ Description: "a green goblin looking nasty",
+ ScheduledStatusID: "",
+ Blurhash: "LKK9MT,p|YSNDkJ-5rsmvnwcOoe:",
+ Processing: 2,
+ File: gtsmodel.File{
+ Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/avatar/original/a849906f-8b8e-4b43-ac2f-6979ccbcd442.jpeg",
+ ContentType: "image/jpeg",
+ FileSize: 457680,
+ UpdatedAt: time.Now().Add(47 * time.Hour),
+ },
+ Thumbnail: gtsmodel.Thumbnail{
+ Path: "/gotosocial/storage/580072df-4d03-4684-a412-89fd6f7d77e6/avatar/small/a849906f-8b8e-4b43-ac2f-6979ccbcd442.jpeg",
+ ContentType: "image/jpeg",
+ FileSize: 15374,
+ UpdatedAt: time.Now().Add(47 * time.Hour),
+ URL: "http://localhost:8080/fileserver/580072df-4d03-4684-a412-89fd6f7d77e6/avatar/small/a849906f-8b8e-4b43-ac2f-6979ccbcd442.jpeg",
+ RemoteURL: "",
+ },
+ Avatar: true,
+ Header: false,
+ },
}
}
+// NewTestEmojis returns a map of gts emojis, keyed by the emoji shortcode
func NewTestEmojis() map[string]*gtsmodel.Emoji {
return map[string]*gtsmodel.Emoji{
"rainbow": {
@@ -693,9 +720,14 @@ func NewTestStoredAttachments() map[string]filenames {
original: "ohyou-original.jpeg",
small: "ohyou-small.jpeg",
},
+ "local_account_1_avatar": {
+ original: "zork-original.jpeg",
+ small: "zork-small.jpeg",
+ },
}
}
+// NewtestStoredEmoji returns a map of filenames, keyed according to which emoji they pertain to
func NewTestStoredEmoji() map[string]filenames {
return map[string]filenames{
"rainbow": {
@@ -710,24 +742,24 @@ func NewTestStoredEmoji() map[string]filenames {
func NewTestStatuses() map[string]*gtsmodel.Status {
return map[string]*gtsmodel.Status{
"admin_account_status_1": {
- ID: "502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
- URI: "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
- URL: "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
- Content: "hello world! #welcome ! first post on the instance :rainbow: !",
- Attachments: []string{"b052241b-f30f-4dc6-92fc-2bad0be1f8d8"},
- Tags: []string{"a7e8f5ca-88a1-4652-8079-a187eab8d56e"},
- Mentions: []string{},
- Emojis: []string{"a96ec4f3-1cae-47e4-a508-f9d66a6b221b"},
- CreatedAt: time.Now().Add(-71 * time.Hour),
- UpdatedAt: time.Now().Add(-71 * time.Hour),
- Local: true,
- AccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "",
- Visibility: gtsmodel.VisibilityPublic,
- Sensitive: false,
- Language: "en",
+ ID: "502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+ URI: "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+ URL: "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+ Content: "hello world! #welcome ! first post on the instance :rainbow: !",
+ Attachments: []string{"b052241b-f30f-4dc6-92fc-2bad0be1f8d8"},
+ Tags: []string{"a7e8f5ca-88a1-4652-8079-a187eab8d56e"},
+ Mentions: []string{},
+ Emojis: []string{"a96ec4f3-1cae-47e4-a508-f9d66a6b221b"},
+ CreatedAt: time.Now().Add(-71 * time.Hour),
+ UpdatedAt: time.Now().Add(-71 * time.Hour),
+ Local: true,
+ AccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: false,
+ Language: "en",
CreatedWithApplicationID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -738,20 +770,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"admin_account_status_2": {
- ID: "0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
- URI: "http://localhost:8080/users/admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
- URL: "http://localhost:8080/@admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
- Content: "🐕🐕🐕🐕🐕",
- CreatedAt: time.Now().Add(-70 * time.Hour),
- UpdatedAt: time.Now().Add(-70 * time.Hour),
- Local: true,
- AccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "open to see some puppies",
- Visibility: gtsmodel.VisibilityPublic,
- Sensitive: true,
- Language: "en",
+ ID: "0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+ URI: "http://localhost:8080/users/admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+ URL: "http://localhost:8080/@admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+ Content: "🐕🐕🐕🐕🐕",
+ CreatedAt: time.Now().Add(-70 * time.Hour),
+ UpdatedAt: time.Now().Add(-70 * time.Hour),
+ Local: true,
+ AccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "open to see some puppies",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: true,
+ Language: "en",
CreatedWithApplicationID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -762,20 +794,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_1": {
- ID: "91b1e795-74ff-4672-a4c4-476616710e2d",
- URI: "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
- URL: "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
- Content: "hello everyone!",
- CreatedAt: time.Now().Add(-47 * time.Hour),
- UpdatedAt: time.Now().Add(-47 * time.Hour),
- Local: true,
- AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "introduction post",
- Visibility: gtsmodel.VisibilityPublic,
- Sensitive: true,
- Language: "en",
+ ID: "91b1e795-74ff-4672-a4c4-476616710e2d",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
+ Content: "hello everyone!",
+ CreatedAt: time.Now().Add(-47 * time.Hour),
+ UpdatedAt: time.Now().Add(-47 * time.Hour),
+ Local: true,
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "introduction post",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: true,
+ Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -786,20 +818,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_2": {
- ID: "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
- URI: "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
- URL: "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
- Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
- CreatedAt: time.Now().Add(-46 * time.Hour),
- UpdatedAt: time.Now().Add(-46 * time.Hour),
- Local: true,
- AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "",
- Visibility: gtsmodel.VisibilityUnlocked,
- Sensitive: false,
- Language: "en",
+ ID: "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+ Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
+ CreatedAt: time.Now().Add(-46 * time.Hour),
+ UpdatedAt: time.Now().Add(-46 * time.Hour),
+ Local: true,
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityUnlocked,
+ Sensitive: false,
+ Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: false,
@@ -810,20 +842,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_3": {
- ID: "5e41963f-8ab9-4147-9f00-52d56e19da65",
- URI: "http://localhost:8080/users/the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
- URL: "http://localhost:8080/@the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
- Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
- CreatedAt: time.Now().Add(-45 * time.Hour),
- UpdatedAt: time.Now().Add(-45 * time.Hour),
- Local: true,
- AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "test: you shouldn't be able to interact with this post in any way",
- Visibility: gtsmodel.VisibilityMutualsOnly,
- Sensitive: false,
- Language: "en",
+ ID: "5e41963f-8ab9-4147-9f00-52d56e19da65",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
+ Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
+ CreatedAt: time.Now().Add(-45 * time.Hour),
+ UpdatedAt: time.Now().Add(-45 * time.Hour),
+ Local: true,
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "test: you shouldn't be able to interact with this post in any way",
+ Visibility: gtsmodel.VisibilityMutualsOnly,
+ Sensitive: false,
+ Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -834,21 +866,21 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_4": {
- ID: "18524c05-97dc-46d7-b474-c811bd9e1e32",
- URI: "http://localhost:8080/users/the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32",
- URL: "http://localhost:8080/@the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32",
- Content: "here's a little gif of trent",
- Attachments: []string{"510f6033-798b-4390-81b1-c38ca2205ad3"},
- CreatedAt: time.Now().Add(-1 * time.Hour),
- UpdatedAt: time.Now().Add(-1 * time.Hour),
- Local: true,
- AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "eye contact, trent reznor gif",
- Visibility: gtsmodel.VisibilityMutualsOnly,
- Sensitive: false,
- Language: "en",
+ ID: "18524c05-97dc-46d7-b474-c811bd9e1e32",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/18524c05-97dc-46d7-b474-c811bd9e1e32",
+ Content: "here's a little gif of trent",
+ Attachments: []string{"510f6033-798b-4390-81b1-c38ca2205ad3"},
+ CreatedAt: time.Now().Add(-1 * time.Hour),
+ UpdatedAt: time.Now().Add(-1 * time.Hour),
+ Local: true,
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "eye contact, trent reznor gif",
+ Visibility: gtsmodel.VisibilityMutualsOnly,
+ Sensitive: false,
+ Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -859,20 +891,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_2_status_1": {
- ID: "8945ccf2-3873-45e9-aa13-fd7163f19775",
- URI: "http://localhost:8080/users/1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
- URL: "http://localhost:8080/@1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
- Content: "🐢 hi everyone i post about turtles 🐢",
- CreatedAt: time.Now().Add(-189 * time.Hour),
- UpdatedAt: time.Now().Add(-189 * time.Hour),
- Local: true,
- AccountID: "eecaad73-5703-426d-9312-276641daa31e",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "introduction post",
- Visibility: gtsmodel.VisibilityPublic,
- Sensitive: true,
- Language: "en",
+ ID: "8945ccf2-3873-45e9-aa13-fd7163f19775",
+ URI: "http://localhost:8080/users/1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
+ URL: "http://localhost:8080/@1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
+ Content: "🐢 hi everyone i post about turtles 🐢",
+ CreatedAt: time.Now().Add(-189 * time.Hour),
+ UpdatedAt: time.Now().Add(-189 * time.Hour),
+ Local: true,
+ AccountID: "eecaad73-5703-426d-9312-276641daa31e",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "introduction post",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: true,
+ Language: "en",
CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -883,20 +915,20 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_2_status_2": {
- ID: "c7e25a86-f0d3-4705-a73c-c597f687d3dd",
- URI: "http://localhost:8080/users/1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
- URL: "http://localhost:8080/@1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
- Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
- CreatedAt: time.Now().Add(-1 * time.Minute),
- UpdatedAt: time.Now().Add(-1 * time.Minute),
- Local: true,
- AccountID: "eecaad73-5703-426d-9312-276641daa31e",
- InReplyToID: "",
- BoostOfID: "",
- ContentWarning: "",
- Visibility: gtsmodel.VisibilityPublic,
- Sensitive: true,
- Language: "en",
+ ID: "c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+ URI: "http://localhost:8080/users/1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+ URL: "http://localhost:8080/@1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+ Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
+ CreatedAt: time.Now().Add(-1 * time.Minute),
+ UpdatedAt: time.Now().Add(-1 * time.Minute),
+ Local: true,
+ AccountID: "eecaad73-5703-426d-9312-276641daa31e",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: true,
+ Language: "en",
CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
VisibilityAdvanced: >smodel.VisibilityAdvanced{
Federated: true,
@@ -906,9 +938,34 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
+ "local_account_2_status_3": {
+ ID: "75960e30-7a8e-4f45-87fa-440a4d1c9572",
+ URI: "http://localhost:8080/users/1happyturtle/statuses/75960e30-7a8e-4f45-87fa-440a4d1c9572",
+ URL: "http://localhost:8080/@1happyturtle/statuses/75960e30-7a8e-4f45-87fa-440a4d1c9572",
+ Content: "🐢 i don't mind people sharing this one but I don't want likes or replies to it because cba🐢",
+ CreatedAt: time.Now().Add(-2 * time.Minute),
+ UpdatedAt: time.Now().Add(-2 * time.Minute),
+ Local: true,
+ AccountID: "eecaad73-5703-426d-9312-276641daa31e",
+ InReplyToID: "",
+ BoostOfID: "",
+ ContentWarning: "you won't be able to like or reply to this",
+ Visibility: gtsmodel.VisibilityUnlocked,
+ Sensitive: true,
+ Language: "en",
+ CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
+ VisibilityAdvanced: >smodel.VisibilityAdvanced{
+ Federated: true,
+ Boostable: true,
+ Replyable: false,
+ Likeable: false,
+ },
+ ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+ },
}
}
+// NewTestTags returns a map of gts model tags keyed by their name
func NewTestTags() map[string]*gtsmodel.Tag {
return map[string]*gtsmodel.Tag{
"welcome": {
@@ -923,3 +980,16 @@ func NewTestTags() map[string]*gtsmodel.Tag {
},
}
}
+
+// NewTestFaves returns a map of gts model faves, keyed in the format [faving_account]_[target_status]
+func NewTestFaves() map[string]*gtsmodel.StatusFave {
+ return map[string]*gtsmodel.StatusFave{
+ "local_account_1_admin_account_status_1": {
+ ID: "fc4d42ef-631c-4125-bd9d-88695131284c",
+ CreatedAt: time.Now().Add(-47 * time.Hour),
+ AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", // local account 1
+ TargetAccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f", // admin account
+ StatusID: "502ccd6f-0edf-48d7-9016-2dfa4d3714cd", // admin account status 1
+ },
+ }
+}