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{
Name: flagNames.StorageBasePath,
Usage: "Full path to an already-created directory where gts should store/retrieve media files",
Value: "/opt/gotosocial",
Usage: "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.",
Value: "/gotosocial/storage/media",
EnvVars: []string{envNames.StorageBasePath},
},
&cli.StringFlag{

View File

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

View File

@ -27,10 +27,10 @@ import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/config"
"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/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"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.
// 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/
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")
// 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/superseriousbusiness/gotosocial/internal/config"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/oauth2/v4"
"github.com/superseriousbusiness/oauth2/v4/models"
oauthmodels "github.com/superseriousbusiness/oauth2/v4/models"
@ -56,12 +58,13 @@ type AccountCreateTestSuite struct {
suite.Suite
config *config.Config
log *logrus.Logger
testAccountLocal *model.Account
testApplication *model.Application
testAccountLocal *gtsmodel.Account
testApplication *gtsmodel.Application
testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
db db.DB
accountModule *accountModule
newUserFormHappyPath url.Values
@ -78,13 +81,13 @@ func (suite *AccountCreateTestSuite) SetupSuite() {
log.SetLevel(logrus.TraceLevel)
suite.log = log
suite.testAccountLocal = &model.Account{
suite.testAccountLocal = &gtsmodel.Account{
ID: uuid.NewString(),
Username: "test_user",
}
// can use this test application throughout
suite.testApplication = &model.Application{
suite.testApplication = &gtsmodel.Application{
ID: "weeweeeeeeeeeeeeee",
Name: "a test application",
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)
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!
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() {
@ -172,14 +177,14 @@ func (suite *AccountCreateTestSuite) TearDownSuite() {
func (suite *AccountCreateTestSuite) SetupTest() {
// create all the tables we might need in thie suite
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.FollowRequest{},
&model.Status{},
&model.Application{},
&model.EmailDomainBlock{},
&model.MediaAttachment{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Follow{},
&gtsmodel.FollowRequest{},
&gtsmodel.Status{},
&gtsmodel.Application{},
&gtsmodel.EmailDomainBlock{},
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
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
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.FollowRequest{},
&model.Status{},
&model.Application{},
&model.EmailDomainBlock{},
&model.MediaAttachment{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Follow{},
&gtsmodel.FollowRequest{},
&gtsmodel.Status{},
&gtsmodel.Application{},
&gtsmodel.EmailDomainBlock{},
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
if err := suite.db.DropTable(m); err != nil {
@ -259,7 +264,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
t := &mastotypes.Token{}
t := &mastomodel.Token{}
err = json.Unmarshal(b, t)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "we're authorized now!", t.AccessToken)
@ -267,7 +272,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
// check new account
// 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)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), acct)
@ -288,7 +293,7 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
// check new user
// 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)
assert.Nil(suite.T(), err)
assert.NotNil(suite.T(), usr)

View File

@ -23,7 +23,7 @@ import (
"github.com/gin-gonic/gin"
"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
@ -37,7 +37,7 @@ func (m *accountModule) accountGETHandler(c *gin.Context) {
return
}
targetAccount := &model.Account{}
targetAccount := &gtsmodel.Account{}
if err := m.db.GetByID(targetAcctID, targetAccount); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
c.JSON(http.StatusNotFound, gin.H{"error": "Record not found"})
@ -47,7 +47,7 @@ func (m *accountModule) accountGETHandler(c *gin.Context) {
return
}
acctInfo, err := m.db.AccountToMastoPublic(targetAccount)
acctInfo, err := m.mastoConverter.AccountToMastoPublic(targetAccount)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return

View File

@ -27,10 +27,10 @@ import (
"net/http"
"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/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
)
// 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 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)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@ -75,7 +75,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
}
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)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@ -87,7 +87,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
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()})
return
}
@ -98,7 +98,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
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)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@ -126,7 +126,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
}
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()})
return
}
@ -138,14 +138,14 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
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()})
return
}
}
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()})
return
}
@ -156,7 +156,7 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
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()})
return
}
@ -168,14 +168,14 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
// }
// 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 {
l.Debugf("could not fetch updated account %s: %s", authed.Account.ID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
acctSensitive, err := m.db.AccountToMastoSensitive(updatedAccount)
acctSensitive, err := m.mastoConverter.AccountToMastoSensitive(updatedAccount)
if err != nil {
l.Tracef("could not convert account into mastosensitive account: %s", err)
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,
// parsing and checking the image, and doing the necessary updates in the database for this to become
// 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
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)
@ -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,
// parsing and checking the image, and doing the necessary updates in the database for this to become
// 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
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)

View File

@ -39,7 +39,8 @@ import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
"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/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage"
@ -52,12 +53,13 @@ type AccountUpdateTestSuite struct {
suite.Suite
config *config.Config
log *logrus.Logger
testAccountLocal *model.Account
testApplication *model.Application
testAccountLocal *gtsmodel.Account
testApplication *gtsmodel.Application
testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
db db.DB
accountModule *accountModule
newUserFormHappyPath url.Values
@ -74,13 +76,13 @@ func (suite *AccountUpdateTestSuite) SetupSuite() {
log.SetLevel(logrus.TraceLevel)
suite.log = log
suite.testAccountLocal = &model.Account{
suite.testAccountLocal = &gtsmodel.Account{
ID: uuid.NewString(),
Username: "test_user",
}
// can use this test application throughout
suite.testApplication = &model.Application{
suite.testApplication = &gtsmodel.Application{
ID: "weeweeeeeeeeeeeeee",
Name: "a test application",
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)
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!
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() {
@ -168,14 +172,14 @@ func (suite *AccountUpdateTestSuite) TearDownSuite() {
func (suite *AccountUpdateTestSuite) SetupTest() {
// create all the tables we might need in thie suite
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.FollowRequest{},
&model.Status{},
&model.Application{},
&model.EmailDomainBlock{},
&model.MediaAttachment{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Follow{},
&gtsmodel.FollowRequest{},
&gtsmodel.Status{},
&gtsmodel.Application{},
&gtsmodel.EmailDomainBlock{},
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
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
models := []interface{}{
&model.User{},
&model.Account{},
&model.Follow{},
&model.FollowRequest{},
&model.Status{},
&model.Application{},
&model.EmailDomainBlock{},
&model.MediaAttachment{},
&gtsmodel.User{},
&gtsmodel.Account{},
&gtsmodel.Follow{},
&gtsmodel.FollowRequest{},
&gtsmodel.Status{},
&gtsmodel.Application{},
&gtsmodel.EmailDomainBlock{},
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
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)
acctSensitive, err := m.db.AccountToMastoSensitive(authed.Account)
acctSensitive, err := m.mastoConverter.AccountToMastoSensitive(authed.Account)
if err != nil {
l.Tracef("could not convert account into mastosensitive account: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})

View File

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

View File

@ -24,9 +24,9 @@ import (
"github.com/gin-gonic/gin"
"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/pkg/mastotypes"
)
// 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()
// generate the application to put in the database
app := &model.Application{
app := &gtsmodel.Application{
Name: form.ClientName,
Website: form.Website,
RedirectURI: form.RedirectURIs,
@ -108,6 +108,12 @@ func (m *appModule) appsPOSTHandler(c *gin.Context) {
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/
c.JSON(http.StatusOK, app.ToMastoSensitive())
c.JSON(http.StatusOK, mastoApp)
}

View File

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

View File

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

View File

@ -27,8 +27,8 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
)
// 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"})
return
}
app := &model.Application{
app := &gtsmodel.Application{
ClientID: clientID,
}
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
user := &model.User{
user := &gtsmodel.User{
ID: userID,
}
if err := m.db.GetByID(user.ID, user); err != nil {
@ -74,7 +74,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
return
}
acct := &model.Account{
acct := &gtsmodel.Account{
ID: user.AccountID,
}

View File

@ -20,7 +20,7 @@ package auth
import (
"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"
)
@ -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())
// 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 {
l.Warnf("no user found for validated uid %s", uid)
return
@ -54,7 +54,7 @@ func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
c.Set(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 {
l.Warnf("no account found for validated user %s", uid)
return
@ -66,7 +66,7 @@ func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
// check for application token
if cid := ti.GetClientID(); cid != "" {
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 {
l.Tracef("no app found for client %s", cid)
}

View File

@ -24,7 +24,7 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"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
gtsUser := &model.User{}
gtsUser := &gtsmodel.User{}
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)

View File

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

View File

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

View File

@ -28,11 +28,11 @@ import (
"github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/config"
"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"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
)
type advancedStatusCreateForm struct {
@ -42,7 +42,7 @@ type advancedStatusCreateForm struct {
type advancedVisibilityFlagsForm struct {
// 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)
Federated *bool `form:"federated"`
// This status can be boosted/reblogged
@ -96,7 +96,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
thisStatusID := uuid.NewString()
thisStatusURI := fmt.Sprintf("%s/%s", uris.StatusesURI, thisStatusID)
thisStatusURL := fmt.Sprintf("%s/%s", uris.StatusesURL, thisStatusID)
newStatus := &model.Status{
newStatus := &gtsmodel.Status{
ID: thisStatusID,
URI: thisStatusURI,
URL: thisStatusURL,
@ -106,7 +106,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
Local: true,
AccountID: authed.Account.ID,
ContentWarning: form.SpoilerText,
ActivityStreamsType: model.ActivityStreamsNote,
ActivityStreamsType: gtsmodel.ActivityStreamsNote,
Sensitive: form.Sensitive,
Language: form.Language,
}
@ -135,16 +135,23 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
return
}
// convert mentions to *model.Mention
// convert mentions to *gtsmodel.Mention
menchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), authed.Account.ID, thisStatusID)
if err != nil {
l.Debugf("error generating mentions from status: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "error generating mentions from status"})
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
// convert tags to *model.Tag
// convert tags to *gtsmodel.Tag
tags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), authed.Account.ID, thisStatusID)
if err != nil {
l.Debugf("error generating hashtags from status: %s", err)
@ -153,7 +160,7 @@ func (m *statusModule) statusCreatePOSTHandler(c *gin.Context) {
}
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)
if err != nil {
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
m.distributor.FromClientAPI() <- distributor.FromClientAPI{
APObjectType: model.ActivityStreamsNote,
APActivityType: model.ActivityStreamsCreate,
APObjectType: gtsmodel.ActivityStreamsNote,
APActivityType: gtsmodel.ActivityStreamsCreate,
Activity: newStatus,
}
// return populated status to submitter
mastoAccount, err := m.db.AccountToMastoPublic(authed.Account)
// now we need to build up the mastodon-style status object to return to the submitter
mastoVis := util.ParseMastoVisFromGTSVis(newStatus.Visibility)
mastoAccount, err := m.mastoConverter.AccountToMastoPublic(authed.Account)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
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{
ID: newStatus.ID,
CreatedAt: newStatus.CreatedAt.Format(time.RFC3339),
InReplyToID: newStatus.InReplyToID,
// InReplyToAccountID: newStatus.ReplyToAccount.ID,
Sensitive: newStatus.Sensitive,
SpoilerText: newStatus.ContentWarning,
Visibility: util.ParseMastoVisFromGTSVis(newStatus.Visibility),
Language: newStatus.Language,
URI: newStatus.URI,
URL: newStatus.URL,
Content: newStatus.Content,
Application: authed.Application.ToMastoPublic(),
Account: mastoAccount,
// MediaAttachments: ,
Text: form.Status,
ID: newStatus.ID,
CreatedAt: newStatus.CreatedAt.Format(time.RFC3339),
InReplyToID: newStatus.InReplyToID,
InReplyToAccountID: newStatus.InReplyToAccountID,
Sensitive: newStatus.Sensitive,
SpoilerText: newStatus.ContentWarning,
Visibility: mastoVis,
Language: newStatus.Language,
URI: newStatus.URI,
URL: newStatus.URL,
Content: newStatus.Content,
Application: mastoApplication,
Account: mastoAccount,
MediaAttachments: mastoAttachments,
Mentions: mastoMentions,
Text: form.Status,
}
c.JSON(http.StatusOK, mastoStatus)
}
@ -255,16 +293,16 @@ func validateCreateStatus(form *advancedStatusCreateForm, config *config.Statuse
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
gtsAdvancedVis := &model.VisibilityAdvanced{
gtsAdvancedVis := &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: true,
Replyable: true,
Likeable: true,
}
var gtsBasicVis model.Visibility
var gtsBasicVis gtsmodel.Visibility
// Advanced takes priority if it's set.
// If it's not set, take whatever masto visibility is set.
// 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 {
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
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
if form.Federated != nil {
gtsAdvancedVis.Federated = *form.Federated
@ -298,7 +336,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
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
gtsAdvancedVis.Boostable = false
@ -314,7 +352,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
gtsAdvancedVis.Likeable = *form.Likeable
}
case model.VisibilityDirect:
case gtsmodel.VisibilityDirect:
// direct is pretty easy: there's only one possible setting so return it
gtsAdvancedVis.Federated = true
gtsAdvancedVis.Boostable = false
@ -327,7 +365,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis model.Vis
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 == "" {
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?
//
// If this is all OK, then we fetch the repliedStatus and the repliedAccount for later processing.
repliedStatus := &model.Status{}
repliedAccount := &model.Account{}
repliedStatus := &gtsmodel.Status{}
repliedAccount := &gtsmodel.Account{}
// check replied status exists + is replyable
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
@ -356,26 +394,35 @@ func (m *statusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
// check replied account is known to us
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
if blocked, err := m.db.Blocked(thisAccountID, repliedAccount.ID); err != nil || blocked {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
if blocked, err := m.db.Blocked(thisAccountID, repliedAccount.ID); err != nil {
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.InReplyToAccountID = repliedAccount.ID
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 {
return nil
}
attachments := []*model.MediaAttachment{}
attachments := []*gtsmodel.MediaAttachment{}
for _, mediaID := range form.MediaIDs {
// check these attachments exist
a := &model.MediaAttachment{}
a := &gtsmodel.MediaAttachment{}
if err := m.db.GetByID(mediaID, a); err != nil {
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
}
func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string, status *model.Status) error {
func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error {
if form.Language != "" {
status.Language = form.Language
} else {

View File

@ -34,12 +34,13 @@ import (
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
"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/mastotypes"
mastomodel "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/gotosocial/testrig"
)
@ -49,12 +50,13 @@ type StatusCreateTestSuite struct {
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
distributor *distributor.MockDistributor
testTokens map[string]*oauth.Token
testClients map[string]*oauth.Client
testApplications map[string]*model.Application
testUsers map[string]*model.User
testAccounts map[string]*model.Account
testApplications map[string]*gtsmodel.Application
testUsers map[string]*gtsmodel.User
testAccounts map[string]*gtsmodel.Account
log *logrus.Logger
db db.DB
statusModule *statusModule
@ -113,10 +115,11 @@ func (suite *StatusCreateTestSuite) SetupSuite() {
suite.mockOauthServer = &oauth.MockServer{}
suite.mockStorage = &storage.MockStorage{}
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.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() {
@ -184,16 +187,15 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerSuccessful() {
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
fmt.Println(string(b))
statusReply := &mastotypes.Status{}
statusReply := &mastomodel.Status{}
err = json.Unmarshal(b, statusReply)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
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() {
@ -209,31 +211,60 @@ func (suite *StatusCreateTestSuite) TestStatusCreatePOSTHandlerReplyToFail() {
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": {"this is a reply to a status that doesn't exist"},
"spoiler_text": {"don't open cuz it won't work"},
"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"},
"status": {"this is a reply to a status that doesn't exist"},
"spoiler_text": {"don't open cuz it won't work"},
"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"},
}
suite.statusModule.statusCreatePOSTHandler(ctx)
// 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)
result := recorder.Result()
defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
fmt.Println(string(b))
statusReply := &mastotypes.Status{}
statusReply := &mastomodel.Status{}
err = json.Unmarshal(b, statusReply)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), "hello hello", statusReply.SpoilerText)
assert.Equal(suite.T(), "this is a brand new status!", statusReply.Content)
assert.True(suite.T(), statusReply.Sensitive)
assert.Equal(suite.T(), mastotypes.VisibilityPrivate, statusReply.Visibility)
assert.Equal(suite.T(), "", statusReply.SpoilerText)
assert.Equal(suite.T(), fmt.Sprintf("hello @%s this reply should work!", testrig.TestAccounts()["local_account_2"].Username), statusReply.Content)
assert.False(suite.T(), statusReply.Sensitive)
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) {

View File

@ -27,8 +27,7 @@ import (
"github.com/go-fed/activity/pub"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
)
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.
// 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
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.
// 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
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.
// 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
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.
// 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
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.
// 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
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
// 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!
// 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.
// 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
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.
// 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.
// 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(mediaAttachment *model.MediaAttachment, accountID string) error
SetHeaderOrAvatarForAccountID(mediaAttachment *gtsmodel.MediaAttachment, accountID string) error
// 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.
GetAvatarForAccountID(avatar *model.MediaAttachment, accountID string) error
GetAvatarForAccountID(avatar *gtsmodel.MediaAttachment, accountID string) error
// 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.
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.
// That is, it returns true if account1 blocks account2, OR if account2 blocks account1.
@ -182,39 +181,30 @@ type DB interface {
USEFUL CONVERSION FUNCTIONS
*/
// 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 *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
// MentionStringsToMentions takes a slice of deduplicated, lowercase account names in the form "@test@whatever.example.org" for a remote account,
// or @test for a local account, 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.
//
// Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking if they exist
// and conveniently returning them.
MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*model.Mention, error)
// Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking
// if they exist in the db and conveniently returning them.
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
// 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.
//
// Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking if they exist
// and conveniently returning them.
TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*model.Tag, error)
// Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking
// if they exist in the db and conveniently returning them.
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
// 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.
//
// Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking if they exist
// and conveniently returning them.
EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*model.Emoji, error)
// Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking
// if they exist in the db and conveniently returning them.
EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error)
}
// 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/>.
*/
// 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.
// 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/
package model
package gtsmodel
import (
"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 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 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

View File

@ -16,7 +16,7 @@
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
type ActivityStreamsObject string

View File

@ -16,9 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
import "github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
package gtsmodel
// 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.
@ -40,23 +38,3 @@ type Application struct {
// a vapid key generated for this app when it was created
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"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import (
"time"
@ -29,7 +29,9 @@ type MediaAttachment struct {
ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"`
// ID of the status to which this is attached
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
// When was the attachment created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
@ -81,7 +83,9 @@ type Thumbnail struct {
FileSize int
// When was the file last updated
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
}
@ -106,11 +110,13 @@ const (
// FileTypeImage is for jpegs and pngs
FileTypeImage FileType = "image"
// 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 FileType = "audio"
// FileTypeVideo is for files with audio + visual
FileTypeVideo FileType = "video"
// FileTypeUnknown is for unknown file types (surprise surprise!)
FileTypeUnknown FileType = "unknown"
)
// FileMeta describes metadata about the actual contents of the file.
@ -119,7 +125,7 @@ type FileMeta struct {
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 {
Width int
Height int
@ -127,7 +133,7 @@ type Small struct {
Aspect float64
}
// ImageOriginal implements OriginalMeta for still images
// Original can be used for original metadata for any media type
type Original struct {
Width int
Height int

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
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/>.
*/
package model
package gtsmodel
import "time"
@ -40,6 +40,8 @@ type Status struct {
AccountID string
// id of the status this status is a reply to
InReplyToID string
// id of the account that this status replies to
InReplyToAccountID string
// id of the status this status is a boost of
BoostOfID string
// cw string for this status

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import "time"

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package model
package gtsmodel
import (
"net"

View File

@ -6,9 +6,7 @@ import (
context "context"
mock "github.com/stretchr/testify/mock"
mastotypes "github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
model "github.com/superseriousbusiness/gotosocial/internal/db/model"
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
net "net"
@ -20,52 +18,6 @@ type MockDB struct {
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
func (_m *MockDB) Blocked(account1 string, account2 string) (bool, error) {
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
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)
var r0 []*model.Emoji
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Emoji); ok {
var r0 []*gtsmodel.Emoji
if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Emoji); ok {
r0 = rf(emojis, originAccountID, statusID)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
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)
} else {
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
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)
var r0 []*model.Mention
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Mention); ok {
var r0 []*gtsmodel.Mention
if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Mention); ok {
r0 = rf(targetAccounts, originAccountID, statusID)
} else {
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
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)
var r0 *model.User
if rf, ok := ret.Get(0).(func(string, string, bool, string, string, net.IP, string, string) *model.User); ok {
var r0 *gtsmodel.User
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)
} else {
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
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)
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)
} else {
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
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)
var r0 []*model.Tag
if rf, ok := ret.Get(0).(func([]string, string, string) []*model.Tag); ok {
var r0 []*gtsmodel.Tag
if rf, ok := ret.Get(0).(func([]string, string, string) []*gtsmodel.Tag); ok {
r0 = rf(tags, originAccountID, statusID)
} else {
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/sirupsen/logrus"
"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/pkg/mastotypes"
"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 {
models := []interface{}{
(*model.Account)(nil),
(*model.Status)(nil),
(*model.User)(nil),
(*gtsmodel.Account)(nil),
(*gtsmodel.Status)(nil),
(*gtsmodel.User)(nil),
}
ps.log.Info("creating db schema")
@ -312,8 +312,8 @@ func (ps *postgresService) DeleteWhere(key string, value interface{}, i interfac
HANDY SHORTCUTS
*/
func (ps *postgresService) GetAccountByUserID(userID string, account *model.Account) error {
user := &model.User{
func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.Account) error {
user := &gtsmodel.User{
ID: userID,
}
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
}
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -341,7 +341,7 @@ func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, follo
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -351,7 +351,7 @@ func (ps *postgresService) GetFollowingByAccountID(accountID string, following *
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -361,7 +361,7 @@ func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -371,7 +371,7 @@ func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]
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")
if limit != 0 {
q = q.Limit(limit)
@ -388,7 +388,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
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 == pg.ErrNoRows {
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 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 := 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)
} else if err != pg.ErrNoRows {
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 @
// 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
return fmt.Errorf("email domain %s is blocked", domain)
} 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
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
return fmt.Errorf("email %s already in use", email)
} else if err != pg.ErrNoRows {
@ -439,7 +439,7 @@ func (ps *postgresService) IsEmailAvailable(email string) error {
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)
if err != nil {
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)
a := &model.Account{
a := &gtsmodel.Account{
Username: username,
DisplayName: username,
Reason: reason,
URL: uris.UserURL,
PrivateKey: key,
PublicKey: &key.PublicKey,
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
URI: uris.UserURI,
InboxURL: uris.InboxURI,
OutboxURL: uris.OutboxURI,
@ -470,7 +470,7 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
if err != nil {
return nil, fmt.Errorf("error hashing password: %s", err)
}
u := &model.User{
u := &gtsmodel.User{
AccountID: a.ID,
EncryptedPassword: string(pw),
SignUpIP: signUpIP,
@ -486,12 +486,12 @@ func (ps *postgresService) NewSignup(username string, reason string, requireAppr
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()
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -501,7 +501,7 @@ func (ps *postgresService) GetHeaderForAccountID(header *model.MediaAttachment,
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 == pg.ErrNoRows {
return ErrNoEntries{}
@ -513,12 +513,13 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment,
func (ps *postgresService) Blocked(account1 string, account2 string) (bool, error) {
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).
WhereOr("target_account_id = ?", account1).Where("account_id = ?", account2).
Select(); err != nil {
if err == pg.ErrNoRows {
blocked = false
return blocked, nil
} else {
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:
// 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.
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....
mastoAccount, err := ps.AccountToMastoPublic(a)
if err != nil {
@ -545,7 +546,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
// then adding the Source object to it...
// check pending follow requests aimed at this account
fr := []model.FollowRequest{}
fr := []gtsmodel.FollowRequest{}
if err := ps.GetFollowRequestsForAccountID(a.ID, &fr); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
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
}
func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.Account, error) {
func (ps *postgresService) AccountToMastoPublic(a *gtsmodel.Account) (*mastotypes.Account, error) {
// count followers
followers := []model.Follow{}
followers := []gtsmodel.Follow{}
if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting followers: %s", err)
@ -582,7 +583,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
}
// count following
following := []model.Follow{}
following := []gtsmodel.Follow{}
if err := ps.GetFollowingByAccountID(a.ID, &following); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting following: %s", err)
@ -594,7 +595,7 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
}
// count statuses
statuses := []model.Status{}
statuses := []gtsmodel.Status{}
if err := ps.GetStatusesByAccountID(a.ID, &statuses); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
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
lastStatus := &model.Status{}
lastStatus := &gtsmodel.Status{}
if err := ps.GetLastStatusForAccountID(a.ID, lastStatus); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
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
avi := &model.MediaAttachment{}
avi := &gtsmodel.MediaAttachment{}
if err := ps.GetAvatarForAccountID(avi, a.ID); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
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
aviURLStatic := avi.Thumbnail.Path
header := &model.MediaAttachment{}
header := &gtsmodel.MediaAttachment{}
if err := ps.GetHeaderForAccountID(avi, a.ID); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting header: %s", err)
@ -681,11 +682,12 @@ func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.A
}, nil
}
func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*model.Mention, error) {
menchies := []*model.Mention{}
func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) {
menchies := []*gtsmodel.Mention{}
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.
// But we still need to do a bit of fiddling to get what we need here -- the username and domain.
// A mentioned account looks like "@test@example.org" or just "@test" for a local account
// -- 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 @
t := strings.TrimPrefix(a, "@")
@ -693,41 +695,51 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
// 2. split the username and domain
s := strings.Split(t, "@")
// 3. it should *always* be length 2 so if it's not then something is seriously wrong
if len(s) != 2 {
return nil, fmt.Errorf("mentioned account format %s was not valid", a)
// 3. if it's length 1 it's a local account, length 2 means remote, anything else means something is wrong
var local bool
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
if username == "" || domain == "" {
return nil, fmt.Errorf("username or domain for %s was nil", a)
if username == "" || (!local && domain == "") {
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
mentionedAccount := &model.Account{}
mentionedAccount := &gtsmodel.Account{}
var err error
if domain == ps.config.Host {
if local {
// 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 {
// 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 == pg.ErrNoRows {
// 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
}
// 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!
menchies = append(menchies, &model.Mention{
menchies = append(menchies, &gtsmodel.Mention{
StatusID: statusID,
OriginAccountID: originAccountID,
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:
// A) it might later and
// A) it probably will later and
// B) it's v. similar to MentionStringsToMentions
func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*model.Tag, error) {
newTags := []*model.Tag{}
func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
newTags := []*gtsmodel.Tag{}
for _, t := range tags {
newTags = append(newTags, &model.Tag{
newTags = append(newTags, &gtsmodel.Tag{
Name: t,
})
}
return newTags, nil
}
func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*model.Emoji, error) {
newEmojis := []*model.Emoji{}
func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
newEmojis := []*gtsmodel.Emoji{}
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()
if err != nil {
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)
continue
}

View File

@ -21,7 +21,7 @@ package distributor
import (
"github.com/go-fed/activity/pub"
"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
@ -97,13 +97,13 @@ func (d *distributor) Stop() error {
}
type FromClientAPI struct {
APObjectType model.ActivityStreamsObject
APActivityType model.ActivityStreamsActivity
APObjectType gtsmodel.ActivityStreamsObject
APActivityType gtsmodel.ActivityStreamsActivity
Activity interface{}
}
type ToClientAPI struct {
APObjectType model.ActivityStreamsObject
APActivityType model.ActivityStreamsActivity
APObjectType gtsmodel.ActivityStreamsObject
APActivityType gtsmodel.ActivityStreamsActivity
Activity interface{}
}

View File

@ -35,6 +35,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"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)
oauthServer := oauth.New(dbService, log)
// build converters and util
mastoConverter := mastotypes.New(c, dbService)
// build client api modules
authModule := auth.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, log)
appsModule := app.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, mastoConverter, log)
appsModule := app.New(oauthServer, dbService, mastoConverter, log)
apiModules := []apimodule.ClientAPIModule{
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"`
// The location of a scaled-down preview of the attachment.
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"`
// 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.
TextURL string `json:"text_url,omitempty"`
// 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/superseriousbusiness/gotosocial/internal/config"
"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"
)
@ -37,7 +37,7 @@ type MediaHandler interface {
// 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,
// 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 {
@ -68,7 +68,7 @@ type HeaderInfo struct {
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")
if headerOrAvi != "header" && headerOrAvi != "avatar" {
@ -107,7 +107,7 @@ func (mh *mediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID stri
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 isAvatar bool
@ -152,34 +152,38 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
extension := strings.Split(contentType, "/")[1]
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...
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 {
return nil, fmt.Errorf("storage error: %s", err)
}
// 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 {
return nil, fmt.Errorf("storage error: %s", err)
}
ma := &model.MediaAttachment{
ma := &gtsmodel.MediaAttachment{
ID: newMediaID,
StatusID: "",
URL: originalURL,
RemoteURL: "",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Type: model.FileTypeImage,
FileMeta: model.FileMeta{
Original: model.Original{
Type: gtsmodel.FileTypeImage,
FileMeta: gtsmodel.FileMeta{
Original: gtsmodel.Original{
Width: original.width,
Height: original.height,
Size: original.size,
Aspect: original.aspect,
},
Small: model.Small{
Small: gtsmodel.Small{
Width: small.width,
Height: small.height,
Size: small.size,
@ -191,17 +195,18 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
ScheduledStatusID: "",
Blurhash: original.blurhash,
Processing: 2,
File: model.File{
File: gtsmodel.File{
Path: originalPath,
ContentType: contentType,
FileSize: len(original.image),
UpdatedAt: time.Now(),
},
Thumbnail: model.Thumbnail{
Thumbnail: gtsmodel.Thumbnail{
Path: smallPath,
ContentType: contentType,
FileSize: len(small.image),
UpdatedAt: time.Now(),
URL: smallURL,
RemoteURL: "",
},
Avatar: isAvatar,

View File

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

View File

@ -4,7 +4,7 @@ package media
import (
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
@ -13,15 +13,15 @@ type MockMediaHandler struct {
}
// 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)
var r0 *model.MediaAttachment
if rf, ok := ret.Get(0).(func([]byte, string, string) *model.MediaAttachment); ok {
var r0 *gtsmodel.MediaAttachment
if rf, ok := ret.Get(0).(func([]byte, string, string) *gtsmodel.MediaAttachment); ok {
r0 = rf(img, accountID, headerOrAvi)
} else {
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/sirupsen/logrus"
"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/errors"
"github.com/superseriousbusiness/oauth2/v4/manage"
@ -34,6 +34,9 @@ import (
)
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"
// SessionAuthorizedUser is the key set in the gin context for the id of
// a User who has successfully passed Bearer token authorization.
@ -65,9 +68,9 @@ type s struct {
type Authed struct {
Token oauth2.TokenInfo
Application *model.Application
User *model.User
Account *model.Account
Application *gtsmodel.Application
User *gtsmodel.User
Account *gtsmodel.Account
}
// 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)
if ok {
parsed, ok := i.(*model.Application)
parsed, ok := i.(*gtsmodel.Application)
if !ok {
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)
if ok {
parsed, ok := i.(*model.User)
parsed, ok := i.(*gtsmodel.User)
if !ok {
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)
if ok {
parsed, ok := i.(*model.Account)
parsed, ok := i.(*gtsmodel.Account)
if !ok {
return nil, errors.New("could not parse account from session context")
}

View File

@ -21,8 +21,8 @@ package util
import (
"fmt"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/pkg/mastotypes"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
mastotypes "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
)
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.
func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility {
func ParseGTSVisFromMastoVis(m mastotypes.Visibility) gtsmodel.Visibility {
switch m {
case mastotypes.VisibilityPublic:
return model.VisibilityPublic
return gtsmodel.VisibilityPublic
case mastotypes.VisibilityUnlisted:
return model.VisibilityUnlocked
return gtsmodel.VisibilityUnlocked
case mastotypes.VisibilityPrivate:
return model.VisibilityFollowersOnly
return gtsmodel.VisibilityFollowersOnly
case mastotypes.VisibilityDirect:
return model.VisibilityDirect
return gtsmodel.VisibilityDirect
default:
break
}
@ -81,15 +81,15 @@ func ParseGTSVisFromMastoVis(m mastotypes.Visibility) model.Visibility {
}
// 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 {
case model.VisibilityPublic:
case gtsmodel.VisibilityPublic:
return mastotypes.VisibilityPublic
case model.VisibilityUnlocked:
case gtsmodel.VisibilityUnlocked:
return mastotypes.VisibilityUnlisted
case model.VisibilityFollowersOnly, model.VisibilityMutualsOnly:
case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly:
return mastotypes.VisibilityPrivate
case model.VisibilityDirect:
case gtsmodel.VisibilityDirect:
return mastotypes.VisibilityDirect
default:
break

View File

@ -19,16 +19,13 @@
package util
import (
"fmt"
"regexp"
"strings"
)
// To play around with these regexes, see: https://regex101.com/r/2km2EK/1
var (
// mention regex can be played around with here: https://regex101.com/r/2km2EK/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 = fmt.Sprintf(`(?: |^|\W)(@[a-zA-Z0-9_]+@%s(?: |\n)`, hostnameRegexString)
// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1
mentionRegexString = `(?: |^|\W)(@[a-zA-Z0-9_]+(?:@[a-zA-Z0-9_\-\.]+)?)(?: |\n)`
mentionRegex = regexp.MustCompile(mentionRegexString)
// hashtag regex can be played with here: https://regex101.com/r/Vhy8pg/1
hashtagRegexString = `(?: |^|\W)?#([a-zA-Z0-9]{1,30})(?:\b|\r)`
@ -43,7 +40,7 @@ var (
// mentioned in that status.
//
// 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.
func DeriveMentions(status string) []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
@thiswontwork though! @NORWILL@THIS.one!!
@thisisalocaluser ! @NORWILL@THIS.one!!
here is a duplicate mention: @hello@test.lgbt
`
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(), "@someone_else@testing.best-horse.com", menchies[1])
assert.Equal(suite.T(), "@hello@test.lgbt", menchies[2])
assert.Equal(suite.T(), "@thisisalocaluser", menchies[3])
}
func (suite *StatusTestSuite) TestDeriveMentionsEmpty() {

View File

@ -2,23 +2,23 @@ package testrig
import (
"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"
)
var testModels []interface{} = []interface{}{
&model.Account{},
&model.Application{},
&model.Block{},
&model.DomainBlock{},
&model.EmailDomainBlock{},
&model.Follow{},
&model.FollowRequest{},
&model.MediaAttachment{},
&model.Mention{},
&model.Status{},
&model.Tag{},
&model.User{},
&gtsmodel.Account{},
&gtsmodel.Application{},
&gtsmodel.Block{},
&gtsmodel.DomainBlock{},
&gtsmodel.EmailDomainBlock{},
&gtsmodel.Follow{},
&gtsmodel.FollowRequest{},
&gtsmodel.MediaAttachment{},
&gtsmodel.Mention{},
&gtsmodel.Status{},
&gtsmodel.Tag{},
&gtsmodel.User{},
&oauth.Token{},
&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
}

View File

@ -6,20 +6,20 @@ import (
"net"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db/model"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func TestTokens() map[string]*oauth.Token {
tokens := map[string]*oauth.Token{
"local_account_1": {
ID: "64cf4214-33ab-4220-b5ca-4a6a12263b20",
ClientID: "73b48d42-029d-4487-80fc-329a5cf67869",
UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b",
RedirectURI: "http://localhost:8080",
Scope: "read write follow push",
Access: "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
AccessCreateAt: time.Now(),
ID: "64cf4214-33ab-4220-b5ca-4a6a12263b20",
ClientID: "73b48d42-029d-4487-80fc-329a5cf67869",
UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b",
RedirectURI: "http://localhost:8080",
Scope: "read write follow push",
Access: "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
AccessCreateAt: time.Now(),
AccessExpiresAt: time.Now().Add(72 * time.Hour),
},
}
@ -38,8 +38,8 @@ func TestClients() map[string]*oauth.Client {
return clients
}
func TestApplications() map[string]*model.Application {
apps := map[string]*model.Application{
func TestApplications() map[string]*gtsmodel.Application {
apps := map[string]*gtsmodel.Application{
"application_1": {
ID: "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
Name: "really cool gts application",
@ -54,8 +54,8 @@ func TestApplications() map[string]*model.Application {
return apps
}
func TestUsers() map[string]*model.User {
users := map[string]*model.User{
func TestUsers() map[string]*gtsmodel.User {
users := map[string]*gtsmodel.User{
"unconfirmed_account": {
ID: "0f7b1d24-1e49-4ee0-bc7e-fd87b7289eea",
Email: "",
@ -181,8 +181,8 @@ func TestUsers() map[string]*model.User {
return users
}
func TestAccounts() map[string]*model.Account {
accounts := map[string]*model.Account{
func TestAccounts() map[string]*gtsmodel.Account {
accounts := map[string]*gtsmodel.Account{
"unconfirmed_account": {
ID: "59e197f5-87cd-4be8-ac7c-09082ccc4b4d",
Username: "weed_lord420",
@ -197,7 +197,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "",
DisplayName: "",
Fields: []model.Field{},
Fields: []gtsmodel.Field{},
Note: "",
Memorial: false,
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.",
Locked: false,
Discoverable: false,
Privacy: model.VisibilityPublic,
Privacy: gtsmodel.VisibilityPublic,
Sensitive: false,
Language: "en",
URI: "http://localhost:8080/users/weed_lord420",
@ -218,7 +218,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/weed_lord420/followers",
FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
@ -242,7 +242,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "",
DisplayName: "",
Fields: []model.Field{},
Fields: []gtsmodel.Field{},
Note: "",
Memorial: false,
MovedToAccountID: "",
@ -252,7 +252,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "",
Locked: false,
Discoverable: true,
Privacy: model.VisibilityPublic,
Privacy: gtsmodel.VisibilityPublic,
Sensitive: false,
Language: "en",
URI: "http://localhost:8080/users/admin",
@ -263,7 +263,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/admin/followers",
FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
@ -287,7 +287,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "",
DisplayName: "original zork (he/they)",
Fields: []model.Field{},
Fields: []gtsmodel.Field{},
Note: "hey yo this is my profile!",
Memorial: false,
MovedToAccountID: "",
@ -297,7 +297,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
Locked: false,
Discoverable: true,
Privacy: model.VisibilityPublic,
Privacy: gtsmodel.VisibilityPublic,
Sensitive: false,
Language: "en",
URI: "http://localhost:8080/users/the_mighty_zork",
@ -308,7 +308,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/the_mighty_zork/followers",
FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
@ -332,7 +332,7 @@ func TestAccounts() map[string]*model.Account {
HeaderUpdatedAt: time.Time{},
HeaderRemoteURL: "",
DisplayName: "happy little turtle :3",
Fields: []model.Field{},
Fields: []gtsmodel.Field{},
Note: "i post about things that concern me",
Memorial: false,
MovedToAccountID: "",
@ -342,7 +342,7 @@ func TestAccounts() map[string]*model.Account {
Reason: "",
Locked: true,
Discoverable: false,
Privacy: model.VisibilityFollowersOnly,
Privacy: gtsmodel.VisibilityFollowersOnly,
Sensitive: false,
Language: "en",
URI: "http://localhost:8080/users/1happyturtle",
@ -353,7 +353,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "",
FollowersURL: "http://localhost:8080/users/1happyturtle/followers",
FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{},
@ -378,7 +378,7 @@ func TestAccounts() map[string]*model.Account {
// HeaderUpdatedAt: time.Time{},
// HeaderRemoteURL: "",
DisplayName: "big gerald",
Fields: []model.Field{},
Fields: []gtsmodel.Field{},
Note: "i post about like, i dunno, stuff, or whatever!!!!",
Memorial: false,
MovedToAccountID: "",
@ -397,7 +397,7 @@ func TestAccounts() map[string]*model.Account {
SharedInboxURL: "",
FollowersURL: "https://fossbros-anonymous.io/users/foss_satan/followers",
FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured",
ActorType: model.ActivityStreamsPerson,
ActorType: gtsmodel.ActivityStreamsPerson,
AlsoKnownAs: "",
PrivateKey: &rsa.PrivateKey{},
PublicKey: nil,
@ -440,53 +440,168 @@ func TestAccounts() map[string]*model.Account {
return accounts
}
func TestStatuses() map[string]*model.Status {
return map[string]*model.Status{
"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: model.VisibilityPublic,
Sensitive: true,
Language: "en",
VisibilityAdvanced: &model.VisibilityAdvanced{
func TestStatuses() map[string]*gtsmodel.Status {
return map[string]*gtsmodel.Status{
"admin_account_status_1": {
ID: "502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
URI: "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
URL: "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
Content: "hello world! first post on the instance!",
CreatedAt: time.Now().Add(-71 * time.Hour),
UpdatedAt: time.Now().Add(-71 * time.Hour),
Local: true,
AccountID: "0fb02eae-2214-473f-9667-0a43f22d75ff",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "",
Visibility: gtsmodel.VisibilityPublic,
Sensitive: false,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: true,
Boostable: 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": {
ID: "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",
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),
UpdatedAt: time.Now().Add(-47 * time.Hour),
Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
InReplyToID: "",
BoostOfID: "",
ID: "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",
Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
CreatedAt: time.Now().Add(-46 * time.Hour),
UpdatedAt: time.Now().Add(-46 * time.Hour),
Local: true,
AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
InReplyToID: "",
BoostOfID: "",
ContentWarning: "",
Visibility: model.VisibilityUnlocked,
Sensitive: false,
Language: "en",
VisibilityAdvanced: &model.VisibilityAdvanced{
Visibility: gtsmodel.VisibilityUnlocked,
Sensitive: false,
Language: "en",
VisibilityAdvanced: &gtsmodel.VisibilityAdvanced{
Federated: false,
Boostable: 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,
},
}
}