delete statuses

This commit is contained in:
tsmethurst 2021-04-18 13:16:51 +02:00
commit ddfb9aae65
20 changed files with 475 additions and 147 deletions

View file

@ -71,7 +71,7 @@
* [ ] Statuses
* [x] /api/v1/statuses POST (Create a new status)
* [x] /api/v1/statuses/:id GET (View an existing status)
* [ ] /api/v1/statuses/:id DELETE (Delete a status)
* [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)

View file

@ -28,6 +28,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/testrig"
"github.com/urfave/cli/v2"
)
@ -255,6 +256,19 @@ func main() {
},
},
},
{
Name: "testrig",
Usage: "gotosocial testrig tasks",
Subcommands: []*cli.Command{
{
Name: "start",
Usage: "start the gotosocial testrig",
Action: func(c *cli.Context) error {
return runAction(c, testrig.Run)
},
},
},
},
},
}

View file

@ -32,17 +32,17 @@ import (
)
const (
accountIDKey = "account_id"
mediaTypeKey = "media_type"
mediaSizeKey = "media_size"
fileNameKey = "file_name"
AccountIDKey = "account_id"
MediaTypeKey = "media_type"
MediaSizeKey = "media_size"
FileNameKey = "file_name"
filesPath = "files"
FilesPath = "files"
)
// fileServer implements the RESTAPIModule interface.
// FileServer implements the RESTAPIModule interface.
// The goal here is to serve requested media files if the gotosocial server is configured to use local storage.
type fileServer struct {
type FileServer struct {
config *config.Config
db db.DB
storage storage.Storage
@ -52,7 +52,7 @@ type fileServer struct {
// New returns a new fileServer module
func New(config *config.Config, db db.DB, storage storage.Storage, log *logrus.Logger) apimodule.ClientAPIModule {
return &fileServer{
return &FileServer{
config: config,
db: db,
storage: storage,
@ -62,12 +62,12 @@ func New(config *config.Config, db db.DB, storage storage.Storage, log *logrus.L
}
// Route satisfies the RESTAPIModule interface
func (m *fileServer) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, accountIDKey, mediaTypeKey, mediaSizeKey, fileNameKey), m.ServeFile)
func (m *FileServer) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey), m.ServeFile)
return nil
}
func (m *fileServer) CreateTables(db db.DB) error {
func (m *FileServer) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},
}

View file

@ -33,7 +33,7 @@ import (
//
// Note: to mitigate scraping attempts, no information should be given out on a bad request except "404 page not found".
// Don't give away account ids or media ids or anything like that; callers shouldn't be able to infer anything.
func (m *fileServer) ServeFile(c *gin.Context) {
func (m *FileServer) ServeFile(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "ServeFile",
"request_uri": c.Request.RequestURI,
@ -45,28 +45,28 @@ func (m *fileServer) ServeFile(c *gin.Context) {
// We use request params to check what to pull out of the database/storage so check everything. A request URL should be formatted as follows:
// "https://example.org/fileserver/[ACCOUNT_ID]/[MEDIA_TYPE]/[MEDIA_SIZE]/[FILE_NAME]"
// "FILE_NAME" consists of two parts, the attachment's database id, a period, and the file extension.
accountID := c.Param(accountIDKey)
accountID := c.Param(AccountIDKey)
if accountID == "" {
l.Debug("missing accountID from request")
c.String(http.StatusNotFound, "404 page not found")
return
}
mediaType := c.Param(mediaTypeKey)
mediaType := c.Param(MediaTypeKey)
if mediaType == "" {
l.Debug("missing mediaType from request")
c.String(http.StatusNotFound, "404 page not found")
return
}
mediaSize := c.Param(mediaSizeKey)
mediaSize := c.Param(MediaSizeKey)
if mediaSize == "" {
l.Debug("missing mediaSize from request")
c.String(http.StatusNotFound, "404 page not found")
return
}
fileName := c.Param(fileNameKey)
fileName := c.Param(FileNameKey)
if fileName == "" {
l.Debug("missing fileName from request")
c.String(http.StatusNotFound, "404 page not found")
@ -86,7 +86,7 @@ func (m *fileServer) ServeFile(c *gin.Context) {
c.String(http.StatusNotFound, "404 page not found")
}
func (m *fileServer) serveAttachment(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
func (m *FileServer) serveAttachment(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
l := m.log.WithFields(logrus.Fields{
"func": "serveAttachment",
"request_uri": c.Request.RequestURI,
@ -160,7 +160,7 @@ func (m *fileServer) serveAttachment(c *gin.Context, accountID string, mediaType
c.DataFromReader(http.StatusOK, int64(contentLength), contentType, bytes.NewReader(attachmentBytes), map[string]string{})
}
func (m *fileServer) serveEmoji(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
func (m *FileServer) serveEmoji(c *gin.Context, accountID string, mediaType string, mediaSize string, fileName string) {
l := m.log.WithFields(logrus.Fields{
"func": "serveEmoji",
"request_uri": c.Request.RequestURI,

View file

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fileserver
package test
import (
"context"
@ -30,6 +30,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/fileserver"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
@ -60,7 +61,7 @@ type ServeFileTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// item being tested
fileServer *fileServer
fileServer *fileserver.FileServer
}
/*
@ -78,7 +79,7 @@ func (suite *ServeFileTestSuite) SetupSuite() {
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
// setup module being tested
suite.fileServer = New(suite.config, suite.db, suite.storage, suite.log).(*fileServer)
suite.fileServer = fileserver.New(suite.config, suite.db, suite.storage, suite.log).(*fileserver.FileServer)
}
func (suite *ServeFileTestSuite) TearDownSuite() {
@ -89,7 +90,7 @@ func (suite *ServeFileTestSuite) TearDownSuite() {
func (suite *ServeFileTestSuite) SetupTest() {
testrig.StandardDBSetup(suite.db)
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
@ -120,19 +121,19 @@ func (suite *ServeFileTestSuite) TestServeOriginalFileSuccessful() {
// but because we're calling the ServeFile function directly, we need to set them manually.
ctx.Params = gin.Params{
gin.Param{
Key: accountIDKey,
Key: fileserver.AccountIDKey,
Value: targetAttachment.AccountID,
},
gin.Param{
Key: mediaTypeKey,
Key: fileserver.MediaTypeKey,
Value: media.MediaAttachment,
},
gin.Param{
Key: mediaSizeKey,
Key: fileserver.MediaSizeKey,
Value: media.MediaOriginal,
},
gin.Param{
Key: fileNameKey,
Key: fileserver.FileNameKey,
Value: fmt.Sprintf("%s.jpeg", targetAttachment.ID),
},
}

View file

@ -32,9 +32,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/router"
)
const basePath = "/api/v1/media"
const BasePath = "/api/v1/media"
type mediaModule struct {
type MediaModule struct {
mediaHandler media.MediaHandler
config *config.Config
db db.DB
@ -44,7 +44,7 @@ type mediaModule struct {
// New returns a new auth module
func New(db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, config *config.Config, log *logrus.Logger) apimodule.ClientAPIModule {
return &mediaModule{
return &MediaModule{
mediaHandler: mediaHandler,
config: config,
db: db,
@ -54,12 +54,12 @@ func New(db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Co
}
// Route satisfies the RESTAPIModule interface
func (m *mediaModule) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, basePath, m.mediaCreatePOSTHandler)
func (m *MediaModule) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, BasePath, m.MediaCreatePOSTHandler)
return nil
}
func (m *mediaModule) CreateTables(db db.DB) error {
func (m *MediaModule) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},
}

View file

@ -33,7 +33,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *mediaModule) mediaCreatePOSTHandler(c *gin.Context) {
func (m *MediaModule) MediaCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "statusCreatePOSTHandler")
authed, err := oauth.MustAuth(c, true, true, true, true) // posting new media is serious business so we want *everything*
if err != nil {

View file

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package media
package test
import (
"bytes"
@ -36,6 +36,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
mediamodule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media"
mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
@ -63,7 +64,7 @@ type MediaCreateTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// item being tested
mediaModule *mediaModule
mediaModule *mediamodule.MediaModule
}
/*
@ -81,7 +82,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
// setup module being tested
suite.mediaModule = New(suite.db, suite.mediaHandler, suite.mastoConverter, suite.config, suite.log).(*mediaModule)
suite.mediaModule = mediamodule.New(suite.db, suite.mediaHandler, suite.mastoConverter, suite.config, suite.log).(*mediamodule.MediaModule)
}
func (suite *MediaCreateTestSuite) TearDownSuite() {
@ -92,7 +93,7 @@ func (suite *MediaCreateTestSuite) TearDownSuite() {
func (suite *MediaCreateTestSuite) SetupTest() {
testrig.StandardDBSetup(suite.db)
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
@ -129,18 +130,18 @@ func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful()
}
// create the request
buf, w, err := testrig.CreateMultipartFormData("file", "../../../testrig/media/test-jpeg.jpg", map[string]string{
buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
"description": "this is a test image -- a cool background from somewhere",
"focus": "-0.5,0.5",
})
if err != nil {
panic(err)
}
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", mediamodule.BasePath), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
// do the actual request
suite.mediaModule.mediaCreatePOSTHandler(ctx)
suite.mediaModule.MediaCreatePOSTHandler(ctx)
// check what's in storage *after* the request
storageKeysAfterRequest, err := suite.storage.ListKeys()

View file

@ -32,32 +32,30 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/distributor"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router"
)
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 = 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"
)
type statusModule struct {
type StatusModule struct {
config *config.Config
db db.DB
oauthServer oauth.Server
mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
distributor distributor.Distributor
@ -65,8 +63,8 @@ type statusModule struct {
}
// New returns a new account module
func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule {
return &statusModule{
func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule {
return &StatusModule{
config: config,
db: db,
mediaHandler: mediaHandler,
@ -77,13 +75,14 @@ func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler
}
// 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.MethodGet, basePathWithID, m.muxHandler)
func (m *StatusModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler)
r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler)
r.AttachHandler(http.MethodDelete, BasePathWithID, m.muxHandler)
return nil
}
func (m *statusModule) CreateTables(db db.DB) error {
func (m *StatusModule) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.User{},
&gtsmodel.Account{},
@ -111,14 +110,19 @@ func (m *statusModule) CreateTables(db db.DB) error {
return nil
}
func (m *statusModule) muxHandler(c *gin.Context) {
func (m *StatusModule) muxHandler(c *gin.Context) {
m.log.Debug("entering mux handler")
ru := c.Request.RequestURI
if strings.HasPrefix(ru, contextPath) {
// TODO
} else if strings.HasPrefix(ru, rebloggedPath) {
// TODO
} else {
m.statusGETHandler(c)
if c.Request.Method == http.MethodGet {
if strings.HasPrefix(ru, ContextPath) {
// TODO
} else if strings.HasPrefix(ru, RebloggedPath) {
// TODO
} else {
m.StatusGETHandler(c)
}
}
if c.Request.Method == http.MethodDelete {
m.StatusDELETEHandler(c)
}
}

View file

@ -53,7 +53,7 @@ type advancedVisibilityFlagsForm struct {
Likeable *bool `form:"likeable"`
}
func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
func (m *StatusModule) StatusCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "statusCreatePOSTHandler")
authed, err := oauth.MustAuth(c, true, true, true, true) // posting a status is serious business so we want *everything*
if err != nil {
@ -180,10 +180,8 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
Activity: newStatus,
}
/*
FROM THIS POINT ONWARDS WE ARE JUST CREATING THE FRONTEND REPRESENTATION OF THE STATUS TO RETURN TO THE SUBMITTER
*/
mastoStatus, err := m.mastoConverter.StatusToMasto(newStatus, authed.Account, authed.Account, nil, newStatus.GTSReplyToAccount, newStatus.GTSReplyToStatus)
// return the frontend representation of the new status to the submitter
mastoStatus, err := m.mastoConverter.StatusToMasto(newStatus, authed.Account, authed.Account, nil, newStatus.GTSReplyToAccount, nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@ -320,7 +318,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis gtsmodel.
return nil
}
func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (m *StatusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.InReplyToID == "" {
return nil
}
@ -369,7 +367,7 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
return nil
}
func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (m *StatusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.MediaIDs == nil {
return nil
}
@ -410,7 +408,7 @@ func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string
return nil
}
func (m *statusModule) parseMentions(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *StatusModule) parseMentions(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
menchies := []string{}
gtsMenchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), accountID, status.ID)
if err != nil {
@ -429,7 +427,7 @@ func (m *statusModule) parseMentions(form *advancedStatusCreateForm, accountID s
return nil
}
func (m *statusModule) parseTags(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *StatusModule) parseTags(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
tags := []string{}
gtsTags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), accountID, status.ID)
if err != nil {
@ -448,7 +446,7 @@ func (m *statusModule) parseTags(form *advancedStatusCreateForm, accountID strin
return nil
}
func (m *statusModule) parseEmojis(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *StatusModule) parseEmojis(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
emojis := []string{}
gtsEmojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), accountID, status.ID)
if err != nil {

View file

@ -0,0 +1,106 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
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) StatusDELETEHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "StatusDELETEHandler",
"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 delete 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 := &gtsmodel.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
}
if targetStatus.AccountID != authed.Account.ID {
l.Debug("status doesn't belong to requesting account")
c.JSON(http.StatusForbidden, gin.H{"error": "not allowed"})
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
}
var boostOfStatus *gtsmodel.Status
if targetStatus.BoostOfID != "" {
boostOfStatus = &gtsmodel.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, authed.Account, 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 err := m.db.DeleteByID(targetStatus.ID, targetStatus); err != nil {
l.Errorf("error deleting status from the database: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
m.distributor.FromClientAPI() <- distributor.FromClientAPI{
APObjectType: gtsmodel.ActivityStreamsNote,
APActivityType: gtsmodel.ActivityStreamsDelete,
Activity: targetStatus,
}
c.JSON(http.StatusOK, mastoStatus)
}

View file

@ -28,7 +28,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *statusModule) statusGETHandler(c *gin.Context) {
func (m *StatusModule) StatusGETHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "statusGETHandler",
"request_uri": c.Request.RequestURI,
@ -46,7 +46,7 @@ func (m *statusModule) statusGETHandler(c *gin.Context) {
requestingAccount = authed.Account
}
targetStatusID := c.Param(idKey)
targetStatusID := c.Param(IDKey)
if targetStatusID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
return

View file

@ -31,6 +31,7 @@ import (
"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"
@ -64,7 +65,7 @@ type StatusCreateTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// module being tested
statusModule *statusModule
statusModule *status.StatusModule
}
/*
@ -84,7 +85,7 @@ func (suite *StatusCreateTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = New(suite.config, suite.db, suite.oauthServer, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*statusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
}
func (suite *StatusCreateTestSuite) TearDownSuite() {
@ -129,7 +130,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
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", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {"this is a brand new status! #helloworld"},
"spoiler_text": {"hello hello"},
@ -139,7 +140,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
"replyable": {"false"},
"federated": {"false"},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
suite.statusModule.StatusCreatePOSTHandler(ctx)
// check response
@ -183,11 +184,11 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
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", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {"here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: "},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
suite.statusModule.StatusCreatePOSTHandler(ctx)
suite.EqualValues(http.StatusOK, recorder.Code)
@ -224,13 +225,13 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
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", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {"this is a reply to a status that doesn't exist"},
"spoiler_text": {"don't open cuz it won't work"},
"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
suite.statusModule.StatusCreatePOSTHandler(ctx)
// check response
@ -255,12 +256,12 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
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", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {fmt.Sprintf("hello @%s this reply should work!", testrig.NewTestAccounts()["local_account_2"].Username)},
"in_reply_to_id": {testrig.NewTestStatuses()["local_account_2_status_1"].ID},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
suite.statusModule.StatusCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusOK, recorder.Code)
@ -295,12 +296,12 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
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", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", status.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {"here's an image attachment"},
"media_ids": {"7a3b9f77-ab30-461e-bdd8-e64bd1db3008"},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
suite.statusModule.StatusCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusOK, recorder.Code)

View file

@ -23,6 +23,7 @@ import (
"github.com/sirupsen/logrus"
"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"
@ -55,7 +56,7 @@ type StatusGetTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// module being tested
statusModule *statusModule
statusModule *status.StatusModule
}
/*
@ -75,7 +76,7 @@ func (suite *StatusGetTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = New(suite.config, suite.db, suite.oauthServer, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*statusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
}
func (suite *StatusGetTestSuite) TearDownSuite() {

View file

@ -60,12 +60,6 @@ func newPostgresService(ctx context.Context, c *config.Config, log *logrus.Entry
}
log.Debugf("using pg options: %+v", opts)
readyChan := make(chan interface{})
opts.OnConnect = func(ctx context.Context, c *pg.Conn) error {
close(readyChan)
return nil
}
// create a connection
pgCtx, cancel := context.WithCancel(ctx)
conn := pg.Connect(opts).WithContext(pgCtx)
@ -80,8 +74,7 @@ func newPostgresService(ctx context.Context, c *config.Config, log *logrus.Entry
})
}
// actually *begin* the connection so that we can tell if the db is there
// and listening, and also trigger the opts.OnConnect function passed in above
// actually *begin* the connection so that we can tell if the db is there and listening
if err := conn.Ping(ctx); err != nil {
cancel()
return nil, fmt.Errorf("db connection error: %s", err)
@ -95,16 +88,6 @@ func newPostgresService(ctx context.Context, c *config.Config, log *logrus.Entry
}
log.Infof("connected to postgres version: %s", version)
// make sure the opts.OnConnect function has been triggered
// and closed the ready channel
select {
case <-readyChan:
log.Infof("postgres connection ready")
case <-time.After(5 * time.Second):
cancel()
return nil, errors.New("db connection timeout")
}
ps := &postgresService{
config: c,
conn: conn,
@ -585,15 +568,18 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
// If requesting account is nil, that means whoever requested the status didn't auth, or their auth failed.
// In this case, we can still serve the status if it's public, otherwise we definitely shouldn't.
if requestingAccount == nil {
if targetStatus.Visibility == gtsmodel.VisibilityPublic {
return true, nil
}
l.Debug("requesting account is nil but the target status isn't public")
return false, nil
}
// if requesting account is suspended then don't show the status -- although they probably shouldn't have gotten
// this far (ie., been authed) in the first place: this is just for safety.
if !requestingAccount.SuspendedAt.IsZero() {
l.Debug("requesting account is suspended")
return false, nil
}
@ -603,24 +589,33 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
if err := ps.conn.Model(requestingUser).Where("account_id = ?", requestingAccount.ID).Select(); err != nil {
// if the requesting account is local but doesn't have a corresponding user in the db this is a problem
if err == pg.ErrNoRows {
l.Debug("requesting account is local but there's no corresponding user")
return false, nil
} else {
l.Debugf("requesting account is local but there was an error getting the corresponding user: %s", err)
return false, err
}
}
// okay, user exists, so make sure it has full privileges/is confirmed/approved
if requestingUser.Disabled || !requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() {
l.Debug("requesting account is local but corresponding user is either disabled, not approved, or not confirmed")
return false, nil
}
}
// if the target status belongs to the requesting account, they should always be able to view it at this point
if targetStatus.AccountID == requestingAccount.ID {
return true, nil
}
// At this point we have a populated targetAccount, targetStatus, and requestingAccount, so we can check for blocks and whathaveyou
// First check if a block exists directly between the target account (which authored the status) and the requesting account.
if blocked, err := ps.Blocked(targetAccount.ID, requestingAccount.ID); err != nil {
// something went wrong figuring out if the accounts have a block
l.Debug("something went wrong figuring out if the accounts have a block: %s", err)
return false, err
} else if blocked {
// don't allow the status to be viewed if a block exists in *either* direction between these two accounts, no creepy stalking please
l.Debug("a block exists between requesting account and target account")
return false, nil
}

View file

@ -83,7 +83,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log)
fileServerModule := fileserver.New(c, dbService, storageBackend, log)
adminModule := admin.New(c, dbService, mediaHandler, mastoConverter, log)
statusModule := status.New(c, dbService, oauthServer, mediaHandler, mastoConverter, distributor, log)
statusModule := status.New(c, dbService, mediaHandler, mastoConverter, distributor, log)
securityModule := security.New(c, log)
apiModules := []apimodule.ClientAPIModule{

View file

@ -345,40 +345,53 @@ func (c *converter) StatusToMasto(
return nil, fmt.Errorf("error counting faves: %s", err)
}
faved, err := c.db.StatusFavedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err)
}
var faved bool
var reblogged bool
var bookmarked bool
var pinned bool
var muted bool
reblogged, err := c.db.StatusRebloggedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err)
}
// requestingAccount will be nil for public requests without auth
// But if it's not nil, we can also get information about the requestingAccount's interaction with this status
if requestingAccount != nil {
faved, err = c.db.StatusFavedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err)
}
muted, err := c.db.StatusMutedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err)
}
reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err)
}
bookmarked, err := c.db.StatusBookmarkedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err)
}
muted, err = c.db.StatusMutedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err)
}
pinned, err := c.db.StatusPinnedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err)
bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err)
}
pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID)
if err != nil {
return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err)
}
}
var mastoRebloggedStatus *mastotypes.Status // TODO
application := &gtsmodel.Application{}
if err := c.db.GetByID(s.CreatedWithApplicationID, application); err != nil {
return nil, fmt.Errorf("error fetching application used to create status: %s", err)
}
mastoApplication, err := c.AppToMastoPublic(application)
if err != nil {
return nil, fmt.Errorf("error parsing application used to create status: %s", err)
var mastoApplication *mastotypes.Application
if s.CreatedWithApplicationID != "" {
gtsApplication := &gtsmodel.Application{}
if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil {
return nil, fmt.Errorf("error fetching application used to create status: %s", err)
}
mastoApplication, err = c.AppToMastoPublic(gtsApplication)
if err != nil {
return nil, fmt.Errorf("error parsing application used to create status: %s", err)
}
}
mastoTargetAccount, err := c.AccountToMastoPublic(targetAccount)

125
testrig/actions.go Normal file
View file

@ -0,0 +1,125 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
package testrig
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/action"
"github.com/superseriousbusiness/gotosocial/internal/apimodule"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/admin"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/app"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/auth"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/fileserver"
mediaModule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/security"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/status"
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial"
)
// Run creates and starts a gotosocial testrig server
var Run action.GTSAction = func(ctx context.Context, _ *config.Config, log *logrus.Logger) error {
dbService := NewTestDB()
router := NewTestRouter()
storageBackend := NewTestStorage()
mediaHandler := NewTestMediaHandler(dbService, storageBackend)
oauthServer := NewTestOauthServer(dbService)
distributor := NewTestDistributor()
if err := distributor.Start(); err != nil {
return fmt.Errorf("error starting distributor: %s", err)
}
mastoConverter := NewTestMastoConverter(dbService)
c := NewTestConfig()
StandardDBSetup(dbService)
StandardStorageSetup(storageBackend, "./testrig/media")
// build client api modules
authModule := auth.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, mastoConverter, log)
appsModule := app.New(oauthServer, dbService, mastoConverter, log)
mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log)
fileServerModule := fileserver.New(c, dbService, storageBackend, log)
adminModule := admin.New(c, dbService, mediaHandler, mastoConverter, log)
statusModule := status.New(c, dbService, mediaHandler, mastoConverter, distributor, log)
securityModule := security.New(c, log)
apiModules := []apimodule.ClientAPIModule{
// modules with middleware go first
securityModule,
authModule,
// now everything else
accountModule,
appsModule,
mm,
fileServerModule,
adminModule,
statusModule,
}
for _, m := range apiModules {
if err := m.Route(router); err != nil {
return fmt.Errorf("routing error: %s", err)
}
if err := m.CreateTables(dbService); err != nil {
return fmt.Errorf("table creation error: %s", err)
}
}
// if err := dbService.CreateInstanceAccount(); err != nil {
// return fmt.Errorf("error creating instance account: %s", err)
// }
gts, err := gotosocial.New(dbService, &cache.MockCache{}, router, federation.New(dbService, log), c)
if err != nil {
return fmt.Errorf("error creating gotosocial service: %s", err)
}
if err := gts.Start(ctx); err != nil {
return fmt.Errorf("error starting gotosocial service: %s", err)
}
// catch shutdown signals from the operating system
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
sig := <-sigs
log.Infof("received signal %s, shutting down", sig)
StandardDBTeardown(dbService)
StandardStorageTeardown(storageBackend)
// close down all running services in order
if err := gts.Stop(ctx); err != nil {
return fmt.Errorf("error closing gotosocial service: %s", err)
}
log.Info("done! exiting...")
return nil
}

29
testrig/router.go Normal file
View file

@ -0,0 +1,29 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
package testrig
import "github.com/superseriousbusiness/gotosocial/internal/router"
func NewTestRouter() router.Router {
r, err := router.New(NewTestConfig(), NewTestLog())
if err != nil {
panic(err)
}
return r
}

View file

@ -48,12 +48,24 @@ func NewTestTokens() map[string]*oauth.Token {
// NewTestClients returns a map of Clients keyed according to which account they are used by.
func NewTestClients() map[string]*oauth.Client {
clients := map[string]*oauth.Client{
"admin_account": {
ID: "1c5cefc8-f0c9-4307-8506-ca6e3888675e",
Secret: "dda8e835-2c9c-4bd2-9b8b-77c2e26d7a7a",
Domain: "http://localhost:8080",
UserID: "0fb02eae-2214-473f-9667-0a43f22d75ff", // admin_account
},
"local_account_1": {
ID: "73b48d42-029d-4487-80fc-329a5cf67869",
Secret: "c3724c74-dc3b-41b2-a108-0ea3d8399830",
Domain: "http://localhost:8080",
UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b", // local_account_1
},
"local_account_2": {
ID: "a4f6a2ea-a32b-4600-8853-72fc4ad98a1f",
Secret: "8f5603a5-c721-46cd-8f1b-2e368f51379f",
Domain: "http://localhost:8080",
UserID: "d120bd97-866f-4a05-9690-a1294b9934c3", // local_account_2
},
}
return clients
}
@ -61,6 +73,16 @@ func NewTestClients() map[string]*oauth.Client {
// NewTestApplications returns a map of applications keyed to which number application they are.
func NewTestApplications() map[string]*gtsmodel.Application {
apps := map[string]*gtsmodel.Application{
"admin_account": {
ID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c",
Name: "superseriousbusiness",
Website: "https://superserious.business",
RedirectURI: "http://localhost:8080",
ClientID: "1c5cefc8-f0c9-4307-8506-ca6e3888675e", // admin client
ClientSecret: "dda8e835-2c9c-4bd2-9b8b-77c2e26d7a7a", // admin client
Scopes: "read write follow push",
VapidKey: "76ae0095-8a10-438f-9f49-522d1985b190",
},
"application_1": {
ID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
Name: "really cool gts application",
@ -71,6 +93,16 @@ func NewTestApplications() map[string]*gtsmodel.Application {
Scopes: "read write follow push",
VapidKey: "4738dfd7-ca73-4aa6-9aa9-80e946b7db36",
},
"application_2": {
ID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
Name: "kindaweird",
Website: "https://kindaweird.app",
RedirectURI: "http://localhost:8080",
ClientID: "a4f6a2ea-a32b-4600-8853-72fc4ad98a1f", // client_2
ClientSecret: "8f5603a5-c721-46cd-8f1b-2e368f51379f", // client_2
Scopes: "read write follow push",
VapidKey: "c040a5fc-e1e2-4859-bbea-0a3efbca1c4b",
},
}
return apps
}
@ -128,7 +160,7 @@ func NewTestUsers() map[string]*gtsmodel.User {
CreatedByApplicationID: "",
LastEmailedAt: time.Now().Add(-30 * time.Minute),
ConfirmationToken: "",
ConfirmedAt: time.Time{},
ConfirmedAt: time.Now().Add(-72 * time.Hour),
ConfirmationSentAt: time.Time{},
UnconfirmedEmail: "",
Moderator: true,
@ -689,13 +721,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
CreatedAt: time.Now().Add(-71 * time.Hour),
UpdatedAt: time.Now().Add(-71 * time.Hour),
Local: true,
AccountID: "0fb02eae-2214-473f-9667-0a43f22d75ff",
AccountID: "8020dbb4-1e7b-4d99-a872-4cf94e64210f",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: false,
Language: "en",
CreatedWithApplicationID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
@ -712,13 +745,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
CreatedAt: time.Now().Add(-70 * time.Hour),
UpdatedAt: time.Now().Add(-70 * time.Hour),
Local: true,
AccountID: "0fb02eae-2214-473f-9667-0a43f22d75ff",
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: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
@ -742,6 +776,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
@ -765,6 +800,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityUnlocked,
Sensitive: false,
Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: false,
Boostable: true,
@ -788,6 +824,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityMutualsOnly,
Sensitive: false,
Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: false,
@ -812,6 +849,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityMutualsOnly,
Sensitive: false,
Language: "en",
CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
@ -835,6 +873,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
@ -858,6 +897,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,