too many changes to name honestly

This commit is contained in:
tsmethurst
2021-04-08 23:29:35 +02:00
parent e6c590c065
commit f210d39891
85 changed files with 1178 additions and 587 deletions

View File

@ -153,8 +153,8 @@ func main() {
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: flagNames.StorageBasePath, Name: flagNames.StorageBasePath,
Usage: "Full path to an already-created directory where gts should store/retrieve media files", Usage: "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.",
Value: "/opt/gotosocial", Value: "/gotosocial/storage/media",
EnvVars: []string{envNames.StorageBasePath}, EnvVars: []string{envNames.StorageBasePath},
}, },
&cli.StringFlag{ &cli.StringFlag{

View File

@ -28,7 +28,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
@ -43,21 +45,23 @@ const (
) )
type accountModule struct { type accountModule struct {
config *config.Config config *config.Config
db db.DB db db.DB
oauthServer oauth.Server oauthServer oauth.Server
mediaHandler media.MediaHandler mediaHandler media.MediaHandler
log *logrus.Logger mastoConverter mastotypes.Converter
log *logrus.Logger
} }
// 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, log *logrus.Logger) apimodule.ClientAPIModule { func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &accountModule{ return &accountModule{
config: config, config: config,
db: db, db: db,
oauthServer: oauthServer, oauthServer: oauthServer,
mediaHandler: mediaHandler, mediaHandler: mediaHandler,
log: log, mastoConverter: mastoConverter,
log: log,
} }
} }
@ -70,14 +74,14 @@ func (m *accountModule) Route(r router.Router) error {
func (m *accountModule) CreateTables(db db.DB) error { func (m *accountModule) CreateTables(db db.DB) error {
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {

View File

@ -27,10 +27,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/oauth2/v4" "github.com/superseriousbusiness/oauth2/v4"
) )
@ -83,7 +83,7 @@ func (m *accountModule) accountCreatePOSTHandler(c *gin.Context) {
// accountCreate does the dirty work of making an account and user in the database. // accountCreate does the dirty work of making an account and user in the database.
// It then returns a token to the caller, for use with the new account, as per the // It then returns a token to the caller, for use with the new account, as per the
// spec here: https://docs.joinmastodon.org/methods/accounts/ // spec here: https://docs.joinmastodon.org/methods/accounts/
func (m *accountModule) accountCreate(form *mastotypes.AccountCreateRequest, signUpIP net.IP, token oauth2.TokenInfo, app *model.Application) (*mastotypes.Token, error) { func (m *accountModule) accountCreate(form *mastotypes.AccountCreateRequest, signUpIP net.IP, token oauth2.TokenInfo, app *gtsmodel.Application) (*mastotypes.Token, error) {
l := m.log.WithField("func", "accountCreate") l := m.log.WithField("func", "accountCreate")
// don't store a reason if we don't require one // don't store a reason if we don't require one

View File

@ -41,11 +41,13 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/oauth2/v4" "github.com/superseriousbusiness/oauth2/v4"
"github.com/superseriousbusiness/oauth2/v4/models" "github.com/superseriousbusiness/oauth2/v4/models"
oauthmodels "github.com/superseriousbusiness/oauth2/v4/models" oauthmodels "github.com/superseriousbusiness/oauth2/v4/models"
@ -56,12 +58,13 @@ type AccountCreateTestSuite struct {
suite.Suite suite.Suite
config *config.Config config *config.Config
log *logrus.Logger log *logrus.Logger
testAccountLocal *model.Account testAccountLocal *gtsmodel.Account
testApplication *model.Application testApplication *gtsmodel.Application
testToken oauth2.TokenInfo testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage mockStorage *storage.MockStorage
mediaHandler media.MediaHandler mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
db db.DB db db.DB
accountModule *accountModule accountModule *accountModule
newUserFormHappyPath url.Values newUserFormHappyPath url.Values
@ -78,13 +81,13 @@ func (suite *AccountCreateTestSuite) SetupSuite() {
log.SetLevel(logrus.TraceLevel) log.SetLevel(logrus.TraceLevel)
suite.log = log suite.log = log
suite.testAccountLocal = &model.Account{ suite.testAccountLocal = &gtsmodel.Account{
ID: uuid.NewString(), ID: uuid.NewString(),
Username: "test_user", Username: "test_user",
} }
// can use this test application throughout // can use this test application throughout
suite.testApplication = &model.Application{ suite.testApplication = &gtsmodel.Application{
ID: "weeweeeeeeeeeeeeee", ID: "weeweeeeeeeeeeeeee",
Name: "a test application", Name: "a test application",
Website: "https://some-application-website.com", Website: "https://some-application-website.com",
@ -158,8 +161,10 @@ func (suite *AccountCreateTestSuite) SetupSuite() {
// set a media handler because some handlers (eg update credentials) need to upload media (new header/avatar) // set a media handler because some handlers (eg update credentials) need to upload media (new header/avatar)
suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log) suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log)
suite.mastoConverter = mastotypes.New(suite.config, suite.db)
// and finally here's the thing we're actually testing! // and finally here's the thing we're actually testing!
suite.accountModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.log).(*accountModule) suite.accountModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.mastoConverter, suite.log).(*accountModule)
} }
func (suite *AccountCreateTestSuite) TearDownSuite() { func (suite *AccountCreateTestSuite) TearDownSuite() {
@ -172,14 +177,14 @@ func (suite *AccountCreateTestSuite) TearDownSuite() {
func (suite *AccountCreateTestSuite) SetupTest() { func (suite *AccountCreateTestSuite) SetupTest() {
// create all the tables we might need in thie suite // create all the tables we might need in thie suite
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.CreateTable(m); err != nil { if err := suite.db.CreateTable(m); err != nil {
@ -210,14 +215,14 @@ func (suite *AccountCreateTestSuite) TearDownTest() {
// remove all the tables we might have used so it's clear for the next test // remove all the tables we might have used so it's clear for the next test
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.DropTable(m); err != nil { if err := suite.db.DropTable(m); err != nil {
@ -259,7 +264,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
defer result.Body.Close() defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body) b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
t := &mastotypes.Token{} t := &mastomodel.Token{}
err = json.Unmarshal(b, t) err = json.Unmarshal(b, t)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "we're authorized now!", t.AccessToken) assert.Equal(suite.T(), "we're authorized now!", t.AccessToken)
@ -267,7 +272,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
// check new account // check new account
// 1. we should be able to get the new account from the db // 1. we should be able to get the new account from the db
acct := &model.Account{} acct := &gtsmodel.Account{}
err = suite.db.GetWhere("username", "test_user", acct) err = suite.db.GetWhere("username", "test_user", acct)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), acct) assert.NotNil(suite.T(), acct)
@ -288,7 +293,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
// check new user // check new user
// 1. we should be able to get the new user from the db // 1. we should be able to get the new user from the db
usr := &model.User{} usr := &gtsmodel.User{}
err = suite.db.GetWhere("unconfirmed_email", suite.newUserFormHappyPath.Get("email"), usr) err = suite.db.GetWhere("unconfirmed_email", suite.newUserFormHappyPath.Get("email"), usr)
assert.Nil(suite.T(), err) assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), usr) assert.NotNil(suite.T(), usr)

View File

@ -23,7 +23,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
) )
// accountGetHandler serves the account information held by the server in response to a GET // accountGetHandler serves the account information held by the server in response to a GET
@ -37,7 +37,7 @@ func (m *accountModule) accountGETHandler(c *gin.Context) {
return return
} }
targetAccount := &model.Account{} targetAccount := &gtsmodel.Account{}
if err := m.db.GetByID(targetAcctID, targetAccount); err != nil { if err := m.db.GetByID(targetAcctID, targetAccount); err != nil {
if _, ok := err.(db.ErrNoEntries); ok { if _, ok := err.(db.ErrNoEntries); ok {
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"}) c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
@ -47,7 +47,7 @@ func (m *accountModule) accountGETHandler(c *gin.Context) {
return return
} }
acctInfo, err := m.db.AccountToMastoPublic(targetAccount) acctInfo, err := m.mastoConverter.AccountToMastoPublic(targetAccount)
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

View File

@ -27,10 +27,10 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
) )
// accountUpdateCredentialsPATCHHandler allows a user to modify their account/profile settings. // accountUpdateCredentialsPATCHHandler allows a user to modify their account/profile settings.
@ -67,7 +67,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
} }
if form.Discoverable != nil { if form.Discoverable != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "discoverable", *form.Discoverable, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "discoverable", *form.Discoverable, &gtsmodel.Account{}); err != nil {
l.Debugf("error updating discoverable: %s", err) l.Debugf("error updating discoverable: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
@ -75,7 +75,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
} }
if form.Bot != nil { if form.Bot != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "bot", *form.Bot, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "bot", *form.Bot, &gtsmodel.Account{}); err != nil {
l.Debugf("error updating bot: %s", err) l.Debugf("error updating bot: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
@ -87,7 +87,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if err := m.db.UpdateOneByID(authed.Account.ID, "display_name", *form.DisplayName, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "display_name", *form.DisplayName, &gtsmodel.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -98,7 +98,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if err := m.db.UpdateOneByID(authed.Account.ID, "note", *form.Note, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "note", *form.Note, &gtsmodel.Account{}); err != nil {
l.Debugf("error updating note: %s", err) l.Debugf("error updating note: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
@ -126,7 +126,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
} }
if form.Locked != nil { if form.Locked != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &gtsmodel.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -138,14 +138,14 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if err := m.db.UpdateOneByID(authed.Account.ID, "language", *form.Source.Language, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "language", *form.Source.Language, &gtsmodel.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
} }
if form.Source.Sensitive != nil { if form.Source.Sensitive != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &gtsmodel.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -156,7 +156,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if err := m.db.UpdateOneByID(authed.Account.ID, "privacy", *form.Source.Privacy, &model.Account{}); err != nil { if err := m.db.UpdateOneByID(authed.Account.ID, "privacy", *form.Source.Privacy, &gtsmodel.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -168,14 +168,14 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
// } // }
// fetch the account with all updated values set // fetch the account with all updated values set
updatedAccount := &model.Account{} updatedAccount := &gtsmodel.Account{}
if err := m.db.GetByID(authed.Account.ID, updatedAccount); err != nil { if err := m.db.GetByID(authed.Account.ID, updatedAccount); err != nil {
l.Debugf("could not fetch updated account %s: %s", authed.Account.ID, err) l.Debugf("could not fetch updated account %s: %s", authed.Account.ID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
acctSensitive, err := m.db.AccountToMastoSensitive(updatedAccount) acctSensitive, err := m.mastoConverter.AccountToMastoSensitive(updatedAccount)
if err != nil { if err != nil {
l.Tracef("could not convert account into mastosensitive account: %s", err) l.Tracef("could not convert account into mastosensitive account: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
@ -195,7 +195,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
// UpdateAccountAvatar does the dirty work of checking the avatar part of an account update form, // UpdateAccountAvatar does the dirty work of checking the avatar part of an account update form,
// parsing and checking the image, and doing the necessary updates in the database for this to become // parsing and checking the image, and doing the necessary updates in the database for this to become
// the account's new avatar image. // the account's new avatar image.
func (m *accountModule) UpdateAccountAvatar(avatar *multipart.FileHeader, accountID string) (*model.MediaAttachment, error) { func (m *accountModule) UpdateAccountAvatar(avatar *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
var err error var err error
if int(avatar.Size) > m.config.MediaConfig.MaxImageSize { if int(avatar.Size) > m.config.MediaConfig.MaxImageSize {
err = fmt.Errorf("avatar with size %d exceeded max image size of %d bytes", avatar.Size, m.config.MediaConfig.MaxImageSize) err = fmt.Errorf("avatar with size %d exceeded max image size of %d bytes", avatar.Size, m.config.MediaConfig.MaxImageSize)
@ -228,7 +228,7 @@ func (m *accountModule) UpdateAccountAvatar(avatar *multipart.FileHeader, accoun
// UpdateAccountHeader does the dirty work of checking the header part of an account update form, // UpdateAccountHeader does the dirty work of checking the header part of an account update form,
// parsing and checking the image, and doing the necessary updates in the database for this to become // parsing and checking the image, and doing the necessary updates in the database for this to become
// the account's new header image. // the account's new header image.
func (m *accountModule) UpdateAccountHeader(header *multipart.FileHeader, accountID string) (*model.MediaAttachment, error) { func (m *accountModule) UpdateAccountHeader(header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
var err error var err error
if int(header.Size) > m.config.MediaConfig.MaxImageSize { if int(header.Size) > m.config.MediaConfig.MaxImageSize {
err = fmt.Errorf("header with size %d exceeded max image size of %d bytes", header.Size, m.config.MediaConfig.MaxImageSize) err = fmt.Errorf("header with size %d exceeded max image size of %d bytes", header.Size, m.config.MediaConfig.MaxImageSize)

View File

@ -39,7 +39,8 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
@ -52,12 +53,13 @@ type AccountUpdateTestSuite struct {
suite.Suite suite.Suite
config *config.Config config *config.Config
log *logrus.Logger log *logrus.Logger
testAccountLocal *model.Account testAccountLocal *gtsmodel.Account
testApplication *model.Application testApplication *gtsmodel.Application
testToken oauth2.TokenInfo testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage mockStorage *storage.MockStorage
mediaHandler media.MediaHandler mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
db db.DB db db.DB
accountModule *accountModule accountModule *accountModule
newUserFormHappyPath url.Values newUserFormHappyPath url.Values
@ -74,13 +76,13 @@ func (suite *AccountUpdateTestSuite) SetupSuite() {
log.SetLevel(logrus.TraceLevel) log.SetLevel(logrus.TraceLevel)
suite.log = log suite.log = log
suite.testAccountLocal = &model.Account{ suite.testAccountLocal = &gtsmodel.Account{
ID: uuid.NewString(), ID: uuid.NewString(),
Username: "test_user", Username: "test_user",
} }
// can use this test application throughout // can use this test application throughout
suite.testApplication = &model.Application{ suite.testApplication = &gtsmodel.Application{
ID: "weeweeeeeeeeeeeeee", ID: "weeweeeeeeeeeeeeee",
Name: "a test application", Name: "a test application",
Website: "https://some-application-website.com", Website: "https://some-application-website.com",
@ -154,8 +156,10 @@ func (suite *AccountUpdateTestSuite) SetupSuite() {
// set a media handler because some handlers (eg update credentials) need to upload media (new header/avatar) // set a media handler because some handlers (eg update credentials) need to upload media (new header/avatar)
suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log) suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log)
suite.mastoConverter = mastotypes.New(suite.config, suite.db)
// and finally here's the thing we're actually testing! // and finally here's the thing we're actually testing!
suite.accountModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.log).(*accountModule) suite.accountModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.mastoConverter, suite.log).(*accountModule)
} }
func (suite *AccountUpdateTestSuite) TearDownSuite() { func (suite *AccountUpdateTestSuite) TearDownSuite() {
@ -168,14 +172,14 @@ func (suite *AccountUpdateTestSuite) TearDownSuite() {
func (suite *AccountUpdateTestSuite) SetupTest() { func (suite *AccountUpdateTestSuite) SetupTest() {
// create all the tables we might need in thie suite // create all the tables we might need in thie suite
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.CreateTable(m); err != nil { if err := suite.db.CreateTable(m); err != nil {
@ -206,14 +210,14 @@ func (suite *AccountUpdateTestSuite) TearDownTest() {
// remove all the tables we might have used so it's clear for the next test // remove all the tables we might have used so it's clear for the next test
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.DropTable(m); err != nil { if err := suite.db.DropTable(m); err != nil {

View File

@ -38,7 +38,7 @@ func (m *accountModule) accountVerifyGETHandler(c *gin.Context) {
} }
l.Tracef("retrieved account %+v, converting to mastosensitive...", authed.Account.ID) l.Tracef("retrieved account %+v, converting to mastosensitive...", authed.Account.ID)
acctSensitive, err := m.db.AccountToMastoSensitive(authed.Account) acctSensitive, err := m.mastoConverter.AccountToMastoSensitive(authed.Account)
if err != nil { if err != nil {
l.Tracef("could not convert account into mastosensitive account: %s", err) l.Tracef("could not convert account into mastosensitive account: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})

View File

@ -25,7 +25,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
) )
@ -33,17 +34,19 @@ import (
const appsPath = "/api/v1/apps" const appsPath = "/api/v1/apps"
type appModule struct { type appModule struct {
server oauth.Server server oauth.Server
db db.DB db db.DB
log *logrus.Logger mastoConverter mastotypes.Converter
log *logrus.Logger
} }
// New returns a new auth module // New returns a new auth module
func New(srv oauth.Server, db db.DB, log *logrus.Logger) apimodule.ClientAPIModule { func New(srv oauth.Server, db db.DB, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &appModule{ return &appModule{
server: srv, server: srv,
db: db, db: db,
log: log, mastoConverter: mastoConverter,
log: log,
} }
} }
@ -57,9 +60,9 @@ func (m *appModule) CreateTables(db db.DB) error {
models := []interface{}{ models := []interface{}{
&oauth.Client{}, &oauth.Client{},
&oauth.Token{}, &oauth.Token{},
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Application{}, &gtsmodel.Application{},
} }
for _, m := range models { for _, m := range models {

View File

@ -24,9 +24,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
) )
// appsPOSTHandler should be served at https://example.org/api/v1/apps // appsPOSTHandler should be served at https://example.org/api/v1/apps
@ -78,7 +78,7 @@ func (m *appModule) appsPOSTHandler(c *gin.Context) {
vapidKey := uuid.NewString() vapidKey := uuid.NewString()
// generate the application to put in the database // generate the application to put in the database
app := &model.Application{ app := &gtsmodel.Application{
Name: form.ClientName, Name: form.ClientName,
Website: form.Website, Website: form.Website,
RedirectURI: form.RedirectURIs, RedirectURI: form.RedirectURIs,
@ -108,6 +108,12 @@ func (m *appModule) appsPOSTHandler(c *gin.Context) {
return return
} }
mastoApp, err := m.mastoConverter.AppToMastoSensitive(app)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// done, return the new app information per the spec here: https://docs.joinmastodon.org/methods/apps/ // done, return the new app information per the spec here: https://docs.joinmastodon.org/methods/apps/
c.JSON(http.StatusOK, app.ToMastoSensitive()) c.JSON(http.StatusOK, mastoApp)
} }

View File

@ -31,7 +31,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
) )
@ -75,9 +75,9 @@ func (m *authModule) CreateTables(db db.DB) error {
models := []interface{}{ models := []interface{}{
&oauth.Client{}, &oauth.Client{},
&oauth.Token{}, &oauth.Token{},
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Application{}, &gtsmodel.Application{},
} }
for _, m := range models { for _, m := range models {

View File

@ -29,7 +29,7 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -39,9 +39,9 @@ type AuthTestSuite struct {
suite.Suite suite.Suite
oauthServer oauth.Server oauthServer oauth.Server
db db.DB db db.DB
testAccount *model.Account testAccount *gtsmodel.Account
testApplication *model.Application testApplication *gtsmodel.Application
testUser *model.User testUser *gtsmodel.User
testClient *oauth.Client testClient *oauth.Client
config *config.Config config *config.Config
} }
@ -75,11 +75,11 @@ func (suite *AuthTestSuite) SetupSuite() {
acctID := uuid.NewString() acctID := uuid.NewString()
suite.testAccount = &model.Account{ suite.testAccount = &gtsmodel.Account{
ID: acctID, ID: acctID,
Username: "test_user", Username: "test_user",
} }
suite.testUser = &model.User{ suite.testUser = &gtsmodel.User{
EncryptedPassword: string(encryptedPassword), EncryptedPassword: string(encryptedPassword),
Email: "user@example.org", Email: "user@example.org",
AccountID: acctID, AccountID: acctID,
@ -89,7 +89,7 @@ func (suite *AuthTestSuite) SetupSuite() {
Secret: "some-secret", Secret: "some-secret",
Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host), Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host),
} }
suite.testApplication = &model.Application{ suite.testApplication = &gtsmodel.Application{
Name: "a test application", Name: "a test application",
Website: "https://some-application-website.com", Website: "https://some-application-website.com",
RedirectURI: "http://localhost:8080", RedirectURI: "http://localhost:8080",
@ -115,9 +115,9 @@ func (suite *AuthTestSuite) SetupTest() {
models := []interface{}{ models := []interface{}{
&oauth.Client{}, &oauth.Client{},
&oauth.Token{}, &oauth.Token{},
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Application{}, &gtsmodel.Application{},
} }
for _, m := range models { for _, m := range models {
@ -148,9 +148,9 @@ func (suite *AuthTestSuite) TearDownTest() {
models := []interface{}{ models := []interface{}{
&oauth.Client{}, &oauth.Client{},
&oauth.Token{}, &oauth.Token{},
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Application{}, &gtsmodel.Application{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.DropTable(m); err != nil { if err := suite.db.DropTable(m); err != nil {

View File

@ -27,8 +27,8 @@ import (
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes" "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
) )
// authorizeGETHandler should be served as GET at https://example.org/oauth/authorize // authorizeGETHandler should be served as GET at https://example.org/oauth/authorize
@ -57,7 +57,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session"})
return return
} }
app := &model.Application{ app := &gtsmodel.Application{
ClientID: clientID, ClientID: clientID,
} }
if err := m.db.GetWhere("client_id", app.ClientID, app); err != nil { if err := m.db.GetWhere("client_id", app.ClientID, app); err != nil {
@ -66,7 +66,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
} }
// we can also use the userid of the user to fetch their username from the db to greet them nicely <3 // we can also use the userid of the user to fetch their username from the db to greet them nicely <3
user := &model.User{ user := &gtsmodel.User{
ID: userID, ID: userID,
} }
if err := m.db.GetByID(user.ID, user); err != nil { if err := m.db.GetByID(user.ID, user); err != nil {
@ -74,7 +74,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
return return
} }
acct := &model.Account{ acct := &gtsmodel.Account{
ID: user.AccountID, ID: user.AccountID,
} }

View File

@ -20,7 +20,7 @@ package auth
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
@ -46,7 +46,7 @@ func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
l.Tracef("authenticated user %s with bearer token, scope is %s", uid, ti.GetScope()) l.Tracef("authenticated user %s with bearer token, scope is %s", uid, ti.GetScope())
// fetch user's and account for this user id // fetch user's and account for this user id
user := &model.User{} user := &gtsmodel.User{}
if err := m.db.GetByID(uid, user); err != nil || user == nil { if err := m.db.GetByID(uid, user); err != nil || user == nil {
l.Warnf("no user found for validated uid %s", uid) l.Warnf("no user found for validated uid %s", uid)
return return
@ -54,7 +54,7 @@ func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
c.Set(oauth.SessionAuthorizedUser, user) c.Set(oauth.SessionAuthorizedUser, user)
l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedUser, user) l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedUser, user)
acct := &model.Account{} acct := &gtsmodel.Account{}
if err := m.db.GetByID(user.AccountID, acct); err != nil || acct == nil { if err := m.db.GetByID(user.AccountID, acct); err != nil || acct == nil {
l.Warnf("no account found for validated user %s", uid) l.Warnf("no account found for validated user %s", uid)
return return
@ -66,7 +66,7 @@ func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
// check for application token // check for application token
if cid := ti.GetClientID(); cid != "" { if cid := ti.GetClientID(); cid != "" {
l.Tracef("authenticated client %s with bearer token, scope is %s", cid, ti.GetScope()) l.Tracef("authenticated client %s with bearer token, scope is %s", cid, ti.GetScope())
app := &model.Application{} app := &gtsmodel.Application{}
if err := m.db.GetWhere("client_id", cid, app); err != nil { if err := m.db.GetWhere("client_id", cid, app); err != nil {
l.Tracef("no app found for client %s", cid) l.Tracef("no app found for client %s", cid)
} }

View File

@ -24,7 +24,7 @@ import (
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -84,7 +84,7 @@ func (m *authModule) validatePassword(email string, password string) (userid str
} }
// first we select the user from the database based on email address, bail if no user found for that email // first we select the user from the database based on email address, bail if no user found for that email
gtsUser := &model.User{} gtsUser := &gtsmodel.User{}
if err := m.db.GetWhere("email", email, gtsUser); err != nil { if err := m.db.GetWhere("email", email, gtsUser); err != nil {
l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err) l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err)

View File

@ -7,7 +7,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
) )
@ -44,14 +44,14 @@ func (m *fileServer) Route(s router.Router) error {
func (m *fileServer) CreateTables(db db.DB) error { func (m *fileServer) CreateTables(db db.DB) error {
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {

View File

@ -25,8 +25,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/apimodule" "github.com/superseriousbusiness/gotosocial/internal/apimodule"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/distributor" "github.com/superseriousbusiness/gotosocial/internal/distributor"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
@ -51,22 +52,24 @@ const (
) )
type statusModule struct { type statusModule struct {
config *config.Config config *config.Config
db db.DB db db.DB
oauthServer oauth.Server oauthServer oauth.Server
mediaHandler media.MediaHandler mediaHandler media.MediaHandler
distributor distributor.Distributor mastoConverter mastotypes.Converter
log *logrus.Logger distributor distributor.Distributor
log *logrus.Logger
} }
// 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, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule { 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{ return &statusModule{
config: config, config: config,
db: db, db: db,
mediaHandler: mediaHandler, mediaHandler: mediaHandler,
distributor: distributor, mastoConverter: mastoConverter,
log: log, distributor: distributor,
log: log,
} }
} }
@ -79,17 +82,17 @@ func (m *statusModule) Route(r router.Router) error {
func (m *statusModule) CreateTables(db db.DB) error { func (m *statusModule) CreateTables(db db.DB) error {
models := []interface{}{ models := []interface{}{
&model.User{}, &gtsmodel.User{},
&model.Account{}, &gtsmodel.Account{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.Status{}, &gtsmodel.Status{},
&model.Application{}, &gtsmodel.Application{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
&model.Emoji{}, &gtsmodel.Emoji{},
&model.Tag{}, &gtsmodel.Tag{},
&model.Mention{}, &gtsmodel.Mention{},
} }
for _, m := range models { for _, m := range models {

View File

@ -28,11 +28,11 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/distributor" "github.com/superseriousbusiness/gotosocial/internal/distributor"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
) )
type advancedStatusCreateForm struct { type advancedStatusCreateForm struct {
@ -42,7 +42,7 @@ type advancedStatusCreateForm struct {
type advancedVisibilityFlagsForm struct { type advancedVisibilityFlagsForm struct {
// The gotosocial visibility model // The gotosocial visibility model
VisibilityAdvanced *model.Visibility `form:"visibility_advanced"` VisibilityAdvanced *gtsmodel.Visibility `form:"visibility_advanced"`
// This status will be federated beyond the local timeline(s) // This status will be federated beyond the local timeline(s)
Federated *bool `form:"federated"` Federated *bool `form:"federated"`
// This status can be boosted/reblogged // This status can be boosted/reblogged
@ -96,7 +96,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
thisStatusID := uuid.NewString() thisStatusID := uuid.NewString()
thisStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, thisStatusID) thisStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, thisStatusID)
thisStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, thisStatusID) thisStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, thisStatusID)
newStatus := &model.Status{ newStatus := &gtsmodel.Status{
ID: thisStatusID, ID: thisStatusID,
URI: thisStatusURI, URI: thisStatusURI,
URL: thisStatusURL, URL: thisStatusURL,
@ -106,7 +106,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
Local: true, Local: true,
AccountID: authed.Account.ID, AccountID: authed.Account.ID,
ContentWarning: form.SpoilerText, ContentWarning: form.SpoilerText,
ActivityStreamsType: model.ActivityStreamsNote, ActivityStreamsType: gtsmodel.ActivityStreamsNote,
Sensitive: form.Sensitive, Sensitive: form.Sensitive,
Language: form.Language, Language: form.Language,
} }
@ -135,16 +135,23 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
return return
} }
// convert mentions to *model.Mention // convert mentions to *gtsmodel.Mention
menchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), authed.Account.ID, thisStatusID) menchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), authed.Account.ID, thisStatusID)
if err != nil { if err != nil {
l.Debugf("error generating mentions from status: %s", err) l.Debugf("error generating mentions from status: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating mentions from status"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating mentions from status"})
return return
} }
for _, menchie := range menchies {
if err := m.db.Put(menchie); err != nil {
l.Debugf("error putting mentions in db: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "db error while generating mentions from status"})
return
}
}
newStatus.Mentions = menchies newStatus.Mentions = menchies
// convert tags to *model.Tag // convert tags to *gtsmodel.Tag
tags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), authed.Account.ID, thisStatusID) tags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), authed.Account.ID, thisStatusID)
if err != nil { if err != nil {
l.Debugf("error generating hashtags from status: %s", err) l.Debugf("error generating hashtags from status: %s", err)
@ -153,7 +160,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
} }
newStatus.Tags = tags newStatus.Tags = tags
// convert emojis to *model.Emoji // convert emojis to *gtsmodel.Emoji
emojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), authed.Account.ID, thisStatusID) emojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), authed.Account.ID, thisStatusID)
if err != nil { if err != nil {
l.Debugf("error generating emojis from status: %s", err) l.Debugf("error generating emojis from status: %s", err)
@ -170,33 +177,64 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
// pass to the distributor to take care of side effects -- federation, mentions, updating metadata, etc, etc // pass to the distributor to take care of side effects -- federation, mentions, updating metadata, etc, etc
m.distributor.FromClientAPI() <- distributor.FromClientAPI{ m.distributor.FromClientAPI() <- distributor.FromClientAPI{
APObjectType: model.ActivityStreamsNote, APObjectType: gtsmodel.ActivityStreamsNote,
APActivityType: model.ActivityStreamsCreate, APActivityType: gtsmodel.ActivityStreamsCreate,
Activity: newStatus, Activity: newStatus,
} }
// return populated status to submitter // now we need to build up the mastodon-style status object to return to the submitter
mastoAccount, err := m.db.AccountToMastoPublic(authed.Account)
mastoVis := util.ParseMastoVisFromGTSVis(newStatus.Visibility)
mastoAccount, err := m.mastoConverter.AccountToMastoPublic(authed.Account)
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
} }
mastoAttachments := []mastotypes.Attachment{}
for _, a := range newStatus.Attachments {
ma, err := m.mastoConverter.AttachmentToMasto(a)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
mastoAttachments = append(mastoAttachments, ma)
}
mastoMentions := []mastotypes.Mention{}
for _, gtsm := range newStatus.Mentions {
mm, err := m.mastoConverter.MentionToMasto(gtsm)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
mastoMentions = append(mastoMentions, mm)
}
mastoApplication, err := m.mastoConverter.AppToMastoPublic(authed.Application)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
mastoStatus := &mastotypes.Status{ mastoStatus := &mastotypes.Status{
ID: newStatus.ID, ID: newStatus.ID,
CreatedAt: newStatus.CreatedAt.Format(time.RFC3339), CreatedAt: newStatus.CreatedAt.Format(time.RFC3339),
InReplyToID: newStatus.InReplyToID, InReplyToID: newStatus.InReplyToID,
// InReplyToAccountID: newStatus.ReplyToAccount.ID, InReplyToAccountID: newStatus.InReplyToAccountID,
Sensitive: newStatus.Sensitive, Sensitive: newStatus.Sensitive,
SpoilerText: newStatus.ContentWarning, SpoilerText: newStatus.ContentWarning,
Visibility: util.ParseMastoVisFromGTSVis(newStatus.Visibility), Visibility: mastoVis,
Language: newStatus.Language, Language: newStatus.Language,
URI: newStatus.URI, URI: newStatus.URI,
URL: newStatus.URL, URL: newStatus.URL,
Content: newStatus.Content, Content: newStatus.Content,
Application: authed.Application.ToMastoPublic(), Application: mastoApplication,
Account: mastoAccount, Account: mastoAccount,
// MediaAttachments: , MediaAttachments: mastoAttachments,
Text: form.Status, Mentions: mastoMentions,
Text: form.Status,
} }
c.JSON(http.StatusOK, mastoStatus) c.JSON(http.StatusOK, mastoStatus)
} }
@ -255,16 +293,16 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse
return nil return nil
} }
func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Visibility, status *model.Status) error { func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error {
// by default all flags are set to true // by default all flags are set to true
gtsAdvancedVis := &model.VisibilityAdvanced{ gtsAdvancedVis := &gtsmodel.VisibilityAdvanced{
Federated: true, Federated: true,
Boostable: true, Boostable: true,
Replyable: true, Replyable: true,
Likeable: true, Likeable: true,
} }
var gtsBasicVis model.Visibility var gtsBasicVis gtsmodel.Visibility
// Advanced takes priority if it's set. // Advanced takes priority if it's set.
// If it's not set, take whatever masto visibility is set. // If it's not set, take whatever masto visibility is set.
// If *that's* not set either, then just take the account default. // If *that's* not set either, then just take the account default.
@ -277,10 +315,10 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
} }
switch gtsBasicVis { switch gtsBasicVis {
case model.VisibilityPublic: case gtsmodel.VisibilityPublic:
// for public, there's no need to change any of the advanced flags from true regardless of what the user filled out // for public, there's no need to change any of the advanced flags from true regardless of what the user filled out
break break
case model.VisibilityUnlocked: case gtsmodel.VisibilityUnlocked:
// for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them // for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them
if form.Federated != nil { if form.Federated != nil {
gtsAdvancedVis.Federated = *form.Federated gtsAdvancedVis.Federated = *form.Federated
@ -298,7 +336,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
gtsAdvancedVis.Likeable = *form.Likeable gtsAdvancedVis.Likeable = *form.Likeable
} }
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly: case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly:
// for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them // for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them
gtsAdvancedVis.Boostable = false gtsAdvancedVis.Boostable = false
@ -314,7 +352,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
gtsAdvancedVis.Likeable = *form.Likeable gtsAdvancedVis.Likeable = *form.Likeable
} }
case model.VisibilityDirect: case gtsmodel.VisibilityDirect:
// direct is pretty easy: there's only one possible setting so return it // direct is pretty easy: there's only one possible setting so return it
gtsAdvancedVis.Federated = true gtsAdvancedVis.Federated = true
gtsAdvancedVis.Boostable = false gtsAdvancedVis.Boostable = false
@ -327,7 +365,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
return nil return nil
} }
func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *model.Status) error { func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.InReplyToID == "" { if form.InReplyToID == "" {
return nil return nil
} }
@ -339,8 +377,8 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
// 3. Does a block exist between either the current account or the account that posted the status it's replying to? // 3. Does a block exist between either the current account or the account that posted the status it's replying to?
// //
// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing. // If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing.
repliedStatus := &model.Status{} repliedStatus := &gtsmodel.Status{}
repliedAccount := &model.Account{} repliedAccount := &gtsmodel.Account{}
// check replied status exists + is replyable // check replied status exists + is replyable
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil { if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil {
if _, ok := err.(db.ErrNoEntries); ok { if _, ok := err.(db.ErrNoEntries); ok {
@ -356,26 +394,35 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
// check replied account is known to us // check replied account is known to us
if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil { if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) if _, ok := err.(db.ErrNoEntries); ok {
return fmt.Errorf("status with id %s not replyable because account id %s is not known", form.InReplyToID, repliedStatus.AccountID)
} else {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
} }
// check if a block exists // check if a block exists
if blocked, err := m.db.Blocked(thisAccountID, repliedAccount.ID); err != nil || blocked { if blocked, err := m.db.Blocked(thisAccountID, repliedAccount.ID); err != nil {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) if _, ok := err.(db.ErrNoEntries); !ok {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
} else if blocked {
return fmt.Errorf("status with id %s not replyable", form.InReplyToID)
} }
status.InReplyToID = repliedStatus.ID status.InReplyToID = repliedStatus.ID
status.InReplyToAccountID = repliedAccount.ID
return nil return nil
} }
func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccountID string, status *model.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
} }
attachments := []*model.MediaAttachment{} attachments := []*gtsmodel.MediaAttachment{}
for _, mediaID := range form.MediaIDs { for _, mediaID := range form.MediaIDs {
// check these attachments exist // check these attachments exist
a := &model.MediaAttachment{} a := &gtsmodel.MediaAttachment{}
if err := m.db.GetByID(mediaID, a); err != nil { if err := m.db.GetByID(mediaID, a); err != nil {
return fmt.Errorf("invalid media type or media not found for media id %s", mediaID) return fmt.Errorf("invalid media type or media not found for media id %s", mediaID)
} }
@ -389,7 +436,7 @@ func (m *statusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccount
return nil return nil
} }
func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string, status *model.Status) error { func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error {
if form.Language != "" { if form.Language != "" {
status.Language = form.Language status.Language = form.Language
} else { } else {

View File

@ -34,12 +34,13 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/distributor" "github.com/superseriousbusiness/gotosocial/internal/distributor"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/gotosocial/testrig" "github.com/superseriousbusiness/gotosocial/testrig"
) )
@ -49,12 +50,13 @@ type StatusCreateTestSuite struct {
mockOauthServer *oauth.MockServer mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage mockStorage *storage.MockStorage
mediaHandler media.MediaHandler mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
distributor *distributor.MockDistributor distributor *distributor.MockDistributor
testTokens map[string]*oauth.Token testTokens map[string]*oauth.Token
testClients map[string]*oauth.Client testClients map[string]*oauth.Client
testApplications map[string]*model.Application testApplications map[string]*gtsmodel.Application
testUsers map[string]*model.User testUsers map[string]*gtsmodel.User
testAccounts map[string]*model.Account testAccounts map[string]*gtsmodel.Account
log *logrus.Logger log *logrus.Logger
db db.DB db db.DB
statusModule *statusModule statusModule *statusModule
@ -113,10 +115,11 @@ func (suite *StatusCreateTestSuite) SetupSuite() {
suite.mockOauthServer = &oauth.MockServer{} suite.mockOauthServer = &oauth.MockServer{}
suite.mockStorage = &storage.MockStorage{} suite.mockStorage = &storage.MockStorage{}
suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log) suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log)
suite.mastoConverter = mastotypes.New(suite.config, suite.db)
suite.distributor = &distributor.MockDistributor{} suite.distributor = &distributor.MockDistributor{}
suite.distributor.On("FromClientAPI").Return(make(chan distributor.FromClientAPI, 100)) suite.distributor.On("FromClientAPI").Return(make(chan distributor.FromClientAPI, 100))
suite.statusModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.distributor, suite.log).(*statusModule) suite.statusModule = New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*statusModule)
} }
func (suite *StatusCreateTestSuite) TearDownSuite() { func (suite *StatusCreateTestSuite) TearDownSuite() {
@ -184,16 +187,15 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() {
defer result.Body.Close() defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body) b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
fmt.Println(string(b))
statusReply := &mastotypes.Status{} statusReply := &mastomodel.Status{}
err = json.Unmarshal(b, statusReply) err = json.Unmarshal(b, statusReply)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText) assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content) assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
assert.True(suite.T(), statusReply.Sensitive) assert.True(suite.T(), statusReply.Sensitive)
assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility) assert.Equal(suite.T(), mastomodel.VisibilityPrivate, statusReply.Visibility)
} }
func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() { func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() {
@ -209,31 +211,60 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() {
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", 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
// 1. we should have OK from our call to the function suite.EqualValues(http.StatusBadRequest, recorder.Code)
result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), `{"error":"status with id 3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50 not replyable because it doesn't exist"}`, string(b))
}
func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToLocalSuccess() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request.Form = url.Values{
"status": {fmt.Sprintf("hello @%s this reply should work!", testrig.TestAccounts()["local_account_2"].Username)},
"in_reply_to_id": {testrig.TestStatuses()["local_account_2_status_1"].ID},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusOK, recorder.Code) suite.EqualValues(http.StatusOK, recorder.Code)
result := recorder.Result() result := recorder.Result()
defer result.Body.Close() defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body) b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
fmt.Println(string(b))
statusReply := &mastotypes.Status{} statusReply := &mastomodel.Status{}
err = json.Unmarshal(b, statusReply) err = json.Unmarshal(b, statusReply)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText) assert.Equal(suite.T(), "", statusReply.SpoilerText)
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content) assert.Equal(suite.T(), fmt.Sprintf("hello @%s this reply should work!", testrig.TestAccounts()["local_account_2"].Username), statusReply.Content)
assert.True(suite.T(), statusReply.Sensitive) assert.False(suite.T(), statusReply.Sensitive)
assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility) assert.Equal(suite.T(), mastomodel.VisibilityPublic, statusReply.Visibility)
assert.Equal(suite.T(), testrig.TestStatuses()["local_account_2_status_1"].ID, statusReply.InReplyToID)
assert.Equal(suite.T(), testrig.TestAccounts()["local_account_2"].ID, statusReply.InReplyToAccountID)
assert.Len(suite.T(), statusReply.Mentions, 1)
} }
func TestStatusCreateTestSuite(t *testing.T) { func TestStatusCreateTestSuite(t *testing.T) {

View File

@ -27,8 +27,7 @@ import (
"github.com/go-fed/activity/pub" "github.com/go-fed/activity/pub"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
) )
const dbTypePostgres string = "POSTGRES" const dbTypePostgres string = "POSTGRES"
@ -115,38 +114,38 @@ type DB interface {
// GetAccountByUserID is a shortcut for the common action of fetching an account corresponding to a user ID. // GetAccountByUserID is a shortcut for the common action of fetching an account corresponding to a user ID.
// The given account pointer will be set to the result of the query, whatever it is. // The given account pointer will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetAccountByUserID(userID string, account *model.Account) error GetAccountByUserID(userID string, account *gtsmodel.Account) error
// GetFollowRequestsForAccountID is a shortcut for the common action of fetching a list of follow requests targeting the given account ID. // GetFollowRequestsForAccountID is a shortcut for the common action of fetching a list of follow requests targeting the given account ID.
// The given slice 'followRequests' will be set to the result of the query, whatever it is. // The given slice 'followRequests' will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetFollowRequestsForAccountID(accountID string, followRequests *[]model.FollowRequest) error GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error
// GetFollowingByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is following. // GetFollowingByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is following.
// The given slice 'following' will be set to the result of the query, whatever it is. // The given slice 'following' will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetFollowingByAccountID(accountID string, following *[]model.Follow) error GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error
// GetFollowersByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is followed by. // GetFollowersByAccountID is a shortcut for the common action of fetching a list of accounts that accountID is followed by.
// The given slice 'followers' will be set to the result of the query, whatever it is. // The given slice 'followers' will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetFollowersByAccountID(accountID string, followers *[]model.Follow) error GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error
// GetStatusesByAccountID is a shortcut for the common action of fetching a list of statuses produced by accountID. // GetStatusesByAccountID is a shortcut for the common action of fetching a list of statuses produced by accountID.
// The given slice 'statuses' will be set to the result of the query, whatever it is. // The given slice 'statuses' will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetStatusesByAccountID(accountID string, statuses *[]model.Status) error GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error
// GetStatusesByTimeDescending is a shortcut for getting the most recent statuses. accountID is optional, if not provided // GetStatusesByTimeDescending is a shortcut for getting the most recent statuses. accountID is optional, if not provided
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can // then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
// be very memory intensive so you probably shouldn't do this! // be very memory intensive so you probably shouldn't do this!
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetStatusesByTimeDescending(accountID string, statuses *[]model.Status, limit int) error GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error
// GetLastStatusForAccountID simply gets the most recent status by the given account. // GetLastStatusForAccountID simply gets the most recent status by the given account.
// The given slice 'status' pointer will be set to the result of the query, whatever it is. // The given slice 'status' pointer will be set to the result of the query, whatever it is.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetLastStatusForAccountID(accountID string, status *model.Status) error GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error
// IsUsernameAvailable checks whether a given username is available on our domain. // IsUsernameAvailable checks whether a given username is available on our domain.
// Returns an error if the username is already taken, or something went wrong in the db. // Returns an error if the username is already taken, or something went wrong in the db.
@ -161,18 +160,18 @@ type DB interface {
// NewSignup creates a new user in the database with the given parameters, with an *unconfirmed* email address. // NewSignup creates a new user in the database with the given parameters, with an *unconfirmed* email address.
// By the time this function is called, it should be assumed that all the parameters have passed validation! // By the time this function is called, it should be assumed that all the parameters have passed validation!
NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*model.User, error) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*gtsmodel.User, error)
// SetHeaderOrAvatarForAccountID sets the header or avatar for the given accountID to the given media attachment. // SetHeaderOrAvatarForAccountID sets the header or avatar for the given accountID to the given media attachment.
SetHeaderOrAvatarForAccountID(mediaAttachment *model.MediaAttachment, accountID string) error SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error
// GetHeaderAvatarForAccountID gets the current avatar for the given account ID. // GetHeaderAvatarForAccountID gets the current avatar for the given account ID.
// The passed mediaAttachment pointer will be populated with the value of the avatar, if it exists. // The passed mediaAttachment pointer will be populated with the value of the avatar, if it exists.
GetAvatarForAccountID(avatar *model.MediaAttachment, accountID string) error GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error
// GetHeaderForAccountID gets the current header for the given account ID. // GetHeaderForAccountID gets the current header for the given account ID.
// The passed mediaAttachment pointer will be populated with the value of the header, if it exists. // The passed mediaAttachment pointer will be populated with the value of the header, if it exists.
GetHeaderForAccountID(header *model.MediaAttachment, accountID string) error GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error
// Blocked checks whether a block exists in eiher direction between two accounts. // Blocked checks whether a block exists in eiher direction between two accounts.
// That is, it returns true if account1 blocks account2, OR if account2 blocks account1. // That is, it returns true if account1 blocks account2, OR if account2 blocks account1.
@ -182,39 +181,30 @@ type DB interface {
USEFUL CONVERSION FUNCTIONS USEFUL CONVERSION FUNCTIONS
*/ */
// AccountToMastoSensitive takes a db model account as a param, and returns a populated mastotype account, or an error // MentionStringsToMentions takes a slice of deduplicated, lowercase account names in the form "@test@whatever.example.org" for a remote account,
// if something goes wrong. The returned account should be ready to serialize on an API level, and may have sensitive fields, // or @test for a local account, which have been mentioned in a status.
// so serve it only to an authorized user who should have permission to see it. // It takes the id of the account that wrote the status, and the id of the status itself, and then
AccountToMastoSensitive(account *model.Account) (*mastotypes.Account, error)
// AccountToMastoPublic takes a db model account as a param, and returns a populated mastotype account, or an error
// if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields.
// In other words, this is the public record that the server has of an account.
AccountToMastoPublic(account *model.Account) (*mastotypes.Account, error)
// MentionStringsToMentions takes a slice of deduplicated, lowercase account names in the form "@test@whatever.example.org", which have been
// mentioned in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
// checks in the database for the mentioned accounts, and returns a slice of mentions generated based on the given parameters. // checks in the database for the mentioned accounts, and returns a slice of mentions generated based on the given parameters.
// //
// Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking if they exist // Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking
// and conveniently returning them. // if they exist in the db and conveniently returning them.
MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*model.Mention, error) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error)
// TagStringsToTags takes a slice of deduplicated, lowercase tags in the form "somehashtag", which have been // TagStringsToTags takes a slice of deduplicated, lowercase tags in the form "somehashtag", which have been
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then // used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
// returns a slice of *model.Tag corresponding to the given tags. // returns a slice of *model.Tag corresponding to the given tags.
// //
// Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking if they exist // Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking
// and conveniently returning them. // if they exist in the db and conveniently returning them.
TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*model.Tag, error) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error)
// EmojiStringsToEmojis takes a slice of deduplicated, lowercase emojis in the form ":emojiname:", which have been // EmojiStringsToEmojis takes a slice of deduplicated, lowercase emojis in the form ":emojiname:", which have been
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then // used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
// returns a slice of *model.Emoji corresponding to the given emojis. // returns a slice of *model.Emoji corresponding to the given emojis.
// //
// Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking if they exist // Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking
// and conveniently returning them. // if they exist in the db and conveniently returning them.
EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*model.Emoji, error) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error)
} }
// New returns a new database service that satisfies the DB interface and, by extension, // New returns a new database service that satisfies the DB interface and, by extension,

View File

@ -16,11 +16,11 @@
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 model contains types used *internally* by GoToSocial and added/removed/selected from the database. // Package gtsmodel contains types used *internally* by GoToSocial and added/removed/selected from the database.
// These types should never be serialized and/or sent out via public APIs, as they contain sensitive information. // These types should never be serialized and/or sent out via public APIs, as they contain sensitive information.
// The annotation used on these structs is for handling them via the go-pg ORM (hence why they're in this db subdir). // The annotation used on these structs is for handling them via the go-pg ORM (hence why they're in this db subdir).
// See here for more info on go-pg model annotations: https://pg.uptrace.dev/models/ // See here for more info on go-pg model annotations: https://pg.uptrace.dev/models/
package model package gtsmodel
import ( import (
"crypto/rsa" "crypto/rsa"
@ -38,7 +38,7 @@ type Account struct {
// Username of the account, should just be a string of [a-z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org`` // Username of the account, should just be a string of [a-z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``
Username string `pg:",notnull,unique:userdomain"` // username and domain should be unique *with* each other Username string `pg:",notnull,unique:userdomain"` // username and domain should be unique *with* each other
// Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username. // Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username.
Domain string `pg:"default:null,unique:userdomain"` // username and domain should be unique *with* each other Domain string `pg:",unique:userdomain"` // username and domain should be unique *with* each other
/* /*
ACCOUNT METADATA ACCOUNT METADATA

View File

@ -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 model package gtsmodel
// ActivityStreamsObject refers to https://www.w3.org/TR/activitystreams-vocabulary/#object-types // ActivityStreamsObject refers to https://www.w3.org/TR/activitystreams-vocabulary/#object-types
type ActivityStreamsObject string type ActivityStreamsObject string

View File

@ -16,9 +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 model package gtsmodel
import "github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
// Application represents an application that can perform actions on behalf of a user. // Application represents an application that can perform actions on behalf of a user.
// It is used to authorize tokens etc, and is associated with an oauth client id in the database. // It is used to authorize tokens etc, and is associated with an oauth client id in the database.
@ -40,23 +38,3 @@ type Application struct {
// a vapid key generated for this app when it was created // a vapid key generated for this app when it was created
VapidKey string VapidKey string
} }
// ToMastoSensitive returns this application as a mastodon api type, ready for serialization
func (a *Application) ToMastoSensitive() *mastotypes.Application {
return &mastotypes.Application{
ID: a.ID,
Name: a.Name,
Website: a.Website,
RedirectURI: a.RedirectURI,
ClientID: a.ClientID,
ClientSecret: a.ClientSecret,
VapidKey: a.VapidKey,
}
}
func (a *Application) ToMastoPublic() *mastotypes.Application {
return &mastotypes.Application{
Name: a.Name,
Website: a.Website,
}
}

View File

@ -1,4 +1,4 @@
package model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import ( import (
"time" "time"
@ -29,7 +29,9 @@ type MediaAttachment struct {
ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"` ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"`
// ID of the status to which this is attached // ID of the status to which this is attached
StatusID string StatusID string
// Where can the attachment be retrieved on a remote server // Where can the attachment be retrieved on *this* server
URL string
// Where can the attachment be retrieved on a remote server (empty for local media)
RemoteURL string RemoteURL string
// When was the attachment created // When was the attachment created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
@ -81,7 +83,9 @@ type Thumbnail struct {
FileSize int FileSize int
// When was the file last updated // When was the file last updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"` UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
// What is the remote URL of the thumbnail // What is the URL of the thumbnail on the local server
URL string
// What is the remote URL of the thumbnail (empty for local media)
RemoteURL string RemoteURL string
} }
@ -106,11 +110,13 @@ const (
// FileTypeImage is for jpegs and pngs // FileTypeImage is for jpegs and pngs
FileTypeImage FileType = "image" FileTypeImage FileType = "image"
// FileTypeGif is for native gifs and soundless videos that have been converted to gifs // FileTypeGif is for native gifs and soundless videos that have been converted to gifs
FileTypeGif FileType = "gif" FileTypeGif FileType = "gifv"
// FileTypeAudio is for audio-only files (no video) // FileTypeAudio is for audio-only files (no video)
FileTypeAudio FileType = "audio" FileTypeAudio FileType = "audio"
// FileTypeVideo is for files with audio + visual // FileTypeVideo is for files with audio + visual
FileTypeVideo FileType = "video" FileTypeVideo FileType = "video"
// FileTypeUnknown is for unknown file types (surprise surprise!)
FileTypeUnknown FileType = "unknown"
) )
// FileMeta describes metadata about the actual contents of the file. // FileMeta describes metadata about the actual contents of the file.
@ -119,7 +125,7 @@ type FileMeta struct {
Small Small Small Small
} }
// Small implements SmallMeta and can be used for a thumbnail of any media type // Small can be used for a thumbnail of any media type
type Small struct { type Small struct {
Width int Width int
Height int Height int
@ -127,7 +133,7 @@ type Small struct {
Aspect float64 Aspect float64
} }
// ImageOriginal implements OriginalMeta for still images // Original can be used for original metadata for any media type
type Original struct { type Original struct {
Width int Width int
Height int Height int

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -0,0 +1,21 @@
/*
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 gtsmodel

View File

@ -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 model package gtsmodel
import "time" import "time"
@ -40,6 +40,8 @@ type Status struct {
AccountID string AccountID string
// id of the status this status is a reply to // id of the status this status is a reply to
InReplyToID string InReplyToID string
// id of the account that this status replies to
InReplyToAccountID string
// id of the status this status is a boost of // id of the status this status is a boost of
BoostOfID string BoostOfID string
// cw string for this status // cw string for this status

View File

@ -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 model package gtsmodel
import "time" import "time"

View File

@ -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 model package gtsmodel
import ( import (
"net" "net"

View File

@ -6,9 +6,7 @@ import (
context "context" context "context"
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
mastotypes "github.com/superseriousbusiness/gotosocial/pkg/mastotypes" gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
model "github.com/superseriousbusiness/gotosocial/internal/db/model"
net "net" net "net"
@ -20,52 +18,6 @@ type MockDB struct {
mock.Mock mock.Mock
} }
// AccountToMastoPublic provides a mock function with given fields: account
func (_m *MockDB) AccountToMastoPublic(account *model.Account) (*mastotypes.Account, error) {
ret := _m.Called(account)
var r0 *mastotypes.Account
if rf, ok := ret.Get(0).(func(*model.Account) *mastotypes.Account); ok {
r0 = rf(account)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Account)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.Account) error); ok {
r1 = rf(account)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AccountToMastoSensitive provides a mock function with given fields: account
func (_m *MockDB) AccountToMastoSensitive(account *model.Account) (*mastotypes.Account, error) {
ret := _m.Called(account)
var r0 *mastotypes.Account
if rf, ok := ret.Get(0).(func(*model.Account) *mastotypes.Account); ok {
r0 = rf(account)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Account)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.Account) error); ok {
r1 = rf(account)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Blocked provides a mock function with given fields: account1, account2 // Blocked provides a mock function with given fields: account1, account2
func (_m *MockDB) Blocked(account1 string, account2 string) (bool, error) { func (_m *MockDB) Blocked(account1 string, account2 string) (bool, error) {
ret := _m.Called(account1, account2) ret := _m.Called(account1, account2)
@ -144,15 +96,15 @@ func (_m *MockDB) DropTable(i interface{}) error {
} }
// EmojiStringsToEmojis provides a mock function with given fields: emojis, originAccountID, statusID // EmojiStringsToEmojis provides a mock function with given fields: emojis, originAccountID, statusID
func (_m *MockDB) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*model.Emoji, error) { func (_m *MockDB) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
ret := _m.Called(emojis, originAccountID, statusID) ret := _m.Called(emojis, originAccountID, statusID)
var r0 []*model.Emoji var r0 []*gtsmodel.Emoji
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Emoji); ok { if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Emoji); ok {
r0 = rf(emojis, originAccountID, statusID) r0 = rf(emojis, originAccountID, statusID)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Emoji) r0 = ret.Get(0).([]*gtsmodel.Emoji)
} }
} }
@ -183,11 +135,11 @@ func (_m *MockDB) Federation() pub.Database {
} }
// GetAccountByUserID provides a mock function with given fields: userID, account // GetAccountByUserID provides a mock function with given fields: userID, account
func (_m *MockDB) GetAccountByUserID(userID string, account *model.Account) error { func (_m *MockDB) GetAccountByUserID(userID string, account *gtsmodel.Account) error {
ret := _m.Called(userID, account) ret := _m.Called(userID, account)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *model.Account) error); ok { if rf, ok := ret.Get(0).(func(string, *gtsmodel.Account) error); ok {
r0 = rf(userID, account) r0 = rf(userID, account)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -211,11 +163,11 @@ func (_m *MockDB) GetAll(i interface{}) error {
} }
// GetAvatarForAccountID provides a mock function with given fields: avatar, accountID // GetAvatarForAccountID provides a mock function with given fields: avatar, accountID
func (_m *MockDB) GetAvatarForAccountID(avatar *model.MediaAttachment, accountID string) error { func (_m *MockDB) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error {
ret := _m.Called(avatar, accountID) ret := _m.Called(avatar, accountID)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(*model.MediaAttachment, string) error); ok { if rf, ok := ret.Get(0).(func(*gtsmodel.MediaAttachment, string) error); ok {
r0 = rf(avatar, accountID) r0 = rf(avatar, accountID)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -239,11 +191,11 @@ func (_m *MockDB) GetByID(id string, i interface{}) error {
} }
// GetFollowRequestsForAccountID provides a mock function with given fields: accountID, followRequests // GetFollowRequestsForAccountID provides a mock function with given fields: accountID, followRequests
func (_m *MockDB) GetFollowRequestsForAccountID(accountID string, followRequests *[]model.FollowRequest) error { func (_m *MockDB) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error {
ret := _m.Called(accountID, followRequests) ret := _m.Called(accountID, followRequests)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *[]model.FollowRequest) error); ok { if rf, ok := ret.Get(0).(func(string, *[]gtsmodel.FollowRequest) error); ok {
r0 = rf(accountID, followRequests) r0 = rf(accountID, followRequests)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -253,11 +205,11 @@ func (_m *MockDB) GetFollowRequestsForAccountID(accountID string, followRequests
} }
// GetFollowersByAccountID provides a mock function with given fields: accountID, followers // GetFollowersByAccountID provides a mock function with given fields: accountID, followers
func (_m *MockDB) GetFollowersByAccountID(accountID string, followers *[]model.Follow) error { func (_m *MockDB) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error {
ret := _m.Called(accountID, followers) ret := _m.Called(accountID, followers)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *[]model.Follow) error); ok { if rf, ok := ret.Get(0).(func(string, *[]gtsmodel.Follow) error); ok {
r0 = rf(accountID, followers) r0 = rf(accountID, followers)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -267,11 +219,11 @@ func (_m *MockDB) GetFollowersByAccountID(accountID string, followers *[]model.F
} }
// GetFollowingByAccountID provides a mock function with given fields: accountID, following // GetFollowingByAccountID provides a mock function with given fields: accountID, following
func (_m *MockDB) GetFollowingByAccountID(accountID string, following *[]model.Follow) error { func (_m *MockDB) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error {
ret := _m.Called(accountID, following) ret := _m.Called(accountID, following)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *[]model.Follow) error); ok { if rf, ok := ret.Get(0).(func(string, *[]gtsmodel.Follow) error); ok {
r0 = rf(accountID, following) r0 = rf(accountID, following)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -281,11 +233,11 @@ func (_m *MockDB) GetFollowingByAccountID(accountID string, following *[]model.F
} }
// GetHeaderForAccountID provides a mock function with given fields: header, accountID // GetHeaderForAccountID provides a mock function with given fields: header, accountID
func (_m *MockDB) GetHeaderForAccountID(header *model.MediaAttachment, accountID string) error { func (_m *MockDB) GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error {
ret := _m.Called(header, accountID) ret := _m.Called(header, accountID)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(*model.MediaAttachment, string) error); ok { if rf, ok := ret.Get(0).(func(*gtsmodel.MediaAttachment, string) error); ok {
r0 = rf(header, accountID) r0 = rf(header, accountID)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -295,11 +247,11 @@ func (_m *MockDB) GetHeaderForAccountID(header *model.MediaAttachment, accountID
} }
// GetLastStatusForAccountID provides a mock function with given fields: accountID, status // GetLastStatusForAccountID provides a mock function with given fields: accountID, status
func (_m *MockDB) GetLastStatusForAccountID(accountID string, status *model.Status) error { func (_m *MockDB) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error {
ret := _m.Called(accountID, status) ret := _m.Called(accountID, status)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *model.Status) error); ok { if rf, ok := ret.Get(0).(func(string, *gtsmodel.Status) error); ok {
r0 = rf(accountID, status) r0 = rf(accountID, status)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -309,11 +261,11 @@ func (_m *MockDB) GetLastStatusForAccountID(accountID string, status *model.Stat
} }
// GetStatusesByAccountID provides a mock function with given fields: accountID, statuses // GetStatusesByAccountID provides a mock function with given fields: accountID, statuses
func (_m *MockDB) GetStatusesByAccountID(accountID string, statuses *[]model.Status) error { func (_m *MockDB) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error {
ret := _m.Called(accountID, statuses) ret := _m.Called(accountID, statuses)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *[]model.Status) error); ok { if rf, ok := ret.Get(0).(func(string, *[]gtsmodel.Status) error); ok {
r0 = rf(accountID, statuses) r0 = rf(accountID, statuses)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -323,11 +275,11 @@ func (_m *MockDB) GetStatusesByAccountID(accountID string, statuses *[]model.Sta
} }
// GetStatusesByTimeDescending provides a mock function with given fields: accountID, statuses, limit // GetStatusesByTimeDescending provides a mock function with given fields: accountID, statuses, limit
func (_m *MockDB) GetStatusesByTimeDescending(accountID string, statuses *[]model.Status, limit int) error { func (_m *MockDB) GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error {
ret := _m.Called(accountID, statuses, limit) ret := _m.Called(accountID, statuses, limit)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(string, *[]model.Status, int) error); ok { if rf, ok := ret.Get(0).(func(string, *[]gtsmodel.Status, int) error); ok {
r0 = rf(accountID, statuses, limit) r0 = rf(accountID, statuses, limit)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -393,15 +345,15 @@ func (_m *MockDB) IsUsernameAvailable(username string) error {
} }
// MentionStringsToMentions provides a mock function with given fields: targetAccounts, originAccountID, statusID // MentionStringsToMentions provides a mock function with given fields: targetAccounts, originAccountID, statusID
func (_m *MockDB) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*model.Mention, error) { func (_m *MockDB) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) {
ret := _m.Called(targetAccounts, originAccountID, statusID) ret := _m.Called(targetAccounts, originAccountID, statusID)
var r0 []*model.Mention var r0 []*gtsmodel.Mention
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Mention); ok { if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Mention); ok {
r0 = rf(targetAccounts, originAccountID, statusID) r0 = rf(targetAccounts, originAccountID, statusID)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Mention) r0 = ret.Get(0).([]*gtsmodel.Mention)
} }
} }
@ -416,15 +368,15 @@ func (_m *MockDB) MentionStringsToMentions(targetAccounts []string, originAccoun
} }
// NewSignup provides a mock function with given fields: username, reason, requireApproval, email, password, signUpIP, locale, appID // NewSignup provides a mock function with given fields: username, reason, requireApproval, email, password, signUpIP, locale, appID
func (_m *MockDB) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*model.User, error) { func (_m *MockDB) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*gtsmodel.User, error) {
ret := _m.Called(username, reason, requireApproval, email, password, signUpIP, locale, appID) ret := _m.Called(username, reason, requireApproval, email, password, signUpIP, locale, appID)
var r0 *model.User var r0 *gtsmodel.User
if rf, ok := ret.Get(0).(func(string, string, bool, string, string, net.IP, string, string) *model.User); ok { if rf, ok := ret.Get(0).(func(string, string, bool, string, string, net.IP, string, string) *gtsmodel.User); ok {
r0 = rf(username, reason, requireApproval, email, password, signUpIP, locale, appID) r0 = rf(username, reason, requireApproval, email, password, signUpIP, locale, appID)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.User) r0 = ret.Get(0).(*gtsmodel.User)
} }
} }
@ -453,11 +405,11 @@ func (_m *MockDB) Put(i interface{}) error {
} }
// SetHeaderOrAvatarForAccountID provides a mock function with given fields: mediaAttachment, accountID // SetHeaderOrAvatarForAccountID provides a mock function with given fields: mediaAttachment, accountID
func (_m *MockDB) SetHeaderOrAvatarForAccountID(mediaAttachment *model.MediaAttachment, accountID string) error { func (_m *MockDB) SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error {
ret := _m.Called(mediaAttachment, accountID) ret := _m.Called(mediaAttachment, accountID)
var r0 error var r0 error
if rf, ok := ret.Get(0).(func(*model.MediaAttachment, string) error); ok { if rf, ok := ret.Get(0).(func(*gtsmodel.MediaAttachment, string) error); ok {
r0 = rf(mediaAttachment, accountID) r0 = rf(mediaAttachment, accountID)
} else { } else {
r0 = ret.Error(0) r0 = ret.Error(0)
@ -481,15 +433,15 @@ func (_m *MockDB) Stop(ctx context.Context) error {
} }
// TagStringsToTags provides a mock function with given fields: tags, originAccountID, statusID // TagStringsToTags provides a mock function with given fields: tags, originAccountID, statusID
func (_m *MockDB) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*model.Tag, error) { func (_m *MockDB) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
ret := _m.Called(tags, originAccountID, statusID) ret := _m.Called(tags, originAccountID, statusID)
var r0 []*model.Tag var r0 []*gtsmodel.Tag
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Tag); ok { if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Tag); ok {
r0 = rf(tags, originAccountID, statusID) r0 = rf(tags, originAccountID, statusID)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Tag) r0 = ret.Get(0).([]*gtsmodel.Tag)
} }
} }

View File

@ -36,9 +36,9 @@ import (
"github.com/go-pg/pg/v10/orm" "github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@ -214,9 +214,9 @@ func (ps *postgresService) IsHealthy(ctx context.Context) error {
func (ps *postgresService) CreateSchema(ctx context.Context) error { func (ps *postgresService) CreateSchema(ctx context.Context) error {
models := []interface{}{ models := []interface{}{
(*model.Account)(nil), (*gtsmodel.Account)(nil),
(*model.Status)(nil), (*gtsmodel.Status)(nil),
(*model.User)(nil), (*gtsmodel.User)(nil),
} }
ps.log.Info("creating db schema") ps.log.Info("creating db schema")
@ -312,8 +312,8 @@ func (ps *postgresService) DeleteWhere(key string, value interface{}, i interfac
HANDY SHORTCUTS HANDY SHORTCUTS
*/ */
func (ps *postgresService) GetAccountByUserID(userID string, account *model.Account) error { func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.Account) error {
user := &model.User{ user := &gtsmodel.User{
ID: userID, ID: userID,
} }
if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil { if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil {
@ -331,7 +331,7 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *model.Acco
return nil return nil
} }
func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]model.FollowRequest) error { func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error {
if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil { if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -341,7 +341,7 @@ func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, follo
return nil return nil
} }
func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]model.Follow) error { func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error {
if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil { if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -351,7 +351,7 @@ func (ps *postgresService) GetFollowingByAccountID(accountID string, following *
return nil return nil
} }
func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]model.Follow) error { func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error {
if err := ps.conn.Model(followers).Where("target_account_id = ?", accountID).Select(); err != nil { if err := ps.conn.Model(followers).Where("target_account_id = ?", accountID).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -361,7 +361,7 @@ func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *
return nil return nil
} }
func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]model.Status) error { func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error {
if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil { if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -371,7 +371,7 @@ func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]
return nil return nil
} }
func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuses *[]model.Status, limit int) error { func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error {
q := ps.conn.Model(statuses).Order("created_at DESC") q := ps.conn.Model(statuses).Order("created_at DESC")
if limit != 0 { if limit != 0 {
q = q.Limit(limit) q = q.Limit(limit)
@ -388,7 +388,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
return nil return nil
} }
func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *model.Status) error { func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error {
if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil { if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -403,7 +403,7 @@ func (ps *postgresService) IsUsernameAvailable(username string) error {
// if no error we fail because it means we found something // if no error we fail because it means we found something
// if error but it's not pg.ErrNoRows then we fail // if error but it's not pg.ErrNoRows then we fail
// if err is pg.ErrNoRows we're good, we found nothing so continue // if err is pg.ErrNoRows we're good, we found nothing so continue
if err := ps.conn.Model(&model.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { if err := ps.conn.Model(&gtsmodel.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil {
return fmt.Errorf("username %s already in use", username) return fmt.Errorf("username %s already in use", username)
} else if err != pg.ErrNoRows { } else if err != pg.ErrNoRows {
return fmt.Errorf("db error: %s", err) return fmt.Errorf("db error: %s", err)
@ -420,7 +420,7 @@ func (ps *postgresService) IsEmailAvailable(email string) error {
domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @
// check if the email domain is blocked // check if the email domain is blocked
if err := ps.conn.Model(&model.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil { if err := ps.conn.Model(&gtsmodel.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil {
// fail because we found something // fail because we found something
return fmt.Errorf("email domain %s is blocked", domain) return fmt.Errorf("email domain %s is blocked", domain)
} else if err != pg.ErrNoRows { } else if err != pg.ErrNoRows {
@ -429,7 +429,7 @@ func (ps *postgresService) IsEmailAvailable(email string) error {
} }
// check if this email is associated with a user already // check if this email is associated with a user already
if err := ps.conn.Model(&model.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { if err := ps.conn.Model(&gtsmodel.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil {
// fail because we found something // fail because we found something
return fmt.Errorf("email %s already in use", email) return fmt.Errorf("email %s already in use", email)
} else if err != pg.ErrNoRows { } else if err != pg.ErrNoRows {
@ -439,7 +439,7 @@ func (ps *postgresService) IsEmailAvailable(email string) error {
return nil return nil
} }
func (ps *postgresService) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*model.User, error) { func (ps *postgresService) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string) (*gtsmodel.User, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048) key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
ps.log.Errorf("error creating new rsa key: %s", err) ps.log.Errorf("error creating new rsa key: %s", err)
@ -448,14 +448,14 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
uris := util.GenerateURIs(username, ps.config.Protocol, ps.config.Host) uris := util.GenerateURIs(username, ps.config.Protocol, ps.config.Host)
a := &model.Account{ a := &gtsmodel.Account{
Username: username, Username: username,
DisplayName: username, DisplayName: username,
Reason: reason, Reason: reason,
URL: uris.UserURL, URL: uris.UserURL,
PrivateKey: key, PrivateKey: key,
PublicKey: &key.PublicKey, PublicKey: &key.PublicKey,
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
URI: uris.UserURI, URI: uris.UserURI,
InboxURL: uris.InboxURI, InboxURL: uris.InboxURI,
OutboxURL: uris.OutboxURI, OutboxURL: uris.OutboxURI,
@ -470,7 +470,7 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
if err != nil { if err != nil {
return nil, fmt.Errorf("error hashing password: %s", err) return nil, fmt.Errorf("error hashing password: %s", err)
} }
u := &model.User{ u := &gtsmodel.User{
AccountID: a.ID, AccountID: a.ID,
EncryptedPassword: string(pw), EncryptedPassword: string(pw),
SignUpIP: signUpIP, SignUpIP: signUpIP,
@ -486,12 +486,12 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
return u, nil return u, nil
} }
func (ps *postgresService) SetHeaderOrAvatarForAccountID(mediaAttachment *model.MediaAttachment, accountID string) error { func (ps *postgresService) SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error {
_, err := ps.conn.Model(mediaAttachment).Insert() _, err := ps.conn.Model(mediaAttachment).Insert()
return err return err
} }
func (ps *postgresService) GetHeaderForAccountID(header *model.MediaAttachment, accountID string) error { func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachment, accountID string) error {
if err := ps.conn.Model(header).Where("account_id = ?", accountID).Where("header = ?", true).Select(); err != nil { if err := ps.conn.Model(header).Where("account_id = ?", accountID).Where("header = ?", true).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -501,7 +501,7 @@ func (ps *postgresService) GetHeaderForAccountID(header *model.MediaAttachment,
return nil return nil
} }
func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment, accountID string) error { func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error {
if err := ps.conn.Model(avatar).Where("account_id = ?", accountID).Where("avatar = ?", true).Select(); err != nil { if err := ps.conn.Model(avatar).Where("account_id = ?", accountID).Where("avatar = ?", true).Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
@ -513,12 +513,13 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment,
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) { func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
var blocked bool var blocked bool
if err := ps.conn.Model(&model.Block{}). if err := ps.conn.Model(&gtsmodel.Block{}).
Where("account_id = ?", account1).Where("target_account_id = ?", account2). Where("account_id = ?", account1).Where("target_account_id = ?", account2).
WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2). WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2).
Select(); err != nil { Select(); err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
blocked = false blocked = false
return blocked, nil
} else { } else {
return blocked, err return blocked, err
} }
@ -535,7 +536,7 @@ func (ps *postgresService) Blocked(account1 string, account2 string) (bool, erro
// The resulting account fits the specifications for the path /api/v1/accounts/verify_credentials, as described here: // The resulting account fits the specifications for the path /api/v1/accounts/verify_credentials, as described here:
// https://docs.joinmastodon.org/methods/accounts/. Note that it's *sensitive* because it's only meant to be exposed to the user // https://docs.joinmastodon.org/methods/accounts/. Note that it's *sensitive* because it's only meant to be exposed to the user
// that the account actually belongs to. // that the account actually belongs to.
func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotypes.Account, error) { func (ps *postgresService) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) {
// we can build this sensitive account easily by first getting the public account.... // we can build this sensitive account easily by first getting the public account....
mastoAccount, err := ps.AccountToMastoPublic(a) mastoAccount, err := ps.AccountToMastoPublic(a)
if err != nil { if err != nil {
@ -545,7 +546,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
// then adding the Source object to it... // then adding the Source object to it...
// check pending follow requests aimed at this account // check pending follow requests aimed at this account
fr := []model.FollowRequest{} fr := []gtsmodel.FollowRequest{}
if err := ps.GetFollowRequestsForAccountID(a.ID, &fr); err != nil { if err := ps.GetFollowRequestsForAccountID(a.ID, &fr); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting follow requests: %s", err) return nil, fmt.Errorf("error getting follow requests: %s", err)
@ -568,9 +569,9 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
return mastoAccount, nil return mastoAccount, nil
} }
func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.Account, error) { func (ps *postgresService) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) {
// count followers // count followers
followers := []model.Follow{} followers := []gtsmodel.Follow{}
if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil { if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting followers: %s", err) return nil, fmt.Errorf("error getting followers: %s", err)
@ -582,7 +583,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
} }
// count following // count following
following := []model.Follow{} following := []gtsmodel.Follow{}
if err := ps.GetFollowingByAccountID(a.ID, &following); err != nil { if err := ps.GetFollowingByAccountID(a.ID, &following); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting following: %s", err) return nil, fmt.Errorf("error getting following: %s", err)
@ -594,7 +595,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
} }
// count statuses // count statuses
statuses := []model.Status{} statuses := []gtsmodel.Status{}
if err := ps.GetStatusesByAccountID(a.ID, &statuses); err != nil { if err := ps.GetStatusesByAccountID(a.ID, &statuses); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last statuses: %s", err) return nil, fmt.Errorf("error getting last statuses: %s", err)
@ -606,7 +607,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
} }
// check when the last status was // check when the last status was
lastStatus := &model.Status{} lastStatus := &gtsmodel.Status{}
if err := ps.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { if err := ps.GetLastStatusForAccountID(a.ID, lastStatus); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last status: %s", err) return nil, fmt.Errorf("error getting last status: %s", err)
@ -618,7 +619,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
} }
// build the avatar and header URLs // build the avatar and header URLs
avi := &model.MediaAttachment{} avi := &gtsmodel.MediaAttachment{}
if err := ps.GetAvatarForAccountID(avi, a.ID); err != nil { if err := ps.GetAvatarForAccountID(avi, a.ID); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting avatar: %s", err) return nil, fmt.Errorf("error getting avatar: %s", err)
@ -627,7 +628,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
aviURL := avi.File.Path aviURL := avi.File.Path
aviURLStatic := avi.Thumbnail.Path aviURLStatic := avi.Thumbnail.Path
header := &model.MediaAttachment{} header := &gtsmodel.MediaAttachment{}
if err := ps.GetHeaderForAccountID(avi, a.ID); err != nil { if err := ps.GetHeaderForAccountID(avi, a.ID); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting header: %s", err) return nil, fmt.Errorf("error getting header: %s", err)
@ -681,11 +682,12 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
}, nil }, nil
} }
func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*model.Mention, error) { func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) {
menchies := []*model.Mention{} menchies := []*gtsmodel.Mention{}
for _, a := range targetAccounts { for _, a := range targetAccounts {
// A mentioned account looks like "@test@example.org" -- we can guarantee this from the regex that targetAccounts should have been derived from. // A mentioned account looks like "@test@example.org" or just "@test" for a local account
// But we still need to do a bit of fiddling to get what we need here -- the username and domain. // -- we can guarantee this from the regex that targetAccounts should have been derived from.
// But we still need to do a bit of fiddling to get what we need here -- the username and domain (if given).
// 1. trim off the first @ // 1. trim off the first @
t := strings.TrimPrefix(a, "@") t := strings.TrimPrefix(a, "@")
@ -693,41 +695,51 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
// 2. split the username and domain // 2. split the username and domain
s := strings.Split(t, "@") s := strings.Split(t, "@")
// 3. it should *always* be length 2 so if it's not then something is seriously wrong // 3. if it's length 1 it's a local account, length 2 means remote, anything else means something is wrong
if len(s) != 2 { var local bool
return nil, fmt.Errorf("mentioned account format %s was not valid", a) switch len(s) {
case 1:
local = true
case 2:
local = false
default:
return nil, fmt.Errorf("mentioned account format '%s' was not valid", a)
}
var username, domain string
username = s[0]
if !local {
domain = s[1]
} }
username := s[0]
domain := s[1]
// 4. check we now have a proper username and domain // 4. check we now have a proper username and domain
if username == "" || domain == "" { if username == "" || (!local && domain == "") {
return nil, fmt.Errorf("username or domain for %s was nil", a) return nil, fmt.Errorf("username or domain for '%s' was nil", a)
} }
// okay we're good now, we can start pulling accounts out of the database // okay we're good now, we can start pulling accounts out of the database
mentionedAccount := &model.Account{} mentionedAccount := &gtsmodel.Account{}
var err error var err error
if domain == ps.config.Host { if local {
// local user -- should have a null domain // local user -- should have a null domain
err = ps.conn.Model(mentionedAccount).Where("id = ?", username).Where("domain = null").Select() err = ps.conn.Model(mentionedAccount).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select()
} else { } else {
// remote user -- should have domain defined // remote user -- should have domain defined
err = ps.conn.Model(mentionedAccount).Where("id = ?", username).Where("domain = ?", domain).Select() err = ps.conn.Model(mentionedAccount).Where("username = ?", username).Where("? = ?", pg.Ident("domain"), domain).Select()
} }
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
// no result found for this username/domain so just don't include it as a mencho and carry on about our business // no result found for this username/domain so just don't include it as a mencho and carry on about our business
ps.log.Debugf("no account found with username %s and domain %s, skipping it", username, domain) ps.log.Debugf("no account found with username '%s' and domain '%s', skipping it", username, domain)
continue continue
} }
// a serious error has happened so bail // a serious error has happened so bail
return nil, fmt.Errorf("error getting account with username %s and domain %s: %s", username, domain, err) return nil, fmt.Errorf("error getting account with username '%s' and domain '%s': %s", username, domain, err)
} }
// id, createdAt and updatedAt will be populated by the db, so we have everything we need! // id, createdAt and updatedAt will be populated by the db, so we have everything we need!
menchies = append(menchies, &model.Mention{ menchies = append(menchies, &gtsmodel.Mention{
StatusID: statusID, StatusID: statusID,
OriginAccountID: originAccountID, OriginAccountID: originAccountID,
TargetAccountID: mentionedAccount.ID, TargetAccountID: mentionedAccount.ID,
@ -737,26 +749,26 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
} }
// for now this function doesn't really use the database, but it's here because: // for now this function doesn't really use the database, but it's here because:
// A) it might later and // A) it probably will later and
// B) it's v. similar to MentionStringsToMentions // B) it's v. similar to MentionStringsToMentions
func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*model.Tag, error) { func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
newTags := []*model.Tag{} newTags := []*gtsmodel.Tag{}
for _, t := range tags { for _, t := range tags {
newTags = append(newTags, &model.Tag{ newTags = append(newTags, &gtsmodel.Tag{
Name: t, Name: t,
}) })
} }
return newTags, nil return newTags, nil
} }
func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*model.Emoji, error) { func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
newEmojis := []*model.Emoji{} newEmojis := []*gtsmodel.Emoji{}
for _, e := range emojis { for _, e := range emojis {
emoji := &model.Emoji{} emoji := &gtsmodel.Emoji{}
err := ps.conn.Model(emoji).Where("shortcode = ?", e).Where("visible_in_picker = true").Where("disabled = false").Select() err := ps.conn.Model(emoji).Where("shortcode = ?", e).Where("visible_in_picker = true").Where("disabled = false").Select()
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
// no result found for this username/domain so just don't include it as a mencho and carry on about our business // no result found for this username/domain so just don't include it as an emoji and carry on about our business
ps.log.Debugf("no emoji found with shortcode %s, skipping it", e) ps.log.Debugf("no emoji found with shortcode %s, skipping it", e)
continue continue
} }

View File

@ -21,7 +21,7 @@ package distributor
import ( import (
"github.com/go-fed/activity/pub" "github.com/go-fed/activity/pub"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
) )
// Distributor should be passed to api modules (see internal/apimodule/...). It is used for // Distributor should be passed to api modules (see internal/apimodule/...). It is used for
@ -97,13 +97,13 @@ func (d *distributor) Stop() error {
} }
type FromClientAPI struct { type FromClientAPI struct {
APObjectType model.ActivityStreamsObject APObjectType gtsmodel.ActivityStreamsObject
APActivityType model.ActivityStreamsActivity APActivityType gtsmodel.ActivityStreamsActivity
Activity interface{} Activity interface{}
} }
type ToClientAPI struct { type ToClientAPI struct {
APObjectType model.ActivityStreamsObject APObjectType gtsmodel.ActivityStreamsObject
APActivityType model.ActivityStreamsActivity APActivityType gtsmodel.ActivityStreamsActivity
Activity interface{} Activity interface{}
} }

View File

@ -35,6 +35,7 @@ import (
"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/federation" "github.com/superseriousbusiness/gotosocial/internal/federation"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
@ -62,10 +63,13 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
mediaHandler := media.New(c, dbService, storageBackend, log) mediaHandler := media.New(c, dbService, storageBackend, log)
oauthServer := oauth.New(dbService, log) oauthServer := oauth.New(dbService, log)
// build converters and util
mastoConverter := mastotypes.New(c, dbService)
// build client api modules // build client api modules
authModule := auth.New(oauthServer, dbService, log) authModule := auth.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, log) accountModule := account.New(c, dbService, oauthServer, mediaHandler, mastoConverter, log)
appsModule := app.New(oauthServer, dbService, log) appsModule := app.New(oauthServer, dbService, mastoConverter, log)
apiModules := []apimodule.ClientAPIModule{ apiModules := []apimodule.ClientAPIModule{
authModule, // this one has to go first so the other modules use its middleware authModule, // this one has to go first so the other modules use its middleware

View File

@ -0,0 +1,288 @@
/*
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 mastotypes
import (
"fmt"
"time"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// Converter is an interface for the common action of converting between mastotypes (frontend, serializable) models and internal gts models used in the database.
// It requires access to the database because many of the conversions require pulling out database entries and counting them etc.
type Converter interface {
// AccountToMastoSensitive takes a db model account as a param, and returns a populated mastotype account, or an error
// if something goes wrong. The returned account should be ready to serialize on an API level, and may have sensitive fields,
// so serve it only to an authorized user who should have permission to see it.
AccountToMastoSensitive(account *gtsmodel.Account) (*mastotypes.Account, error)
// AccountToMastoPublic takes a db model account as a param, and returns a populated mastotype account, or an error
// if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields.
// In other words, this is the public record that the server has of an account.
AccountToMastoPublic(account *gtsmodel.Account) (*mastotypes.Account, error)
// AppToMastoSensitive takes a db model application as a param, and returns a populated mastotype application, or an error
// if something goes wrong. The returned application should be ready to serialize on an API level, and may have sensitive fields
// (such as client id and client secret), so serve it only to an authorized user who should have permission to see it.
AppToMastoSensitive(application *gtsmodel.Application) (*mastotypes.Application, error)
// AppToMastoPublic takes a db model application as a param, and returns a populated mastotype application, or an error
// if something goes wrong. The returned application should be ready to serialize on an API level, and has sensitive
// fields sanitized so that it can be served to non-authorized accounts without revealing any private information.
AppToMastoPublic(application *gtsmodel.Application) (*mastotypes.Application, error)
AttachmentToMasto(attachment *gtsmodel.MediaAttachment) (mastotypes.Attachment, error)
MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error)
}
type converter struct {
config *config.Config
db db.DB
}
func New(config *config.Config, db db.DB) Converter {
return &converter{
config: config,
db: db,
}
}
func (c *converter) AccountToMastoSensitive(a *gtsmodel.Account) (*mastotypes.Account, error) {
// we can build this sensitive account easily by first getting the public account....
mastoAccount, err := c.AccountToMastoPublic(a)
if err != nil {
return nil, err
}
// then adding the Source object to it...
// check pending follow requests aimed at this account
fr := []gtsmodel.FollowRequest{}
if err := c.db.GetFollowRequestsForAccountID(a.ID, &fr); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting follow requests: %s", err)
}
}
var frc int
if fr != nil {
frc = len(fr)
}
mastoAccount.Source = &mastotypes.Source{
Privacy: util.ParseMastoVisFromGTSVis(a.Privacy),
Sensitive: a.Sensitive,
Language: a.Language,
Note: a.Note,
Fields: mastoAccount.Fields,
FollowRequestsCount: frc,
}
return mastoAccount, nil
}
func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) {
// count followers
followers := []gtsmodel.Follow{}
if err := c.db.GetFollowersByAccountID(a.ID, &followers); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting followers: %s", err)
}
}
var followersCount int
if followers != nil {
followersCount = len(followers)
}
// count following
following := []gtsmodel.Follow{}
if err := c.db.GetFollowingByAccountID(a.ID, &following); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting following: %s", err)
}
}
var followingCount int
if following != nil {
followingCount = len(following)
}
// count statuses
statuses := []gtsmodel.Status{}
if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last statuses: %s", err)
}
}
var statusesCount int
if statuses != nil {
statusesCount = len(statuses)
}
// check when the last status was
lastStatus := &gtsmodel.Status{}
if err := c.db.GetLastStatusForAccountID(a.ID, lastStatus); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last status: %s", err)
}
}
var lastStatusAt string
if lastStatus != nil {
lastStatusAt = lastStatus.CreatedAt.Format(time.RFC3339)
}
// build the avatar and header URLs
avi := &gtsmodel.MediaAttachment{}
if err := c.db.GetAvatarForAccountID(avi, a.ID); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting avatar: %s", err)
}
}
aviURL := avi.File.Path
aviURLStatic := avi.Thumbnail.Path
header := &gtsmodel.MediaAttachment{}
if err := c.db.GetHeaderForAccountID(avi, a.ID); err != nil {
if _, ok := err.(db.ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting header: %s", err)
}
}
headerURL := header.File.Path
headerURLStatic := header.Thumbnail.Path
// get the fields set on this account
fields := []mastotypes.Field{}
for _, f := range a.Fields {
mField := mastotypes.Field{
Name: f.Name,
Value: f.Value,
}
if !f.VerifiedAt.IsZero() {
mField.VerifiedAt = f.VerifiedAt.Format(time.RFC3339)
}
fields = append(fields, mField)
}
var acct string
if a.Domain != "" {
// this is a remote user
acct = fmt.Sprintf("%s@%s", a.Username, a.Domain)
} else {
// this is a local user
acct = a.Username
}
return &mastotypes.Account{
ID: a.ID,
Username: a.Username,
Acct: acct,
DisplayName: a.DisplayName,
Locked: a.Locked,
Bot: a.Bot,
CreatedAt: a.CreatedAt.Format(time.RFC3339),
Note: a.Note,
URL: a.URL,
Avatar: aviURL,
AvatarStatic: aviURLStatic,
Header: headerURL,
HeaderStatic: headerURLStatic,
FollowersCount: followersCount,
FollowingCount: followingCount,
StatusesCount: statusesCount,
LastStatusAt: lastStatusAt,
Emojis: nil, // TODO: implement this
Fields: fields,
}, nil
}
func (c *converter) AppToMastoSensitive(a *gtsmodel.Application) (*mastotypes.Application, error) {
return &mastotypes.Application{
ID: a.ID,
Name: a.Name,
Website: a.Website,
RedirectURI: a.RedirectURI,
ClientID: a.ClientID,
ClientSecret: a.ClientSecret,
VapidKey: a.VapidKey,
}, nil
}
func (c *converter) AppToMastoPublic(a *gtsmodel.Application) (*mastotypes.Application, error) {
return &mastotypes.Application{
Name: a.Name,
Website: a.Website,
}, nil
}
func (c *converter) AttachmentToMasto(a *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) {
return mastotypes.Attachment{
ID: a.ID,
Type: string(a.Type),
URL: a.URL,
PreviewURL: a.Thumbnail.URL,
RemoteURL: a.RemoteURL,
PreviewRemoteURL: a.Thumbnail.RemoteURL,
Meta: mastotypes.MediaMeta{
Original: mastotypes.MediaDimensions{
Width: a.FileMeta.Original.Width,
Height: a.FileMeta.Original.Height,
Size: fmt.Sprintf("%dx%d", a.FileMeta.Original.Width, a.FileMeta.Original.Height),
Aspect: float32(a.FileMeta.Original.Aspect),
},
Small: mastotypes.MediaDimensions{
Width: a.FileMeta.Small.Width,
Height: a.FileMeta.Small.Height,
Size: fmt.Sprintf("%dx%d", a.FileMeta.Small.Width, a.FileMeta.Small.Height),
Aspect: float32(a.FileMeta.Small.Aspect),
},
},
Description: a.Description,
Blurhash: a.Blurhash,
}, nil
}
func (c *converter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) {
target := &gtsmodel.Account{}
if err := c.db.GetByID(m.TargetAccountID, target); err != nil {
return mastotypes.Mention{}, err
}
var local bool
if target.Domain == "" {
local = true
}
var acct string
if local {
acct = fmt.Sprintf("@%s", target.Username)
} else {
acct = fmt.Sprintf("@%s@%s", target.Username, target.Domain)
}
return mastotypes.Mention{
ID: m.ID,
Username: target.Username,
URL: target.URL,
Acct: acct,
}, nil
}

View File

@ -45,8 +45,10 @@ type Attachment struct {
URL string `json:"url"` URL string `json:"url"`
// The location of a scaled-down preview of the attachment. // The location of a scaled-down preview of the attachment.
PreviewURL string `json:"preview_url"` PreviewURL string `json:"preview_url"`
// The location of the full-size original attachment on the remote website. // The location of the full-size original attachment on the remote server.
RemoteURL string `json:"remote_url,omitempty"` RemoteURL string `json:"remote_url,omitempty"`
// The location of a scaled-down preview of the attachment on the remote server.
PreviewRemoteURL string `json:"preview_remote_url,omitempty"`
// A shorter URL for the attachment. // A shorter URL for the attachment.
TextURL string `json:"text_url,omitempty"` TextURL string `json:"text_url,omitempty"`
// Metadata returned by Paperclip. // Metadata returned by Paperclip.

View File

@ -0,0 +1,106 @@
// Code generated by mockery v2.7.4. DO NOT EDIT.
package mastotypes
import (
mock "github.com/stretchr/testify/mock"
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
)
// MockConverter is an autogenerated mock type for the Converter type
type MockConverter struct {
mock.Mock
}
// AccountToMastoPublic provides a mock function with given fields: account
func (_m *MockConverter) AccountToMastoPublic(account *gtsmodel.Account) (*mastotypes.Account, error) {
ret := _m.Called(account)
var r0 *mastotypes.Account
if rf, ok := ret.Get(0).(func(*gtsmodel.Account) *mastotypes.Account); ok {
r0 = rf(account)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Account)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.Account) error); ok {
r1 = rf(account)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AccountToMastoSensitive provides a mock function with given fields: account
func (_m *MockConverter) AccountToMastoSensitive(account *gtsmodel.Account) (*mastotypes.Account, error) {
ret := _m.Called(account)
var r0 *mastotypes.Account
if rf, ok := ret.Get(0).(func(*gtsmodel.Account) *mastotypes.Account); ok {
r0 = rf(account)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Account)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.Account) error); ok {
r1 = rf(account)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AppToMastoPublic provides a mock function with given fields: application
func (_m *MockConverter) AppToMastoPublic(application *gtsmodel.Application) (*mastotypes.Application, error) {
ret := _m.Called(application)
var r0 *mastotypes.Application
if rf, ok := ret.Get(0).(func(*gtsmodel.Application) *mastotypes.Application); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AppToMastoSensitive provides a mock function with given fields: application
func (_m *MockConverter) AppToMastoSensitive(application *gtsmodel.Application) (*mastotypes.Application, error) {
ret := _m.Called(application)
var r0 *mastotypes.Application
if rf, ok := ret.Get(0).(func(*gtsmodel.Application) *mastotypes.Application); ok {
r0 = rf(application)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*mastotypes.Application)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.Application) error); ok {
r1 = rf(application)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -28,7 +28,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
) )
@ -37,7 +37,7 @@ type MediaHandler interface {
// SetHeaderOrAvatarForAccountID takes a new header image for an account, checks it out, removes exif data from it, // SetHeaderOrAvatarForAccountID takes a new header image for an account, checks it out, removes exif data from it,
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image, // puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
// and then returns information to the caller about the new header. // and then returns information to the caller about the new header.
SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*model.MediaAttachment, error) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error)
} }
type mediaHandler struct { type mediaHandler struct {
@ -68,7 +68,7 @@ type HeaderInfo struct {
INTERFACE FUNCTIONS INTERFACE FUNCTIONS
*/ */
func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*model.MediaAttachment, error) { func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
l := mh.log.WithField("func", "SetHeaderForAccountID") l := mh.log.WithField("func", "SetHeaderForAccountID")
if headerOrAvi != "header" && headerOrAvi != "avatar" { if headerOrAvi != "header" && headerOrAvi != "avatar" {
@ -107,7 +107,7 @@ func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID stri
HELPER FUNCTIONS HELPER FUNCTIONS
*/ */
func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, headerOrAvi string, accountID string) (*model.MediaAttachment, error) { func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, headerOrAvi string, accountID string) (*gtsmodel.MediaAttachment, error) {
var isHeader bool var isHeader bool
var isAvatar bool var isAvatar bool
@ -152,34 +152,38 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
extension := strings.Split(contentType, "/")[1] extension := strings.Split(contentType, "/")[1]
newMediaID := uuid.NewString() newMediaID := uuid.NewString()
base := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath) URLbase := fmt.Sprintf("%s://%s%s", mh.config.StorageConfig.ServeProtocol, mh.config.StorageConfig.ServeHost, mh.config.StorageConfig.ServeBasePath)
originalURL := fmt.Sprintf("%s/%s/%s/original/%s.%s", URLbase, accountID, headerOrAvi, newMediaID, extension)
smallURL := fmt.Sprintf("%s/%s/%s/small/%s.%s", URLbase, accountID, headerOrAvi, newMediaID, extension)
// we store the original... // we store the original...
originalPath := fmt.Sprintf("%s/%s/%s/original/%s.%s", base, accountID, headerOrAvi, newMediaID, extension) originalPath := fmt.Sprintf("%s/%s/%s/original/%s.%s", mh.config.StorageConfig.BasePath, accountID, headerOrAvi, newMediaID, extension)
if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil { if err := mh.storage.StoreFileAt(originalPath, original.image); err != nil {
return nil, fmt.Errorf("storage error: %s", err) return nil, fmt.Errorf("storage error: %s", err)
} }
// and a thumbnail... // and a thumbnail...
smallPath := fmt.Sprintf("%s/%s/%s/small/%s.%s", base, accountID, headerOrAvi, newMediaID, extension) smallPath := fmt.Sprintf("%s/%s/%s/small/%s.%s", mh.config.StorageConfig.BasePath, accountID, headerOrAvi, newMediaID, extension)
if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil { if err := mh.storage.StoreFileAt(smallPath, small.image); err != nil {
return nil, fmt.Errorf("storage error: %s", err) return nil, fmt.Errorf("storage error: %s", err)
} }
ma := &model.MediaAttachment{ ma := &gtsmodel.MediaAttachment{
ID: newMediaID, ID: newMediaID,
StatusID: "", StatusID: "",
URL: originalURL,
RemoteURL: "", RemoteURL: "",
CreatedAt: time.Now(), CreatedAt: time.Now(),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
Type: model.FileTypeImage, Type: gtsmodel.FileTypeImage,
FileMeta: model.FileMeta{ FileMeta: gtsmodel.FileMeta{
Original: model.Original{ Original: gtsmodel.Original{
Width: original.width, Width: original.width,
Height: original.height, Height: original.height,
Size: original.size, Size: original.size,
Aspect: original.aspect, Aspect: original.aspect,
}, },
Small: model.Small{ Small: gtsmodel.Small{
Width: small.width, Width: small.width,
Height: small.height, Height: small.height,
Size: small.size, Size: small.size,
@ -191,17 +195,18 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
ScheduledStatusID: "", ScheduledStatusID: "",
Blurhash: original.blurhash, Blurhash: original.blurhash,
Processing: 2, Processing: 2,
File: model.File{ File: gtsmodel.File{
Path: originalPath, Path: originalPath,
ContentType: contentType, ContentType: contentType,
FileSize: len(original.image), FileSize: len(original.image),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
}, },
Thumbnail: model.Thumbnail{ Thumbnail: gtsmodel.Thumbnail{
Path: smallPath, Path: smallPath,
ContentType: contentType, ContentType: contentType,
FileSize: len(small.image), FileSize: len(small.image),
UpdatedAt: time.Now(), UpdatedAt: time.Now(),
URL: smallURL,
RemoteURL: "", RemoteURL: "",
}, },
Avatar: isAvatar, Avatar: isAvatar,

View File

@ -29,7 +29,7 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"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/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/storage" "github.com/superseriousbusiness/gotosocial/internal/storage"
) )
@ -108,8 +108,8 @@ func (suite *MediaTestSuite) TearDownSuite() {
func (suite *MediaTestSuite) SetupTest() { func (suite *MediaTestSuite) SetupTest() {
// create all the tables we might need in thie suite // create all the tables we might need in thie suite
models := []interface{}{ models := []interface{}{
&model.Account{}, &gtsmodel.Account{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.CreateTable(m); err != nil { if err := suite.db.CreateTable(m); err != nil {
@ -123,8 +123,8 @@ func (suite *MediaTestSuite) TearDownTest() {
// remove all the tables we might have used so it's clear for the next test // remove all the tables we might have used so it's clear for the next test
models := []interface{}{ models := []interface{}{
&model.Account{}, &gtsmodel.Account{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.DropTable(m); err != nil { if err := suite.db.DropTable(m); err != nil {

View File

@ -4,7 +4,7 @@ package media
import ( import (
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
model "github.com/superseriousbusiness/gotosocial/internal/db/model" gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
) )
// MockMediaHandler is an autogenerated mock type for the MediaHandler type // MockMediaHandler is an autogenerated mock type for the MediaHandler type
@ -13,15 +13,15 @@ type MockMediaHandler struct {
} }
// SetHeaderOrAvatarForAccountID provides a mock function with given fields: img, accountID, headerOrAvi // SetHeaderOrAvatarForAccountID provides a mock function with given fields: img, accountID, headerOrAvi
func (_m *MockMediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*model.MediaAttachment, error) { func (_m *MockMediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
ret := _m.Called(img, accountID, headerOrAvi) ret := _m.Called(img, accountID, headerOrAvi)
var r0 *model.MediaAttachment var r0 *gtsmodel.MediaAttachment
if rf, ok := ret.Get(0).(func([]byte, string, string) *model.MediaAttachment); ok { if rf, ok := ret.Get(0).(func([]byte, string, string) *gtsmodel.MediaAttachment); ok {
r0 = rf(img, accountID, headerOrAvi) r0 = rf(img, accountID, headerOrAvi)
} else { } else {
if ret.Get(0) != nil { if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.MediaAttachment) r0 = ret.Get(0).(*gtsmodel.MediaAttachment)
} }
} }

View File

@ -26,7 +26,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/oauth2/v4" "github.com/superseriousbusiness/oauth2/v4"
"github.com/superseriousbusiness/oauth2/v4/errors" "github.com/superseriousbusiness/oauth2/v4/errors"
"github.com/superseriousbusiness/oauth2/v4/manage" "github.com/superseriousbusiness/oauth2/v4/manage"
@ -34,6 +34,9 @@ import (
) )
const ( const (
// SessionAuthorizedToken is the key set in the gin context for the Token
// of a User who has successfully passed Bearer token authorization.
// The interface returned from grabbing this key should be parsed as oauth2.TokenInfo
SessionAuthorizedToken = "authorized_token" SessionAuthorizedToken = "authorized_token"
// SessionAuthorizedUser is the key set in the gin context for the id of // SessionAuthorizedUser is the key set in the gin context for the id of
// a User who has successfully passed Bearer token authorization. // a User who has successfully passed Bearer token authorization.
@ -65,9 +68,9 @@ type s struct {
type Authed struct { type Authed struct {
Token oauth2.TokenInfo Token oauth2.TokenInfo
Application *model.Application Application *gtsmodel.Application
User *model.User User *gtsmodel.User
Account *model.Account Account *gtsmodel.Account
} }
// GetAuthed is a convenience function for returning an Authed struct from a gin context. // GetAuthed is a convenience function for returning an Authed struct from a gin context.
@ -96,7 +99,7 @@ func GetAuthed(c *gin.Context) (*Authed, error) {
i, ok = ctx.Get(SessionAuthorizedApplication) i, ok = ctx.Get(SessionAuthorizedApplication)
if ok { if ok {
parsed, ok := i.(*model.Application) parsed, ok := i.(*gtsmodel.Application)
if !ok { if !ok {
return nil, errors.New("could not parse application from session context") return nil, errors.New("could not parse application from session context")
} }
@ -105,7 +108,7 @@ func GetAuthed(c *gin.Context) (*Authed, error) {
i, ok = ctx.Get(SessionAuthorizedUser) i, ok = ctx.Get(SessionAuthorizedUser)
if ok { if ok {
parsed, ok := i.(*model.User) parsed, ok := i.(*gtsmodel.User)
if !ok { if !ok {
return nil, errors.New("could not parse user from session context") return nil, errors.New("could not parse user from session context")
} }
@ -114,7 +117,7 @@ func GetAuthed(c *gin.Context) (*Authed, error) {
i, ok = ctx.Get(SessionAuthorizedAccount) i, ok = ctx.Get(SessionAuthorizedAccount)
if ok { if ok {
parsed, ok := i.(*model.Account) parsed, ok := i.(*gtsmodel.Account)
if !ok { if !ok {
return nil, errors.New("could not parse account from session context") return nil, errors.New("could not parse account from session context")
} }

View File

@ -21,8 +21,8 @@ package util
import ( import (
"fmt" "fmt"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes" mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
) )
type URIs struct { type URIs struct {
@ -64,16 +64,16 @@ func GenerateURIs(username string, protocol string, host string) *URIs {
} }
// ParseGTSVisFromMastoVis converts a mastodon visibility into its gts equivalent. // ParseGTSVisFromMastoVis converts a mastodon visibility into its gts equivalent.
func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility { func ParseGTSVisFromMastoVis(m mastotypes.Visibility) gtsmodel.Visibility {
switch m { switch m {
case mastotypes.VisibilityPublic: case mastotypes.VisibilityPublic:
return model.VisibilityPublic return gtsmodel.VisibilityPublic
case mastotypes.VisibilityUnlisted: case mastotypes.VisibilityUnlisted:
return model.VisibilityUnlocked return gtsmodel.VisibilityUnlocked
case mastotypes.VisibilityPrivate: case mastotypes.VisibilityPrivate:
return model.VisibilityFollowersOnly return gtsmodel.VisibilityFollowersOnly
case mastotypes.VisibilityDirect: case mastotypes.VisibilityDirect:
return model.VisibilityDirect return gtsmodel.VisibilityDirect
default: default:
break break
} }
@ -81,15 +81,15 @@ func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility {
} }
// ParseMastoVisFromGTSVis converts a gts visibility into its mastodon equivalent // ParseMastoVisFromGTSVis converts a gts visibility into its mastodon equivalent
func ParseMastoVisFromGTSVis(m model.Visibility) mastotypes.Visibility { func ParseMastoVisFromGTSVis(m gtsmodel.Visibility) mastotypes.Visibility {
switch m { switch m {
case model.VisibilityPublic: case gtsmodel.VisibilityPublic:
return mastotypes.VisibilityPublic return mastotypes.VisibilityPublic
case model.VisibilityUnlocked: case gtsmodel.VisibilityUnlocked:
return mastotypes.VisibilityUnlisted return mastotypes.VisibilityUnlisted
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly: case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly:
return mastotypes.VisibilityPrivate return mastotypes.VisibilityPrivate
case model.VisibilityDirect: case gtsmodel.VisibilityDirect:
return mastotypes.VisibilityDirect return mastotypes.VisibilityDirect
default: default:
break break

View File

@ -19,16 +19,13 @@
package util package util
import ( import (
"fmt"
"regexp" "regexp"
"strings" "strings"
) )
// To play around with these regexes, see: https://regex101.com/r/2km2EK/1
var ( var (
// mention regex can be played around with here: https://regex101.com/r/2km2EK/1 // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1
hostnameRegexString = `(?:(?:[a-zA-Z]{1})|(?:[a-zA-Z]{1}[a-zA-Z]{1})|(?:[a-zA-Z]{1}[0-9]{1})|(?:[0-9]{1}[a-zA-Z]{1})|(?:[a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\.(?:[a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,5}))` mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)`
mentionRegexString = fmt.Sprintf(`(?: |^|\W)(@[a-zA-Z0-9_]+@%s(?: |\n)`, hostnameRegexString)
mentionRegex = regexp.MustCompile(mentionRegexString) mentionRegex = regexp.MustCompile(mentionRegexString)
// hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1 // hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1
hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)` hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)`
@ -43,7 +40,7 @@ var (
// mentioned in that status. // mentioned in that status.
// //
// It will look for fully-qualified account names in the form "@user@example.org". // It will look for fully-qualified account names in the form "@user@example.org".
// Mentions that are just in the form "@username" will not be detected. // or the form "@username" for local users.
// The case of the returned mentions will be lowered, for consistency. // The case of the returned mentions will be lowered, for consistency.
func DeriveMentions(status string) []string { func DeriveMentions(status string) []string {
mentionedAccounts := []string{} mentionedAccounts := []string{}

View File

@ -36,16 +36,17 @@ func (suite *StatusTestSuite) TestDeriveMentionsOK() {
@someone_else@testing.best-horse.com can you confirm? @hello@test.lgbt @someone_else@testing.best-horse.com can you confirm? @hello@test.lgbt
@thiswontwork though! @NORWILL@THIS.one!! @thisisalocaluser ! @NORWILL@THIS.one!!
here is a duplicate mention: @hello@test.lgbt here is a duplicate mention: @hello@test.lgbt
` `
menchies := DeriveMentions(statusText) menchies := DeriveMentions(statusText)
assert.Len(suite.T(), menchies, 3) assert.Len(suite.T(), menchies, 4)
assert.Equal(suite.T(), "@dumpsterqueer@example.org", menchies[0]) assert.Equal(suite.T(), "@dumpsterqueer@example.org", menchies[0])
assert.Equal(suite.T(), "@someone_else@testing.best-horse.com", menchies[1]) assert.Equal(suite.T(), "@someone_else@testing.best-horse.com", menchies[1])
assert.Equal(suite.T(), "@hello@test.lgbt", menchies[2]) assert.Equal(suite.T(), "@hello@test.lgbt", menchies[2])
assert.Equal(suite.T(), "@thisisalocaluser", menchies[3])
} }
func (suite *StatusTestSuite) TestDeriveMentionsEmpty() { func (suite *StatusTestSuite) TestDeriveMentionsEmpty() {

View File

@ -2,23 +2,23 @@ package testrig
import ( import (
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
var testModels []interface{} = []interface{}{ var testModels []interface{} = []interface{}{
&model.Account{}, &gtsmodel.Account{},
&model.Application{}, &gtsmodel.Application{},
&model.Block{}, &gtsmodel.Block{},
&model.DomainBlock{}, &gtsmodel.DomainBlock{},
&model.EmailDomainBlock{}, &gtsmodel.EmailDomainBlock{},
&model.Follow{}, &gtsmodel.Follow{},
&model.FollowRequest{}, &gtsmodel.FollowRequest{},
&model.MediaAttachment{}, &gtsmodel.MediaAttachment{},
&model.Mention{}, &gtsmodel.Mention{},
&model.Status{}, &gtsmodel.Status{},
&model.Tag{}, &gtsmodel.Tag{},
&model.User{}, &gtsmodel.User{},
&oauth.Token{}, &oauth.Token{},
&oauth.Client{}, &oauth.Client{},
} }
@ -61,6 +61,12 @@ func StandardDBSetup(db db.DB) error {
} }
} }
for _, v := range TestStatuses() {
if err := db.Put(v); err != nil {
return err
}
}
return nil return nil
} }

View File

@ -6,20 +6,20 @@ import (
"net" "net"
"time" "time"
"github.com/superseriousbusiness/gotosocial/internal/db/model" "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
func TestTokens() map[string]*oauth.Token { func TestTokens() map[string]*oauth.Token {
tokens := map[string]*oauth.Token{ tokens := map[string]*oauth.Token{
"local_account_1": { "local_account_1": {
ID: "64cf4214-33ab-4220-b5ca-4a6a12263b20", ID: "64cf4214-33ab-4220-b5ca-4a6a12263b20",
ClientID: "73b48d42-029d-4487-80fc-329a5cf67869", ClientID: "73b48d42-029d-4487-80fc-329a5cf67869",
UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b", UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b",
RedirectURI: "http://localhost:8080", RedirectURI: "http://localhost:8080",
Scope: "read write follow push", Scope: "read write follow push",
Access: "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX", Access: "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
AccessCreateAt: time.Now(), AccessCreateAt: time.Now(),
AccessExpiresAt: time.Now().Add(72 * time.Hour), AccessExpiresAt: time.Now().Add(72 * time.Hour),
}, },
} }
@ -38,8 +38,8 @@ func TestClients() map[string]*oauth.Client {
return clients return clients
} }
func TestApplications() map[string]*model.Application { func TestApplications() map[string]*gtsmodel.Application {
apps := map[string]*model.Application{ apps := map[string]*gtsmodel.Application{
"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",
@ -54,8 +54,8 @@ func TestApplications() map[string]*model.Application {
return apps return apps
} }
func TestUsers() map[string]*model.User { func TestUsers() map[string]*gtsmodel.User {
users := map[string]*model.User{ users := map[string]*gtsmodel.User{
"unconfirmed_account": { "unconfirmed_account": {
ID: "0f7b1d24-1e49-4ee0-bc7e-fd87b7289eea", ID: "0f7b1d24-1e49-4ee0-bc7e-fd87b7289eea",
Email: "", Email: "",
@ -181,8 +181,8 @@ func TestUsers() map[string]*model.User {
return users return users
} }
func TestAccounts() map[string]*model.Account { func TestAccounts() map[string]*gtsmodel.Account {
accounts := map[string]*model.Account{ accounts := map[string]*gtsmodel.Account{
"unconfirmed_account": { "unconfirmed_account": {
ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d", ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d",
Username: "weed_lord420", Username: "weed_lord420",
@ -197,7 +197,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{}, HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "", HeaderRemoteURL: "",
DisplayName: "", DisplayName: "",
Fields: []model.Field{}, Fields: []gtsmodel.Field{},
Note: "", Note: "",
Memorial: false, Memorial: false,
MovedToAccountID: "", MovedToAccountID: "",
@ -207,7 +207,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.", Reason: "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
Locked: false, Locked: false,
Discoverable: false, Discoverable: false,
Privacy: model.VisibilityPublic, Privacy: gtsmodel.VisibilityPublic,
Sensitive: false, Sensitive: false,
Language: "en", Language: "en",
URI: "http://localhost:8080/users/weed_lord420", URI: "http://localhost:8080/users/weed_lord420",
@ -218,7 +218,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "", SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/weed_lord420/followers", FollowersURL: "http://localhost:8080/users/weed_lord420/followers",
FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured", FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "", AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
@ -242,7 +242,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{}, HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "", HeaderRemoteURL: "",
DisplayName: "", DisplayName: "",
Fields: []model.Field{}, Fields: []gtsmodel.Field{},
Note: "", Note: "",
Memorial: false, Memorial: false,
MovedToAccountID: "", MovedToAccountID: "",
@ -252,7 +252,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "", Reason: "",
Locked: false, Locked: false,
Discoverable: true, Discoverable: true,
Privacy: model.VisibilityPublic, Privacy: gtsmodel.VisibilityPublic,
Sensitive: false, Sensitive: false,
Language: "en", Language: "en",
URI: "http://localhost:8080/users/admin", URI: "http://localhost:8080/users/admin",
@ -263,7 +263,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "", SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/admin/followers", FollowersURL: "http://localhost:8080/users/admin/followers",
FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured", FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "", AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
@ -287,7 +287,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{}, HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "", HeaderRemoteURL: "",
DisplayName: "original zork (he/they)", DisplayName: "original zork (he/they)",
Fields: []model.Field{}, Fields: []gtsmodel.Field{},
Note: "hey yo this is my profile!", Note: "hey yo this is my profile!",
Memorial: false, Memorial: false,
MovedToAccountID: "", MovedToAccountID: "",
@ -297,7 +297,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "I wanna be on this damned webbed site so bad! Please! Wow", Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
Locked: false, Locked: false,
Discoverable: true, Discoverable: true,
Privacy: model.VisibilityPublic, Privacy: gtsmodel.VisibilityPublic,
Sensitive: false, Sensitive: false,
Language: "en", Language: "en",
URI: "http://localhost:8080/users/the_mighty_zork", URI: "http://localhost:8080/users/the_mighty_zork",
@ -308,7 +308,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "", SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers", FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers",
FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured", FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "", AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
@ -332,7 +332,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{}, HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "", HeaderRemoteURL: "",
DisplayName: "happy little turtle :3", DisplayName: "happy little turtle :3",
Fields: []model.Field{}, Fields: []gtsmodel.Field{},
Note: "i post about things that concern me", Note: "i post about things that concern me",
Memorial: false, Memorial: false,
MovedToAccountID: "", MovedToAccountID: "",
@ -342,7 +342,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "", Reason: "",
Locked: true, Locked: true,
Discoverable: false, Discoverable: false,
Privacy: model.VisibilityFollowersOnly, Privacy: gtsmodel.VisibilityFollowersOnly,
Sensitive: false, Sensitive: false,
Language: "en", Language: "en",
URI: "http://localhost:8080/users/1happyturtle", URI: "http://localhost:8080/users/1happyturtle",
@ -353,7 +353,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "", SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/1happyturtle/followers", FollowersURL: "http://localhost:8080/users/1happyturtle/followers",
FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured", FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "", AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
@ -378,7 +378,7 @@ func TestAccounts() map[string]*model.Account {
// HeaderUpdatedAt: time.Time{}, // HeaderUpdatedAt: time.Time{},
// HeaderRemoteURL: "", // HeaderRemoteURL: "",
DisplayName: "big gerald", DisplayName: "big gerald",
Fields: []model.Field{}, Fields: []gtsmodel.Field{},
Note: "i post about like, i dunno, stuff, or whatever!!!!", Note: "i post about like, i dunno, stuff, or whatever!!!!",
Memorial: false, Memorial: false,
MovedToAccountID: "", MovedToAccountID: "",
@ -397,7 +397,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "", SharedInboxURL: "",
FollowersURL: "https://fossbros-anonymous.io/users/foss_satan/followers", FollowersURL: "https://fossbros-anonymous.io/users/foss_satan/followers",
FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured", FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured",
ActorType: model.ActivityStreamsPerson, ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "", AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: nil, PublicKey: nil,
@ -440,53 +440,168 @@ func TestAccounts() map[string]*model.Account {
return accounts return accounts
} }
func TestStatuses() map[string]*model.Status { func TestStatuses() map[string]*gtsmodel.Status {
return map[string]*model.Status{ return map[string]*gtsmodel.Status{
"local_account_1_status_1": { "admin_account_status_1": {
ID: "91b1e795-74ff-4672-a4c4-476616710e2d", ID: "502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
URI: "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d", URI: "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
URL: "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d", URL: "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
Content: "hello everyone!", Content: "hello world! first post on the instance!",
CreatedAt: time.Now().Add(-47 * time.Hour), CreatedAt: time.Now().Add(-71 * time.Hour),
UpdatedAt: time.Now().Add(-47 * time.Hour), UpdatedAt: time.Now().Add(-71 * time.Hour),
Local: true, Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", AccountID: "0fb02eae-2214-473f-9667-0a43f22d75ff",
InReplyToID: "", InReplyToID: "",
BoostOfID: "", BoostOfID: "",
ContentWarning: "introduction post", ContentWarning: "",
Visibility: model.VisibilityPublic, Visibility: gtsmodel.VisibilityPublic,
Sensitive: true, Sensitive: false,
Language: "en", Language: "en",
VisibilityAdvanced: &model.VisibilityAdvanced{ VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true, Federated: true,
Boostable: true, Boostable: true,
Replyable: true, Replyable: true,
Likeable: true, Likeable: true,
}, },
ActivityStreamsType: model.ActivityStreamsNote, ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"admin_account_status_2": {
ID: "0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
URI: "http://localhost:8080/users/admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
URL: "http://localhost:8080/@admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
Content: "🐕🐕🐕🐕🐕",
CreatedAt: time.Now().Add(-70 * time.Hour),
UpdatedAt: time.Now().Add(-70 * time.Hour),
Local: true,
AccountID: "0fb02eae-2214-473f-9667-0a43f22d75ff",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "open to see some puppies",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
Replyable: true,
Likeable: true,
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_1": {
ID: "91b1e795-74ff-4672-a4c4-476616710e2d",
URI: "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
URL: "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
Content: "hello everyone!",
CreatedAt: time.Now().Add(-47 * time.Hour),
UpdatedAt: time.Now().Add(-47 * time.Hour),
Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "introduction post",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
Replyable: true,
Likeable: true,
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
}, },
"local_account_1_status_2": { "local_account_1_status_2": {
ID: "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c", ID: "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
URI: "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c", URI: "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
URL: "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c", URL: "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable", Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
CreatedAt: time.Now().Add(-47 * time.Hour), CreatedAt: time.Now().Add(-46 * time.Hour),
UpdatedAt: time.Now().Add(-47 * time.Hour), UpdatedAt: time.Now().Add(-46 * time.Hour),
Local: true, Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6", AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
InReplyToID: "", InReplyToID: "",
BoostOfID: "", BoostOfID: "",
ContentWarning: "", ContentWarning: "",
Visibility: model.VisibilityUnlocked, Visibility: gtsmodel.VisibilityUnlocked,
Sensitive: false, Sensitive: false,
Language: "en", Language: "en",
VisibilityAdvanced: &model.VisibilityAdvanced{ VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: false, Federated: false,
Boostable: true, Boostable: true,
Replyable: true, Replyable: true,
Likeable: true, Likeable: true,
}, },
ActivityStreamsType: model.ActivityStreamsNote, ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_1_status_3": {
ID: "5e41963f-8ab9-4147-9f00-52d56e19da65",
URI: "http://localhost:8080/users/the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
URL: "http://localhost:8080/@the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
CreatedAt: time.Now().Add(-45 * time.Hour),
UpdatedAt: time.Now().Add(-45 * time.Hour),
Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "test: you shouldn't be able to interact with this post in any way",
Visibility: gtsmodel.VisibilityMutualsOnly,
Sensitive: false,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: false,
Replyable: false,
Likeable: false,
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_2_status_1": {
ID: "8945ccf2-3873-45e9-aa13-fd7163f19775",
URI: "http://localhost:8080/users/1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
URL: "http://localhost:8080/@1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
Content: "🐢 hi everyone i post about turtles 🐢",
CreatedAt: time.Now().Add(-189 * time.Hour),
UpdatedAt: time.Now().Add(-189 * time.Hour),
Local: true,
AccountID: "eecaad73-5703-426d-9312-276641daa31e",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "introduction post",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
Replyable: true,
Likeable: true,
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
},
"local_account_2_status_2": {
ID: "c7e25a86-f0d3-4705-a73c-c597f687d3dd",
URI: "http://localhost:8080/users/1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
URL: "http://localhost:8080/@1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
CreatedAt: time.Now().Add(-1 * time.Minute),
UpdatedAt: time.Now().Add(-1 * time.Minute),
Local: true,
AccountID: "eecaad73-5703-426d-9312-276641daa31e",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: true,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
Replyable: false,
Likeable: true,
},
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
}, },
} }
} }