mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 12:02:25 -05:00
delete statuses
This commit is contained in:
parent
29c17cce12
commit
ddfb9aae65
20 changed files with 475 additions and 147 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
|
|
@ -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{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
106
internal/apimodule/status/statusdelete.go
Normal file
106
internal/apimodule/status/statusdelete.go
Normal 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 := >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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
125
testrig/actions.go
Normal file
125
testrig/actions.go
Normal 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
29
testrig/router.go
Normal 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue