mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 23: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 |   * [ ] Statuses | ||||||
|     * [x] /api/v1/statuses POST                             (Create a new status) |     * [x] /api/v1/statuses POST                             (Create a new status) | ||||||
|     * [x] /api/v1/statuses/:id GET                          (View an existing 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/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/reblogged_by GET             (See who has reblogged a status) | ||||||
|     * [ ] /api/v1/statuses/:id/favourited_by GET            (See who has faved a status) |     * [ ] /api/v1/statuses/:id/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/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gotosocial" | 	"github.com/superseriousbusiness/gotosocial/internal/gotosocial" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/log" | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
| 
 | 
 | ||||||
| 	"github.com/urfave/cli/v2" | 	"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 ( | const ( | ||||||
| 	accountIDKey = "account_id" | 	AccountIDKey = "account_id" | ||||||
| 	mediaTypeKey = "media_type" | 	MediaTypeKey = "media_type" | ||||||
| 	mediaSizeKey = "media_size" | 	MediaSizeKey = "media_size" | ||||||
| 	fileNameKey  = "file_name" | 	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. | // 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 | 	config      *config.Config | ||||||
| 	db          db.DB | 	db          db.DB | ||||||
| 	storage     storage.Storage | 	storage     storage.Storage | ||||||
|  | @ -52,7 +52,7 @@ type fileServer struct { | ||||||
| 
 | 
 | ||||||
| // New returns a new fileServer module | // New returns a new fileServer module | ||||||
| func New(config *config.Config, db db.DB, storage storage.Storage, log *logrus.Logger) apimodule.ClientAPIModule { | func New(config *config.Config, db db.DB, storage storage.Storage, log *logrus.Logger) apimodule.ClientAPIModule { | ||||||
| 	return &fileServer{ | 	return &FileServer{ | ||||||
| 		config:      config, | 		config:      config, | ||||||
| 		db:          db, | 		db:          db, | ||||||
| 		storage:     storage, | 		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 | // Route satisfies the RESTAPIModule interface | ||||||
| func (m *fileServer) Route(s router.Router) error { | 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) | 	s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey), m.ServeFile) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *fileServer) CreateTables(db db.DB) error { | func (m *FileServer) CreateTables(db db.DB) error { | ||||||
| 	models := []interface{}{ | 	models := []interface{}{ | ||||||
| 		>smodel.MediaAttachment{}, | 		>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". | // 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. | // 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{ | 	l := m.log.WithFields(logrus.Fields{ | ||||||
| 		"func":        "ServeFile", | 		"func":        "ServeFile", | ||||||
| 		"request_uri": c.Request.RequestURI, | 		"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: | 	// 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]" | 	// "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. | 	// "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 == "" { | 	if accountID == "" { | ||||||
| 		l.Debug("missing accountID from request") | 		l.Debug("missing accountID from request") | ||||||
| 		c.String(http.StatusNotFound, "404 page not found") | 		c.String(http.StatusNotFound, "404 page not found") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mediaType := c.Param(mediaTypeKey) | 	mediaType := c.Param(MediaTypeKey) | ||||||
| 	if mediaType == "" { | 	if mediaType == "" { | ||||||
| 		l.Debug("missing mediaType from request") | 		l.Debug("missing mediaType from request") | ||||||
| 		c.String(http.StatusNotFound, "404 page not found") | 		c.String(http.StatusNotFound, "404 page not found") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	mediaSize := c.Param(mediaSizeKey) | 	mediaSize := c.Param(MediaSizeKey) | ||||||
| 	if mediaSize == "" { | 	if mediaSize == "" { | ||||||
| 		l.Debug("missing mediaSize from request") | 		l.Debug("missing mediaSize from request") | ||||||
| 		c.String(http.StatusNotFound, "404 page not found") | 		c.String(http.StatusNotFound, "404 page not found") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fileName := c.Param(fileNameKey) | 	fileName := c.Param(FileNameKey) | ||||||
| 	if fileName == "" { | 	if fileName == "" { | ||||||
| 		l.Debug("missing fileName from request") | 		l.Debug("missing fileName from request") | ||||||
| 		c.String(http.StatusNotFound, "404 page not found") | 		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") | 	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{ | 	l := m.log.WithFields(logrus.Fields{ | ||||||
| 		"func":        "serveAttachment", | 		"func":        "serveAttachment", | ||||||
| 		"request_uri": c.Request.RequestURI, | 		"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{}) | 	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{ | 	l := m.log.WithFields(logrus.Fields{ | ||||||
| 		"func":        "serveEmoji", | 		"func":        "serveEmoji", | ||||||
| 		"request_uri": c.Request.RequestURI, | 		"request_uri": c.Request.RequestURI, | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| package fileserver | package test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | @ -30,6 +30,7 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/apimodule/fileserver" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | @ -60,7 +61,7 @@ type ServeFileTestSuite struct { | ||||||
| 	testAttachments  map[string]*gtsmodel.MediaAttachment | 	testAttachments  map[string]*gtsmodel.MediaAttachment | ||||||
| 
 | 
 | ||||||
| 	// item being tested | 	// item being tested | ||||||
| 	fileServer *fileServer | 	fileServer *fileserver.FileServer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -78,7 +79,7 @@ func (suite *ServeFileTestSuite) SetupSuite() { | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// 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() { | func (suite *ServeFileTestSuite) TearDownSuite() { | ||||||
|  | @ -89,7 +90,7 @@ func (suite *ServeFileTestSuite) TearDownSuite() { | ||||||
| 
 | 
 | ||||||
| func (suite *ServeFileTestSuite) SetupTest() { | func (suite *ServeFileTestSuite) SetupTest() { | ||||||
| 	testrig.StandardDBSetup(suite.db) | 	testrig.StandardDBSetup(suite.db) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
| 	suite.testTokens = testrig.NewTestTokens() | 	suite.testTokens = testrig.NewTestTokens() | ||||||
| 	suite.testClients = testrig.NewTestClients() | 	suite.testClients = testrig.NewTestClients() | ||||||
| 	suite.testApplications = testrig.NewTestApplications() | 	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. | 	// but because we're calling the ServeFile function directly, we need to set them manually. | ||||||
| 	ctx.Params = gin.Params{ | 	ctx.Params = gin.Params{ | ||||||
| 		gin.Param{ | 		gin.Param{ | ||||||
| 			Key:   accountIDKey, | 			Key:   fileserver.AccountIDKey, | ||||||
| 			Value: targetAttachment.AccountID, | 			Value: targetAttachment.AccountID, | ||||||
| 		}, | 		}, | ||||||
| 		gin.Param{ | 		gin.Param{ | ||||||
| 			Key:   mediaTypeKey, | 			Key:   fileserver.MediaTypeKey, | ||||||
| 			Value: media.MediaAttachment, | 			Value: media.MediaAttachment, | ||||||
| 		}, | 		}, | ||||||
| 		gin.Param{ | 		gin.Param{ | ||||||
| 			Key:   mediaSizeKey, | 			Key:   fileserver.MediaSizeKey, | ||||||
| 			Value: media.MediaOriginal, | 			Value: media.MediaOriginal, | ||||||
| 		}, | 		}, | ||||||
| 		gin.Param{ | 		gin.Param{ | ||||||
| 			Key:   fileNameKey, | 			Key:   fileserver.FileNameKey, | ||||||
| 			Value: fmt.Sprintf("%s.jpeg", targetAttachment.ID), | 			Value: fmt.Sprintf("%s.jpeg", targetAttachment.ID), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | @ -32,9 +32,9 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | 	"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 | 	mediaHandler   media.MediaHandler | ||||||
| 	config         *config.Config | 	config         *config.Config | ||||||
| 	db             db.DB | 	db             db.DB | ||||||
|  | @ -44,7 +44,7 @@ type mediaModule struct { | ||||||
| 
 | 
 | ||||||
| // New returns a new auth module | // 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 { | 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, | 		mediaHandler:   mediaHandler, | ||||||
| 		config:         config, | 		config:         config, | ||||||
| 		db:             db, | 		db:             db, | ||||||
|  | @ -54,12 +54,12 @@ func New(db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Co | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Route satisfies the RESTAPIModule interface | // Route satisfies the RESTAPIModule interface | ||||||
| func (m *mediaModule) Route(s router.Router) error { | func (m *MediaModule) Route(s router.Router) error { | ||||||
| 	s.AttachHandler(http.MethodPost, basePath, m.mediaCreatePOSTHandler) | 	s.AttachHandler(http.MethodPost, BasePath, m.MediaCreatePOSTHandler) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *mediaModule) CreateTables(db db.DB) error { | func (m *MediaModule) CreateTables(db db.DB) error { | ||||||
| 	models := []interface{}{ | 	models := []interface{}{ | ||||||
| 		>smodel.MediaAttachment{}, | 		>smodel.MediaAttachment{}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"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") | 	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* | 	authed, err := oauth.MustAuth(c, true, true, true, true) // posting new media is serious business so we want *everything* | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| package media | package test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | @ -36,6 +36,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/mastotypes" | 	"github.com/superseriousbusiness/gotosocial/internal/mastotypes" | ||||||
|  | 	mediamodule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media" | ||||||
| 	mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" | 	mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
|  | @ -63,7 +64,7 @@ type MediaCreateTestSuite struct { | ||||||
| 	testAttachments  map[string]*gtsmodel.MediaAttachment | 	testAttachments  map[string]*gtsmodel.MediaAttachment | ||||||
| 
 | 
 | ||||||
| 	// item being tested | 	// item being tested | ||||||
| 	mediaModule *mediaModule | 	mediaModule *mediamodule.MediaModule | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -81,7 +82,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() { | ||||||
| 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | 	suite.oauthServer = testrig.NewTestOauthServer(suite.db) | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// 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() { | func (suite *MediaCreateTestSuite) TearDownSuite() { | ||||||
|  | @ -92,7 +93,7 @@ func (suite *MediaCreateTestSuite) TearDownSuite() { | ||||||
| 
 | 
 | ||||||
| func (suite *MediaCreateTestSuite) SetupTest() { | func (suite *MediaCreateTestSuite) SetupTest() { | ||||||
| 	testrig.StandardDBSetup(suite.db) | 	testrig.StandardDBSetup(suite.db) | ||||||
| 	testrig.StandardStorageSetup(suite.storage, "../../../testrig/media") | 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media") | ||||||
| 	suite.testTokens = testrig.NewTestTokens() | 	suite.testTokens = testrig.NewTestTokens() | ||||||
| 	suite.testClients = testrig.NewTestClients() | 	suite.testClients = testrig.NewTestClients() | ||||||
| 	suite.testApplications = testrig.NewTestApplications() | 	suite.testApplications = testrig.NewTestApplications() | ||||||
|  | @ -129,18 +130,18 @@ func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful() | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// create the request | 	// 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", | 		"description": "this is a test image -- a cool background from somewhere", | ||||||
| 		"focus":       "-0.5,0.5", | 		"focus":       "-0.5,0.5", | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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()) | 	ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) | ||||||
| 
 | 
 | ||||||
| 	// do the actual request | 	// do the actual request | ||||||
| 	suite.mediaModule.mediaCreatePOSTHandler(ctx) | 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | ||||||
| 
 | 
 | ||||||
| 	// check what's in storage *after* the request | 	// check what's in storage *after* the request | ||||||
| 	storageKeysAfterRequest, err := suite.storage.ListKeys() | 	storageKeysAfterRequest, err := suite.storage.ListKeys() | ||||||
|  | @ -32,32 +32,30 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/distributor" | 	"github.com/superseriousbusiness/gotosocial/internal/distributor" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/mastotypes" | 	"github.com/superseriousbusiness/gotosocial/internal/mastotypes" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | 	"github.com/superseriousbusiness/gotosocial/internal/router" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	idKey          = "id" | 	IDKey          = "id" | ||||||
| 	basePath       = "/api/v1/statuses" | 	BasePath       = "/api/v1/statuses" | ||||||
| 	basePathWithID = basePath + "/:" + idKey | 	BasePathWithID = BasePath + "/:" + IDKey | ||||||
| 	contextPath    = basePath + "/context" | 	ContextPath    = BasePath + "/context" | ||||||
| 	rebloggedPath  = basePath + "/reblogged_by" | 	RebloggedPath  = BasePath + "/reblogged_by" | ||||||
| 	favouritedPath = basePath + "/favourited_by" | 	FavouritedPath = BasePath + "/favourited_by" | ||||||
| 	favouritePath  = basePath + "/favourite" | 	FavouritePath  = BasePath + "/favourite" | ||||||
| 	reblogPath     = basePath + "/reblog" | 	ReblogPath     = BasePath + "/reblog" | ||||||
| 	unreblogPath   = basePath + "/unreblog" | 	UnreblogPath   = BasePath + "/unreblog" | ||||||
| 	bookmarkPath   = basePath + "/bookmark" | 	BookmarkPath   = BasePath + "/bookmark" | ||||||
| 	unbookmarkPath = basePath + "/unbookmark" | 	UnbookmarkPath = BasePath + "/unbookmark" | ||||||
| 	mutePath       = basePath + "/mute" | 	MutePath       = BasePath + "/mute" | ||||||
| 	unmutePath     = basePath + "/unmute" | 	UnmutePath     = BasePath + "/unmute" | ||||||
| 	pinPath        = basePath + "/pin" | 	PinPath        = BasePath + "/pin" | ||||||
| 	unpinPath      = basePath + "/unpin" | 	UnpinPath      = BasePath + "/unpin" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type statusModule struct { | type StatusModule struct { | ||||||
| 	config         *config.Config | 	config         *config.Config | ||||||
| 	db             db.DB | 	db             db.DB | ||||||
| 	oauthServer    oauth.Server |  | ||||||
| 	mediaHandler   media.MediaHandler | 	mediaHandler   media.MediaHandler | ||||||
| 	mastoConverter mastotypes.Converter | 	mastoConverter mastotypes.Converter | ||||||
| 	distributor    distributor.Distributor | 	distributor    distributor.Distributor | ||||||
|  | @ -65,8 +63,8 @@ type statusModule struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a new account module | // 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 { | func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule { | ||||||
| 	return &statusModule{ | 	return &StatusModule{ | ||||||
| 		config:         config, | 		config:         config, | ||||||
| 		db:             db, | 		db:             db, | ||||||
| 		mediaHandler:   mediaHandler, | 		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 | // Route attaches all routes from this module to the given router | ||||||
| func (m *statusModule) Route(r router.Router) error { | func (m *StatusModule) Route(r router.Router) error { | ||||||
| 	r.AttachHandler(http.MethodPost, basePath, m.statusCreatePOSTHandler) | 	r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler) | ||||||
| 	r.AttachHandler(http.MethodGet, basePathWithID, m.muxHandler) | 	r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler) | ||||||
|  | 	r.AttachHandler(http.MethodDelete, BasePathWithID, m.muxHandler) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *statusModule) CreateTables(db db.DB) error { | func (m *StatusModule) CreateTables(db db.DB) error { | ||||||
| 	models := []interface{}{ | 	models := []interface{}{ | ||||||
| 		>smodel.User{}, | 		>smodel.User{}, | ||||||
| 		>smodel.Account{}, | 		>smodel.Account{}, | ||||||
|  | @ -111,14 +110,19 @@ func (m *statusModule) CreateTables(db db.DB) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *statusModule) muxHandler(c *gin.Context) { | func (m *StatusModule) muxHandler(c *gin.Context) { | ||||||
| 	m.log.Debug("entering mux handler") | 	m.log.Debug("entering mux handler") | ||||||
| 	ru := c.Request.RequestURI | 	ru := c.Request.RequestURI | ||||||
| 	if strings.HasPrefix(ru, contextPath) { | 	if c.Request.Method == http.MethodGet { | ||||||
| 		// TODO | 		if strings.HasPrefix(ru, ContextPath) { | ||||||
| 	} else if strings.HasPrefix(ru, rebloggedPath) { | 			// TODO | ||||||
| 		// TODO | 		} else if strings.HasPrefix(ru, RebloggedPath) { | ||||||
| 	} else { | 			// TODO | ||||||
| 		m.statusGETHandler(c) | 		} else { | ||||||
|  | 			m.StatusGETHandler(c) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if c.Request.Method == http.MethodDelete { | ||||||
|  | 		m.StatusDELETEHandler(c) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ type advancedVisibilityFlagsForm struct { | ||||||
| 	Likeable *bool `form:"likeable"` | 	Likeable *bool `form:"likeable"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | func (m *StatusModule) StatusCreatePOSTHandler(c *gin.Context) { | ||||||
| 	l := m.log.WithField("func", "statusCreatePOSTHandler") | 	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* | 	authed, err := oauth.MustAuth(c, true, true, true, true) // posting a status is serious business so we want *everything* | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -180,10 +180,8 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) { | ||||||
| 		Activity:       newStatus, | 		Activity:       newStatus, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/* | 	// return the frontend representation of the new status to the submitter | ||||||
| 		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, nil) | ||||||
| 	*/ |  | ||||||
| 	mastoStatus, err := m.mastoConverter.StatusToMasto(newStatus, authed.Account, authed.Account, nil, newStatus.GTSReplyToAccount, newStatus.GTSReplyToStatus) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||||||
| 		return | 		return | ||||||
|  | @ -320,7 +318,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis gtsmodel. | ||||||
| 	return nil | 	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 == "" { | 	if form.InReplyToID == "" { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | @ -369,7 +367,7 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun | ||||||
| 	return nil | 	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 { | 	if form.MediaIDs == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  | @ -410,7 +408,7 @@ func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string | ||||||
| 	return nil | 	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{} | 	menchies := []string{} | ||||||
| 	gtsMenchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), accountID, status.ID) | 	gtsMenchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), accountID, status.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -429,7 +427,7 @@ func (m *statusModule) parseMentions(form *advancedStatusCreateForm, accountID s | ||||||
| 	return nil | 	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{} | 	tags := []string{} | ||||||
| 	gtsTags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), accountID, status.ID) | 	gtsTags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), accountID, status.ID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -448,7 +446,7 @@ func (m *statusModule) parseTags(form *advancedStatusCreateForm, accountID strin | ||||||
| 	return nil | 	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{} | 	emojis := []string{} | ||||||
| 	gtsEmojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), accountID, status.ID) | 	gtsEmojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), accountID, status.ID) | ||||||
| 	if err != nil { | 	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" | 	"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{ | 	l := m.log.WithFields(logrus.Fields{ | ||||||
| 		"func":        "statusGETHandler", | 		"func":        "statusGETHandler", | ||||||
| 		"request_uri": c.Request.RequestURI, | 		"request_uri": c.Request.RequestURI, | ||||||
|  | @ -46,7 +46,7 @@ func (m *statusModule) statusGETHandler(c *gin.Context) { | ||||||
| 		requestingAccount = authed.Account | 		requestingAccount = authed.Account | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	targetStatusID := c.Param(idKey) | 	targetStatusID := c.Param(IDKey) | ||||||
| 	if targetStatusID == "" { | 	if targetStatusID == "" { | ||||||
| 		c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"}) | 		c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"}) | ||||||
| 		return | 		return | ||||||
|  |  | ||||||
|  | @ -31,6 +31,7 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/apimodule/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | @ -64,7 +65,7 @@ type StatusCreateTestSuite struct { | ||||||
| 	testAttachments  map[string]*gtsmodel.MediaAttachment | 	testAttachments  map[string]*gtsmodel.MediaAttachment | ||||||
| 
 | 
 | ||||||
| 	// module being tested | 	// module being tested | ||||||
| 	statusModule *statusModule | 	statusModule *status.StatusModule | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -84,7 +85,7 @@ func (suite *StatusCreateTestSuite) SetupSuite() { | ||||||
| 	suite.distributor = testrig.NewTestDistributor() | 	suite.distributor = testrig.NewTestDistributor() | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// 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() { | func (suite *StatusCreateTestSuite) TearDownSuite() { | ||||||
|  | @ -129,7 +130,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["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{ | 	ctx.Request.Form = url.Values{ | ||||||
| 		"status":              {"this is a brand new status! #helloworld"}, | 		"status":              {"this is a brand new status! #helloworld"}, | ||||||
| 		"spoiler_text":        {"hello hello"}, | 		"spoiler_text":        {"hello hello"}, | ||||||
|  | @ -139,7 +140,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() { | ||||||
| 		"replyable":           {"false"}, | 		"replyable":           {"false"}, | ||||||
| 		"federated":           {"false"}, | 		"federated":           {"false"}, | ||||||
| 	} | 	} | ||||||
| 	suite.statusModule.statusCreatePOSTHandler(ctx) | 	suite.statusModule.StatusCreatePOSTHandler(ctx) | ||||||
| 
 | 
 | ||||||
| 	// check response | 	// check response | ||||||
| 
 | 
 | ||||||
|  | @ -183,11 +184,11 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["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{ | 	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: "}, | 		"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) | 	suite.EqualValues(http.StatusOK, recorder.Code) | ||||||
| 
 | 
 | ||||||
|  | @ -224,13 +225,13 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["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{ | 	ctx.Request.Form = url.Values{ | ||||||
| 		"status":         {"this is a reply to a status that doesn't exist"}, | 		"status":         {"this is a reply to a status that doesn't exist"}, | ||||||
| 		"spoiler_text":   {"don't open cuz it won't work"}, | 		"spoiler_text":   {"don't open cuz it won't work"}, | ||||||
| 		"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"}, | 		"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"}, | ||||||
| 	} | 	} | ||||||
| 	suite.statusModule.statusCreatePOSTHandler(ctx) | 	suite.statusModule.StatusCreatePOSTHandler(ctx) | ||||||
| 
 | 
 | ||||||
| 	// check response | 	// check response | ||||||
| 
 | 
 | ||||||
|  | @ -255,12 +256,12 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["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{ | 	ctx.Request.Form = url.Values{ | ||||||
| 		"status":         {fmt.Sprintf("hello @%s this reply should work!", testrig.NewTestAccounts()["local_account_2"].Username)}, | 		"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}, | 		"in_reply_to_id": {testrig.NewTestStatuses()["local_account_2_status_1"].ID}, | ||||||
| 	} | 	} | ||||||
| 	suite.statusModule.statusCreatePOSTHandler(ctx) | 	suite.statusModule.StatusCreatePOSTHandler(ctx) | ||||||
| 
 | 
 | ||||||
| 	// check response | 	// check response | ||||||
| 	suite.EqualValues(http.StatusOK, recorder.Code) | 	suite.EqualValues(http.StatusOK, recorder.Code) | ||||||
|  | @ -295,12 +296,12 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { | ||||||
| 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) | ||||||
| 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["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{ | 	ctx.Request.Form = url.Values{ | ||||||
| 		"status":    {"here's an image attachment"}, | 		"status":    {"here's an image attachment"}, | ||||||
| 		"media_ids": {"7a3b9f77-ab30-461e-bdd8-e64bd1db3008"}, | 		"media_ids": {"7a3b9f77-ab30-461e-bdd8-e64bd1db3008"}, | ||||||
| 	} | 	} | ||||||
| 	suite.statusModule.statusCreatePOSTHandler(ctx) | 	suite.statusModule.StatusCreatePOSTHandler(ctx) | ||||||
| 
 | 
 | ||||||
| 	// check response | 	// check response | ||||||
| 	suite.EqualValues(http.StatusOK, recorder.Code) | 	suite.EqualValues(http.StatusOK, recorder.Code) | ||||||
|  | @ -23,6 +23,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/apimodule/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" | ||||||
|  | @ -55,7 +56,7 @@ type StatusGetTestSuite struct { | ||||||
| 	testAttachments  map[string]*gtsmodel.MediaAttachment | 	testAttachments  map[string]*gtsmodel.MediaAttachment | ||||||
| 
 | 
 | ||||||
| 	// module being tested | 	// module being tested | ||||||
| 	statusModule *statusModule | 	statusModule *status.StatusModule | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  | @ -75,7 +76,7 @@ func (suite *StatusGetTestSuite) SetupSuite() { | ||||||
| 	suite.distributor = testrig.NewTestDistributor() | 	suite.distributor = testrig.NewTestDistributor() | ||||||
| 
 | 
 | ||||||
| 	// setup module being tested | 	// 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() { | 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) | 	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 | 	// create a connection | ||||||
| 	pgCtx, cancel := context.WithCancel(ctx) | 	pgCtx, cancel := context.WithCancel(ctx) | ||||||
| 	conn := pg.Connect(opts).WithContext(pgCtx) | 	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 | 	// actually *begin* the connection so that we can tell if the db is there and listening | ||||||
| 	// and listening, and also trigger the opts.OnConnect function passed in above |  | ||||||
| 	if err := conn.Ping(ctx); err != nil { | 	if err := conn.Ping(ctx); err != nil { | ||||||
| 		cancel() | 		cancel() | ||||||
| 		return nil, fmt.Errorf("db connection error: %s", err) | 		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) | 	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{ | 	ps := &postgresService{ | ||||||
| 		config: c, | 		config: c, | ||||||
| 		conn:   conn, | 		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. | 	// 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. | 	// In this case, we can still serve the status if it's public, otherwise we definitely shouldn't. | ||||||
| 	if requestingAccount == nil { | 	if requestingAccount == nil { | ||||||
|  | 
 | ||||||
| 		if targetStatus.Visibility == gtsmodel.VisibilityPublic { | 		if targetStatus.Visibility == gtsmodel.VisibilityPublic { | ||||||
| 			return true, nil | 			return true, nil | ||||||
| 		} | 		} | ||||||
|  | 		l.Debug("requesting account is nil but the target status isn't public") | ||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// if requesting account is suspended then don't show the status -- although they probably shouldn't have gotten | 	// 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. | 	// this far (ie., been authed) in the first place: this is just for safety. | ||||||
| 	if !requestingAccount.SuspendedAt.IsZero() { | 	if !requestingAccount.SuspendedAt.IsZero() { | ||||||
|  | 		l.Debug("requesting account is suspended") | ||||||
| 		return false, nil | 		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 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 the requesting account is local but doesn't have a corresponding user in the db this is a problem | ||||||
| 			if err == pg.ErrNoRows { | 			if err == pg.ErrNoRows { | ||||||
|  | 				l.Debug("requesting account is local but there's no corresponding user") | ||||||
| 				return false, nil | 				return false, nil | ||||||
| 			} else { | 			} else { | ||||||
|  | 				l.Debugf("requesting account is local but there was an error getting the corresponding user: %s", err) | ||||||
| 				return false, err | 				return false, err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		// okay, user exists, so make sure it has full privileges/is confirmed/approved | 		// okay, user exists, so make sure it has full privileges/is confirmed/approved | ||||||
| 		if requestingUser.Disabled || !requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() { | 		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 | 			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 | 	// 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. | 	// 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 { | 	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 | 		return false, err | ||||||
| 	} else if blocked { | 	} 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 | 		// 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 | 		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) | 	mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log) | ||||||
| 	fileServerModule := fileserver.New(c, dbService, storageBackend, log) | 	fileServerModule := fileserver.New(c, dbService, storageBackend, log) | ||||||
| 	adminModule := admin.New(c, dbService, mediaHandler, mastoConverter, 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) | 	securityModule := security.New(c, log) | ||||||
| 
 | 
 | ||||||
| 	apiModules := []apimodule.ClientAPIModule{ | 	apiModules := []apimodule.ClientAPIModule{ | ||||||
|  |  | ||||||
|  | @ -345,40 +345,53 @@ func (c *converter) StatusToMasto( | ||||||
| 		return nil, fmt.Errorf("error counting faves: %s", err) | 		return nil, fmt.Errorf("error counting faves: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	faved, err := c.db.StatusFavedBy(s, requestingAccount.ID) | 	var faved bool | ||||||
| 	if err != nil { | 	var reblogged bool | ||||||
| 		return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) | 	var bookmarked bool | ||||||
| 	} | 	var pinned bool | ||||||
|  | 	var muted bool | ||||||
| 
 | 
 | ||||||
| 	reblogged, err := c.db.StatusRebloggedBy(s, requestingAccount.ID) | 	// requestingAccount will be nil for public requests without auth | ||||||
| 	if err != nil { | 	// But if it's not nil, we can also get information about the requestingAccount's interaction with this status | ||||||
| 		return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) | 	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) | 		reblogged, err = c.db.StatusRebloggedBy(s, requestingAccount.ID) | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) | 			return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) | ||||||
| 	} | 		} | ||||||
| 
 | 
 | ||||||
| 	bookmarked, err := c.db.StatusBookmarkedBy(s, requestingAccount.ID) | 		muted, err = c.db.StatusMutedBy(s, requestingAccount.ID) | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) | 			return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) | ||||||
| 	} | 		} | ||||||
| 
 | 
 | ||||||
| 	pinned, err := c.db.StatusPinnedBy(s, requestingAccount.ID) | 		bookmarked, err = c.db.StatusBookmarkedBy(s, requestingAccount.ID) | ||||||
| 	if err != nil { | 		if err != nil { | ||||||
| 		return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err) | 			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 | 	var mastoRebloggedStatus *mastotypes.Status // TODO | ||||||
| 
 | 
 | ||||||
| 	application := >smodel.Application{} | 	var mastoApplication *mastotypes.Application | ||||||
| 	if err := c.db.GetByID(s.CreatedWithApplicationID, application); err != nil { | 	if s.CreatedWithApplicationID != "" { | ||||||
| 		return nil, fmt.Errorf("error fetching application used to create status: %s", err) | 		gtsApplication := >smodel.Application{} | ||||||
| 	} | 		if err := c.db.GetByID(s.CreatedWithApplicationID, gtsApplication); err != nil { | ||||||
| 	mastoApplication, err := c.AppToMastoPublic(application) | 			return nil, fmt.Errorf("error fetching application used to create status: %s", err) | ||||||
| 	if err != nil { | 		} | ||||||
| 		return nil, fmt.Errorf("error parsing 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) | 	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. | // NewTestClients returns a map of Clients keyed according to which account they are used by. | ||||||
| func NewTestClients() map[string]*oauth.Client { | func NewTestClients() map[string]*oauth.Client { | ||||||
| 	clients := 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": { | 		"local_account_1": { | ||||||
| 			ID:     "73b48d42-029d-4487-80fc-329a5cf67869", | 			ID:     "73b48d42-029d-4487-80fc-329a5cf67869", | ||||||
| 			Secret: "c3724c74-dc3b-41b2-a108-0ea3d8399830", | 			Secret: "c3724c74-dc3b-41b2-a108-0ea3d8399830", | ||||||
| 			Domain: "http://localhost:8080", | 			Domain: "http://localhost:8080", | ||||||
| 			UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b", // local_account_1 | 			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 | 	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. | // NewTestApplications returns a map of applications keyed to which number application they are. | ||||||
| func NewTestApplications() map[string]*gtsmodel.Application { | func NewTestApplications() map[string]*gtsmodel.Application { | ||||||
| 	apps := 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": { | 		"application_1": { | ||||||
| 			ID:           "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | 			ID:           "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | ||||||
| 			Name:         "really cool gts application", | 			Name:         "really cool gts application", | ||||||
|  | @ -71,6 +93,16 @@ func NewTestApplications() map[string]*gtsmodel.Application { | ||||||
| 			Scopes:       "read write follow push", | 			Scopes:       "read write follow push", | ||||||
| 			VapidKey:     "4738dfd7-ca73-4aa6-9aa9-80e946b7db36", | 			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 | 	return apps | ||||||
| } | } | ||||||
|  | @ -128,7 +160,7 @@ func NewTestUsers() map[string]*gtsmodel.User { | ||||||
| 			CreatedByApplicationID: "", | 			CreatedByApplicationID: "", | ||||||
| 			LastEmailedAt:          time.Now().Add(-30 * time.Minute), | 			LastEmailedAt:          time.Now().Add(-30 * time.Minute), | ||||||
| 			ConfirmationToken:      "", | 			ConfirmationToken:      "", | ||||||
| 			ConfirmedAt:            time.Time{}, | 			ConfirmedAt:            time.Now().Add(-72 * time.Hour), | ||||||
| 			ConfirmationSentAt:     time.Time{}, | 			ConfirmationSentAt:     time.Time{}, | ||||||
| 			UnconfirmedEmail:       "", | 			UnconfirmedEmail:       "", | ||||||
| 			Moderator:              true, | 			Moderator:              true, | ||||||
|  | @ -689,13 +721,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			CreatedAt:      time.Now().Add(-71 * time.Hour), | 			CreatedAt:      time.Now().Add(-71 * time.Hour), | ||||||
| 			UpdatedAt:      time.Now().Add(-71 * time.Hour), | 			UpdatedAt:      time.Now().Add(-71 * time.Hour), | ||||||
| 			Local:          true, | 			Local:          true, | ||||||
| 			AccountID:      "0fb02eae-2214-473f-9667-0a43f22d75ff", | 			AccountID:      "8020dbb4-1e7b-4d99-a872-4cf94e64210f", | ||||||
| 			InReplyToID:    "", | 			InReplyToID:    "", | ||||||
| 			BoostOfID:      "", | 			BoostOfID:      "", | ||||||
| 			ContentWarning: "", | 			ContentWarning: "", | ||||||
| 			Visibility:     gtsmodel.VisibilityPublic, | 			Visibility:     gtsmodel.VisibilityPublic, | ||||||
| 			Sensitive:      false, | 			Sensitive:      false, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -712,13 +745,14 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			CreatedAt:      time.Now().Add(-70 * time.Hour), | 			CreatedAt:      time.Now().Add(-70 * time.Hour), | ||||||
| 			UpdatedAt:      time.Now().Add(-70 * time.Hour), | 			UpdatedAt:      time.Now().Add(-70 * time.Hour), | ||||||
| 			Local:          true, | 			Local:          true, | ||||||
| 			AccountID:      "0fb02eae-2214-473f-9667-0a43f22d75ff", | 			AccountID:      "8020dbb4-1e7b-4d99-a872-4cf94e64210f", | ||||||
| 			InReplyToID:    "", | 			InReplyToID:    "", | ||||||
| 			BoostOfID:      "", | 			BoostOfID:      "", | ||||||
| 			ContentWarning: "open to see some puppies", | 			ContentWarning: "open to see some puppies", | ||||||
| 			Visibility:     gtsmodel.VisibilityPublic, | 			Visibility:     gtsmodel.VisibilityPublic, | ||||||
| 			Sensitive:      true, | 			Sensitive:      true, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "9bf9e368-037f-444d-8ffd-1091d1c21c4c", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -742,6 +776,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityPublic, | 			Visibility:     gtsmodel.VisibilityPublic, | ||||||
| 			Sensitive:      true, | 			Sensitive:      true, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -765,6 +800,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityUnlocked, | 			Visibility:     gtsmodel.VisibilityUnlocked, | ||||||
| 			Sensitive:      false, | 			Sensitive:      false, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: false, | 				Federated: false, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -788,6 +824,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityMutualsOnly, | 			Visibility:     gtsmodel.VisibilityMutualsOnly, | ||||||
| 			Sensitive:      false, | 			Sensitive:      false, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: false, | 				Boostable: false, | ||||||
|  | @ -812,6 +849,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityMutualsOnly, | 			Visibility:     gtsmodel.VisibilityMutualsOnly, | ||||||
| 			Sensitive:      false, | 			Sensitive:      false, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -835,6 +873,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityPublic, | 			Visibility:     gtsmodel.VisibilityPublic, | ||||||
| 			Sensitive:      true, | 			Sensitive:      true, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  | @ -858,6 +897,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			Visibility:     gtsmodel.VisibilityPublic, | 			Visibility:     gtsmodel.VisibilityPublic, | ||||||
| 			Sensitive:      true, | 			Sensitive:      true, | ||||||
| 			Language:       "en", | 			Language:       "en", | ||||||
|  | 			CreatedWithApplicationID: "6b0cd164-8497-4cd5-bec9-957886fac5df", | ||||||
| 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | 			VisibilityAdvanced: >smodel.VisibilityAdvanced{ | ||||||
| 				Federated: true, | 				Federated: true, | ||||||
| 				Boostable: true, | 				Boostable: true, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue