diff --git a/PROGRESS.md b/PROGRESS.md
index 7010da5ec..013ad080a 100644
--- a/PROGRESS.md
+++ b/PROGRESS.md
@@ -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)
diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go
index 43d2d2ac7..337e7b785 100644
--- a/cmd/gotosocial/main.go
+++ b/cmd/gotosocial/main.go
@@ -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)
+ },
+ },
+ },
+ },
},
}
diff --git a/internal/apimodule/fileserver/fileserver.go b/internal/apimodule/fileserver/fileserver.go
index bc595734f..25f3be864 100644
--- a/internal/apimodule/fileserver/fileserver.go
+++ b/internal/apimodule/fileserver/fileserver.go
@@ -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{}{
>smodel.MediaAttachment{},
}
diff --git a/internal/apimodule/fileserver/servefile.go b/internal/apimodule/fileserver/servefile.go
index 66bc18542..573ad0694 100644
--- a/internal/apimodule/fileserver/servefile.go
+++ b/internal/apimodule/fileserver/servefile.go
@@ -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,
diff --git a/internal/apimodule/fileserver/servefile_test.go b/internal/apimodule/fileserver/test/servefile_test.go
similarity index 91%
rename from internal/apimodule/fileserver/servefile_test.go
rename to internal/apimodule/fileserver/test/servefile_test.go
index 9b744a8f6..8af2b40b3 100644
--- a/internal/apimodule/fileserver/servefile_test.go
+++ b/internal/apimodule/fileserver/test/servefile_test.go
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-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),
},
}
diff --git a/internal/apimodule/media/media.go b/internal/apimodule/media/media.go
index 44848a3f2..c8d3d7425 100644
--- a/internal/apimodule/media/media.go
+++ b/internal/apimodule/media/media.go
@@ -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{}{
>smodel.MediaAttachment{},
}
diff --git a/internal/apimodule/media/mediacreate.go b/internal/apimodule/media/mediacreate.go
index f082e9bf4..06b6d5be6 100644
--- a/internal/apimodule/media/mediacreate.go
+++ b/internal/apimodule/media/mediacreate.go
@@ -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 {
diff --git a/internal/apimodule/media/mediacreate_test.go b/internal/apimodule/media/test/mediacreate_test.go
similarity index 91%
rename from internal/apimodule/media/mediacreate_test.go
rename to internal/apimodule/media/test/mediacreate_test.go
index ab77a9a3d..33215aea9 100644
--- a/internal/apimodule/media/mediacreate_test.go
+++ b/internal/apimodule/media/test/mediacreate_test.go
@@ -16,7 +16,7 @@
along with this program. If not, see .
*/
-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()
diff --git a/internal/apimodule/status/status.go b/internal/apimodule/status/status.go
index cb4dab134..37e6ee268 100644
--- a/internal/apimodule/status/status.go
+++ b/internal/apimodule/status/status.go
@@ -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{}{
>smodel.User{},
>smodel.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)
}
}
diff --git a/internal/apimodule/status/statuscreate.go b/internal/apimodule/status/statuscreate.go
index 0f5b00200..ce1cc6da7 100644
--- a/internal/apimodule/status/statuscreate.go
+++ b/internal/apimodule/status/statuscreate.go
@@ -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 {
diff --git a/internal/apimodule/status/statusdelete.go b/internal/apimodule/status/statusdelete.go
new file mode 100644
index 000000000..85b10ff16
--- /dev/null
+++ b/internal/apimodule/status/statusdelete.go
@@ -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 .
+*/
+
+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 := >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
+ }
+
+ 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 = >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, 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)
+}
diff --git a/internal/apimodule/status/statusget.go b/internal/apimodule/status/statusget.go
index 4d1e3abbb..ed2e89159 100644
--- a/internal/apimodule/status/statusget.go
+++ b/internal/apimodule/status/statusget.go
@@ -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
diff --git a/internal/apimodule/status/statuscreate_test.go b/internal/apimodule/status/test/statuscreate_test.go
similarity index 93%
rename from internal/apimodule/status/statuscreate_test.go
rename to internal/apimodule/status/test/statuscreate_test.go
index 8439b694a..6c5aa6b7d 100644
--- a/internal/apimodule/status/statuscreate_test.go
+++ b/internal/apimodule/status/test/statuscreate_test.go
@@ -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)
diff --git a/internal/apimodule/status/statusget_test.go b/internal/apimodule/status/test/statusget_test.go
similarity index 95%
rename from internal/apimodule/status/statusget_test.go
rename to internal/apimodule/status/test/statusget_test.go
index 4e25dfbcc..2c2e98acd 100644
--- a/internal/apimodule/status/statusget_test.go
+++ b/internal/apimodule/status/test/statusget_test.go
@@ -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() {
diff --git a/internal/db/pg.go b/internal/db/pg.go
index 4098a640b..f2caa813d 100644
--- a/internal/db/pg.go
+++ b/internal/db/pg.go
@@ -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
}
diff --git a/internal/gotosocial/actions.go b/internal/gotosocial/actions.go
index 76557ca3d..2f90858b4 100644
--- a/internal/gotosocial/actions.go
+++ b/internal/gotosocial/actions.go
@@ -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{
diff --git a/internal/mastotypes/converter.go b/internal/mastotypes/converter.go
index c6516422c..46e7796e7 100644
--- a/internal/mastotypes/converter.go
+++ b/internal/mastotypes/converter.go
@@ -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 := >smodel.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 := >smodel.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)
diff --git a/testrig/actions.go b/testrig/actions.go
new file mode 100644
index 000000000..1caa18581
--- /dev/null
+++ b/testrig/actions.go
@@ -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 .
+*/
+
+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
+}
diff --git a/testrig/router.go b/testrig/router.go
new file mode 100644
index 000000000..abd168724
--- /dev/null
+++ b/testrig/router.go
@@ -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 .
+*/
+
+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
+}
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index cbc21237b..829c40455 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -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: >smodel.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: >smodel.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: >smodel.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: >smodel.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: >smodel.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: >smodel.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: >smodel.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: >smodel.VisibilityAdvanced{
Federated: true,
Boostable: true,