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

@ -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

@ -0,0 +1,5 @@
# Mastotypes
This package contains Go types/structs for Mastodon's REST API.
See [here](https://docs.joinmastodon.org/methods/apps/).

View File

@ -0,0 +1,131 @@
/*
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 "mime/multipart"
// Account represents a mastodon-api Account object, as described here: https://docs.joinmastodon.org/entities/account/
type Account struct {
// The account id
ID string `json:"id"`
// The username of the account, not including domain.
Username string `json:"username"`
// The Webfinger account URI. Equal to username for local users, or username@domain for remote users.
Acct string `json:"acct"`
// The profile's display name.
DisplayName string `json:"display_name"`
// Whether the account manually approves follow requests.
Locked bool `json:"locked"`
// Whether the account has opted into discovery features such as the profile directory.
Discoverable bool `json:"discoverable,omitempty"`
// A presentational flag. Indicates that the account may perform automated actions, may not be monitored, or identifies as a robot.
Bot bool `json:"bot"`
// When the account was created. (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// The profile's bio / description.
Note string `json:"note"`
// The location of the user's profile page.
URL string `json:"url"`
// An image icon that is shown next to statuses and in the profile.
Avatar string `json:"avatar"`
// A static version of the avatar. Equal to avatar if its value is a static image; different if avatar is an animated GIF.
AvatarStatic string `json:"avatar_static"`
// An image banner that is shown above the profile and in profile cards.
Header string `json:"header"`
// A static version of the header. Equal to header if its value is a static image; different if header is an animated GIF.
HeaderStatic string `json:"header_static"`
// The reported followers of this profile.
FollowersCount int `json:"followers_count"`
// The reported follows of this profile.
FollowingCount int `json:"following_count"`
// How many statuses are attached to this account.
StatusesCount int `json:"statuses_count"`
// When the most recent status was posted. (ISO 8601 Datetime)
LastStatusAt string `json:"last_status_at"`
// Custom emoji entities to be used when rendering the profile. If none, an empty array will be returned.
Emojis []Emoji `json:"emojis"`
// Additional metadata attached to a profile as name-value pairs.
Fields []Field `json:"fields"`
// An extra entity returned when an account is suspended.
Suspended bool `json:"suspended,omitempty"`
// When a timed mute will expire, if applicable. (ISO 8601 Datetime)
MuteExpiresAt string `json:"mute_expires_at,omitempty"`
// An extra entity to be used with API methods to verify credentials and update credentials.
Source *Source `json:"source,omitempty"`
}
// AccountCreateRequest represents the form submitted during a POST request to /api/v1/accounts.
// See https://docs.joinmastodon.org/methods/accounts/
type AccountCreateRequest struct {
// Text that will be reviewed by moderators if registrations require manual approval.
Reason string `form:"reason"`
// The desired username for the account
Username string `form:"username" binding:"required"`
// The email address to be used for login
Email string `form:"email" binding:"required"`
// The password to be used for login
Password string `form:"password" binding:"required"`
// Whether the user agrees to the local rules, terms, and policies.
// These should be presented to the user in order to allow them to consent before setting this parameter to TRUE.
Agreement bool `form:"agreement" binding:"required"`
// The language of the confirmation email that will be sent
Locale string `form:"locale" binding:"required"`
}
// UpdateCredentialsRequest represents the form submitted during a PATCH request to /api/v1/accounts/update_credentials.
// See https://docs.joinmastodon.org/methods/accounts/
type UpdateCredentialsRequest struct {
// Whether the account should be shown in the profile directory.
Discoverable *bool `form:"discoverable"`
// Whether the account has a bot flag.
Bot *bool `form:"bot"`
// The display name to use for the profile.
DisplayName *string `form:"display_name"`
// The account bio.
Note *string `form:"note"`
// Avatar image encoded using multipart/form-data
Avatar *multipart.FileHeader `form:"avatar"`
// Header image encoded using multipart/form-data
Header *multipart.FileHeader `form:"header"`
// Whether manual approval of follow requests is required.
Locked *bool `form:"locked"`
// New Source values for this account
Source *UpdateSource `form:"source"`
// Profile metadata name and value
FieldsAttributes *[]UpdateField `form:"fields_attributes"`
}
// UpdateSource is to be used specifically in an UpdateCredentialsRequest.
type UpdateSource struct {
// Default post privacy for authored statuses.
Privacy *string `form:"privacy"`
// Whether to mark authored statuses as sensitive by default.
Sensitive *bool `form:"sensitive"`
// Default language to use for authored statuses. (ISO 6391)
Language *string `form:"language"`
}
// UpdateField is to be used specifically in an UpdateCredentialsRequest.
// By default, max 4 fields and 255 characters per property/value.
type UpdateField struct {
// Name of the field
Name *string `form:"name"`
// Value of the field
Value *string `form:"value"`
}

View File

@ -0,0 +1,31 @@
/*
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
// Activity represents the mastodon-api Activity type. See here: https://docs.joinmastodon.org/entities/activity/
type Activity struct {
// Midnight at the first day of the week. (UNIX Timestamp as string)
Week string `json:"week"`
// Statuses created since the week began. Integer cast to string.
Statuses string `json:"statuses"`
// User logins since the week began. Integer cast as string.
Logins string `json:"logins"`
// User registrations since the week began. Integer cast as string.
Registrations string `json:"registrations"`
}

View File

@ -0,0 +1,81 @@
/*
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
// AdminAccountInfo represents the *admin* view of an account's details. See here: https://docs.joinmastodon.org/entities/admin-account/
type AdminAccountInfo struct {
// The ID of the account in the database.
ID string `json:"id"`
// The username of the account.
Username string `json:"username"`
// The domain of the account.
Domain string `json:"domain"`
// When the account was first discovered. (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// The email address associated with the account.
Email string `json:"email"`
// The IP address last used to login to this account.
IP string `json:"ip"`
// The locale of the account. (ISO 639 Part 1 two-letter language code)
Locale string `json:"locale"`
// Invite request text
InviteRequest string `json:"invite_request"`
// The current role of the account.
Role string `json:"role"`
// Whether the account has confirmed their email address.
Confirmed bool `json:"confirmed"`
// Whether the account is currently approved.
Approved bool `json:"approved"`
// Whether the account is currently disabled.
Disabled bool `json:"disabled"`
// Whether the account is currently silenced
Silenced bool `json:"silenced"`
// Whether the account is currently suspended.
Suspended bool `json:"suspended"`
// User-level information about the account.
Account *Account `json:"account"`
// The ID of the application that created this account.
CreatedByApplicationID string `json:"created_by_application_id,omitempty"`
// The ID of the account that invited this user
InvitedByAccountID string `json:"invited_by_account_id"`
}
// AdminReportInfo represents the *admin* view of a report. See here: https://docs.joinmastodon.org/entities/admin-report/
type AdminReportInfo struct {
// The ID of the report in the database.
ID string `json:"id"`
// The action taken to resolve this report.
ActionTaken string `json:"action_taken"`
// An optional reason for reporting.
Comment string `json:"comment"`
// The time the report was filed. (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// The time of last action on this report. (ISO 8601 Datetime)
UpdatedAt string `json:"updated_at"`
// The account which filed the report.
Account *Account `json:"account"`
// The account being reported.
TargetAccount *Account `json:"target_account"`
// The account of the moderator assigned to this report.
AssignedAccount *Account `json:"assigned_account"`
// The action taken by the moderator who handled the report.
ActionTakenByAccount string `json:"action_taken_by_account"`
// Statuses attached to the report, for context.
Statuses []Status `json:"statuses"`
}

View File

@ -0,0 +1,37 @@
/*
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
// Announcement represents an admin/moderator announcement for local users. See here: https://docs.joinmastodon.org/entities/announcement/
type Announcement struct {
ID string `json:"id"`
Content string `json:"content"`
StartsAt string `json:"starts_at"`
EndsAt string `json:"ends_at"`
AllDay bool `json:"all_day"`
PublishedAt string `json:"published_at"`
UpdatedAt string `json:"updated_at"`
Published bool `json:"published"`
Read bool `json:"read"`
Mentions []Mention `json:"mentions"`
Statuses []Status `json:"statuses"`
Tags []Tag `json:"tags"`
Emojis []Emoji `json:"emoji"`
Reactions []AnnouncementReaction `json:"reactions"`
}

View File

@ -0,0 +1,33 @@
/*
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
// AnnouncementReaction represents a user reaction to admin/moderator announcement. See here: https://docs.joinmastodon.org/entities/announcementreaction/
type AnnouncementReaction struct {
// The emoji used for the reaction. Either a unicode emoji, or a custom emoji's shortcode.
Name string `json:"name"`
// The total number of users who have added this reaction.
Count int `json:"count"`
// Whether the authorized user has added this reaction to the announcement.
Me bool `json:"me"`
// A link to the custom emoji.
URL string `json:"url,omitempty"`
// A link to a non-animated version of the custom emoji.
StaticURL string `json:"static_url,omitempty"`
}

View File

@ -0,0 +1,55 @@
/*
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
// Application represents a mastodon-api Application, as defined here: https://docs.joinmastodon.org/entities/application/.
// Primarily, application is used for allowing apps like Tusky etc to connect to Mastodon on behalf of a user.
// See https://docs.joinmastodon.org/methods/apps/
type Application struct {
// The application ID in the db
ID string `json:"id,omitempty"`
// The name of your application.
Name string `json:"name"`
// The website associated with your application (url)
Website string `json:"website,omitempty"`
// Where the user should be redirected after authorization.
RedirectURI string `json:"redirect_uri,omitempty"`
// ClientID to use when obtaining an oauth token for this application (ie., in client_id parameter of https://docs.joinmastodon.org/methods/apps/)
ClientID string `json:"client_id,omitempty"`
// Client secret to use when obtaining an auth token for this application (ie., in client_secret parameter of https://docs.joinmastodon.org/methods/apps/)
ClientSecret string `json:"client_secret,omitempty"`
// Used for Push Streaming API. Returned with POST /api/v1/apps. Equivalent to https://docs.joinmastodon.org/entities/pushsubscription/#server_key
VapidKey string `json:"vapid_key,omitempty"`
}
// ApplicationPOSTRequest represents a POST request to https://example.org/api/v1/apps.
// See here: https://docs.joinmastodon.org/methods/apps/
// And here: https://docs.joinmastodon.org/client/token/
type ApplicationPOSTRequest struct {
// A name for your application
ClientName string `form:"client_name" binding:"required"`
// Where the user should be redirected after authorization.
// To display the authorization code to the user instead of redirecting
// to a web page, use urn:ietf:wg:oauth:2.0:oob in this parameter.
RedirectURIs string `form:"redirect_uris" binding:"required"`
// Space separated list of scopes. If none is provided, defaults to read.
Scopes string `form:"scopes"`
// A URL to the homepage of your app
Website string `form:"website"`
}

View File

@ -0,0 +1,98 @@
/*
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 "mime/multipart"
// AttachmentRequest represents the form data parameters submitted by a client during a media upload request.
// See: https://docs.joinmastodon.org/methods/statuses/media/
type AttachmentRequest struct {
File *multipart.FileHeader `form:"file"`
Thumbnail *multipart.FileHeader `form:"thumbnail"`
Description string `form:"description"`
Focus string `form:"focus"`
}
// Attachment represents the object returned to a client after a successful media upload request.
// See: https://docs.joinmastodon.org/methods/statuses/media/
type Attachment struct {
// The ID of the attachment in the database.
ID string `json:"id"`
// The type of the attachment.
// unknown = unsupported or unrecognized file type.
// image = Static image.
// gifv = Looping, soundless animation.
// video = Video clip.
// audio = Audio track.
Type string `json:"type"`
// The location of the original full-size attachment.
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 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.
// May contain subtrees small and original, as well as various other top-level properties.
// More importantly, there may be another top-level focus Hash object as of 2.3.0, with coordinates can be used for smart thumbnail cropping.
// See https://docs.joinmastodon.org/methods/statuses/media/#focal-points points for more.
Meta MediaMeta `json:"meta,omitempty"`
// Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load.
Description string `json:"description,omitempty"`
// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.
// See https://github.com/woltapp/blurhash
Blurhash string `json:"blurhash,omitempty"`
}
// MediaMeta describes the returned media
type MediaMeta struct {
Length string `json:"length,omitempty"`
Duration float32 `json:"duration,omitempty"`
FPS uint16 `json:"fps,omitempty"`
Size string `json:"size,omitempty"`
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
Aspect float32 `json:"aspect,omitempty"`
AudioEncode string `json:"audio_encode,omitempty"`
AudioBitrate string `json:"audio_bitrate,omitempty"`
AudioChannels string `json:"audio_channels,omitempty"`
Original MediaDimensions `json:"original"`
Small MediaDimensions `json:"small,omitempty"`
Focus MediaFocus `json:"focus,omitempty"`
}
// MediaFocus describes the focal point of a piece of media. It should be returned to the caller as part of MediaMeta.
type MediaFocus struct {
X float32 `json:"x"` // should be between -1 and 1
Y float32 `json:"y"` // should be between -1 and 1
}
// MediaDimensions describes the physical properties of a piece of media. It should be returned to the caller as part of MediaMeta.
type MediaDimensions struct {
Width int `json:"width,omitempty"`
Height int `json:"height,omitempty"`
FrameRate string `json:"frame_rate,omitempty"`
Duration float32 `json:"duration,omitempty"`
Bitrate int `json:"bitrate,omitempty"`
Size string `json:"size,omitempty"`
Aspect float32 `json:"aspect,omitempty"`
}

View File

@ -0,0 +1,61 @@
/*
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
// Card represents a rich preview card that is generated using OpenGraph tags from a URL. See here: https://docs.joinmastodon.org/entities/card/
type Card struct {
// REQUIRED
// Location of linked resource.
URL string `json:"url"`
// Title of linked resource.
Title string `json:"title"`
// Description of preview.
Description string `json:"description"`
// The type of the preview card.
// String (Enumerable, oneOf)
// link = Link OEmbed
// photo = Photo OEmbed
// video = Video OEmbed
// rich = iframe OEmbed. Not currently accepted, so won't show up in practice.
Type string `json:"type"`
// OPTIONAL
// The author of the original resource.
AuthorName string `json:"author_name"`
// A link to the author of the original resource.
AuthorURL string `json:"author_url"`
// The provider of the original resource.
ProviderName string `json:"provider_name"`
// A link to the provider of the original resource.
ProviderURL string `json:"provider_url"`
// HTML to be used for generating the preview card.
HTML string `json:"html"`
// Width of preview, in pixels.
Width int `json:"width"`
// Height of preview, in pixels.
Height int `json:"height"`
// Preview thumbnail.
Image string `json:"image"`
// Used for photo embeds, instead of custom html.
EmbedURL string `json:"embed_url"`
// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.
Blurhash string `json:"blurhash"`
}

View File

@ -0,0 +1,27 @@
/*
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
// Context represents the tree around a given status. Used for reconstructing threads of statuses. See: https://docs.joinmastodon.org/entities/context/
type Context struct {
// Parents in the thread.
Ancestors []Status `json:"ancestors"`
// Children in the thread.
Descendants []Status `json:"descendants"`
}

View File

@ -0,0 +1,36 @@
/*
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
// Conversation represents a conversation with "direct message" visibility. See https://docs.joinmastodon.org/entities/conversation/
type Conversation struct {
// REQUIRED
// Local database ID of the conversation.
ID string `json:"id"`
// Participants in the conversation.
Accounts []Account `json:"accounts"`
// Is the conversation currently marked as unread?
Unread bool `json:"unread"`
// OPTIONAL
// The last status in the conversation, to be used for optional display.
LastStatus *Status `json:"last_status"`
}

View File

@ -0,0 +1,38 @@
/*
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
// Emoji represents a custom emoji. See https://docs.joinmastodon.org/entities/emoji/
type Emoji struct {
// REQUIRED
// The name of the custom emoji.
Shortcode string `json:"shortcode"`
// A link to the custom emoji.
URL string `json:"url"`
// A link to a static copy of the custom emoji.
StaticURL string `json:"static_url"`
// Whether this Emoji should be visible in the picker or unlisted.
VisibleInPicker bool `json:"visible_in_picker"`
// OPTIONAL
// Used for sorting custom emoji in the picker.
Category string `json:"category,omitempty"`
}

View File

@ -0,0 +1,32 @@
/*
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
// Error represents an error message returned from the API. See https://docs.joinmastodon.org/entities/error/
type Error struct {
// REQUIRED
// The error message.
Error string `json:"error"`
// OPTIONAL
// A longer description of the error, mainly provided with the OAuth API.
ErrorDescription string `json:"error_description"`
}

View File

@ -0,0 +1,33 @@
/*
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
// FeaturedTag represents a hashtag that is featured on a profile. See https://docs.joinmastodon.org/entities/featuredtag/
type FeaturedTag struct {
// The internal ID of the featured tag in the database.
ID string `json:"id"`
// The name of the hashtag being featured.
Name string `json:"name"`
// A link to all statuses by a user that contain this hashtag.
URL string `json:"url"`
// The number of authored statuses containing this hashtag.
StatusesCount int `json:"statuses_count"`
// The timestamp of the last authored status containing this hashtag. (ISO 8601 Datetime)
LastStatusAt string `json:"last_status_at"`
}

View File

@ -0,0 +1,33 @@
/*
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
// Field represents a profile field as a name-value pair with optional verification. See https://docs.joinmastodon.org/entities/field/
type Field struct {
// REQUIRED
// The key of a given field's key-value pair.
Name string `json:"name"`
// The value associated with the name key.
Value string `json:"value"`
// OPTIONAL
// Timestamp of when the server verified a URL value for a rel="me” link. String (ISO 8601 Datetime) if value is a verified URL
VerifiedAt string `json:"verified_at,omitempty"`
}

View File

@ -0,0 +1,46 @@
/*
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
// Filter represents a user-defined filter for determining which statuses should not be shown to the user. See https://docs.joinmastodon.org/entities/filter/
// If whole_word is true , client app should do:
// Define word constituent character for your app. In the official implementation, its [A-Za-z0-9_] in JavaScript, and [[:word:]] in Ruby.
// Ruby uses the POSIX character class (Letter | Mark | Decimal_Number | Connector_Punctuation).
// If the phrase starts with a word character, and if the previous character before matched range is a word character, its matched range should be treated to not match.
// If the phrase ends with a word character, and if the next character after matched range is a word character, its matched range should be treated to not match.
// Please check app/javascript/mastodon/selectors/index.js and app/lib/feed_manager.rb in the Mastodon source code for more details.
type Filter struct {
// The ID of the filter in the database.
ID string `json:"id"`
// The text to be filtered.
Phrase string `json:"text"`
// The contexts in which the filter should be applied.
// Array of String (Enumerable anyOf)
// home = home timeline and lists
// notifications = notifications timeline
// public = public timelines
// thread = expanded thread of a detailed status
Context []string `json:"context"`
// Should the filter consider word boundaries?
WholeWord bool `json:"whole_word"`
// When the filter should no longer be applied (ISO 8601 Datetime), or null if the filter does not expire
ExpiresAt string `json:"expires_at,omitempty"`
// Should matching entities in home and notifications be dropped by the server?
Irreversible bool `json:"irreversible"`
}

View File

@ -0,0 +1,29 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mastotypes
// History represents daily usage history of a hashtag. See https://docs.joinmastodon.org/entities/history/
type History struct {
// UNIX timestamp on midnight of the given day (string cast from integer).
Day string `json:"day"`
// The counted usage of the tag within that day (string cast from integer).
Uses string `json:"uses"`
// The total of accounts using the tag within that day (string cast from integer).
Accounts string `json:"accounts"`
}

View File

@ -0,0 +1,33 @@
/*
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
// IdentityProof represents a proof from an external identity provider. See https://docs.joinmastodon.org/entities/identityproof/
type IdentityProof struct {
// The name of the identity provider.
Provider string `json:"provider"`
// The account owner's username on the identity provider's service.
ProviderUsername string `json:"provider_username"`
// The account owner's profile URL on the identity provider.
ProfileURL string `json:"profile_url"`
// A link to a statement of identity proof, hosted by the identity provider.
ProofURL string `json:"proof_url"`
// When the identity proof was last updated.
UpdatedAt string `json:"updated_at"`
}

View File

@ -0,0 +1,72 @@
/*
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
// Instance represents the software instance of Mastodon running on this domain. See https://docs.joinmastodon.org/entities/instance/
type Instance struct {
// REQUIRED
// The domain name of the instance.
URI string `json:"uri"`
// The title of the website.
Title string `json:"title"`
// Admin-defined description of the Mastodon site.
Description string `json:"description"`
// A shorter description defined by the admin.
ShortDescription string `json:"short_description"`
// An email that may be contacted for any inquiries.
Email string `json:"email"`
// The version of Mastodon installed on the instance.
Version string `json:"version"`
// Primary langauges of the website and its staff.
Languages []string `json:"languages"`
// Whether registrations are enabled.
Registrations bool `json:"registrations"`
// Whether registrations require moderator approval.
ApprovalRequired bool `json:"approval_required"`
// Whether invites are enabled.
InvitesEnabled bool `json:"invites_enabled"`
// URLs of interest for clients apps.
URLS *InstanceURLs `json:"urls"`
// Statistics about how much information the instance contains.
Stats *InstanceStats `json:"stats"`
// OPTIONAL
// Banner image for the website.
Thumbnail string `json:"thumbnail,omitempty"`
// A user that can be contacted, as an alternative to email.
ContactAccount *Account `json:"contact_account,omitempty"`
}
// InstanceURLs represents URLs necessary for successfully connecting to the instance as a user. See https://docs.joinmastodon.org/entities/instance/
type InstanceURLs struct {
// Websockets address for push streaming.
StreamingAPI string `json:"streaming_api"`
}
// InstanceStats represents some public-facing stats about the instance. See https://docs.joinmastodon.org/entities/instance/
type InstanceStats struct {
// Users registered on this instance.
UserCount int `json:"user_count"`
// Statuses authored by users on instance.
StatusCount int `json:"status_count"`
// Domains federated with this instance.
DomainCount int `json:"domain_count"`
}

View File

@ -0,0 +1,31 @@
/*
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
// List represents a list of some users that the authenticated user follows. See https://docs.joinmastodon.org/entities/list/
type List struct {
// The internal database ID of the list.
ID string `json:"id"`
// The user-defined title of the list.
Title string `json:"title"`
// followed = Show replies to any followed user
// list = Show replies to members of the list
// none = Show replies to no one
RepliesPolicy string `json:"replies_policy"`
}

View File

@ -0,0 +1,37 @@
/*
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
// Marker represents the last read position within a user's timelines. See https://docs.joinmastodon.org/entities/marker/
type Marker struct {
// Information about the user's position in the home timeline.
Home *TimelineMarker `json:"home"`
// Information about the user's position in their notifications.
Notifications *TimelineMarker `json:"notifications"`
}
// TimelineMarker contains information about a user's progress through a specific timeline. See https://docs.joinmastodon.org/entities/marker/
type TimelineMarker struct {
// The ID of the most recently viewed entity.
LastReadID string `json:"last_read_id"`
// The timestamp of when the marker was set (ISO 8601 Datetime)
UpdatedAt string `json:"updated_at"`
// Used for locking to prevent write conflicts.
Version string `json:"version"`
}

View File

@ -0,0 +1,31 @@
/*
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
// Mention represents the mastodon-api mention type, as documented here: https://docs.joinmastodon.org/entities/mention/
type Mention struct {
// The account id of the mentioned user.
ID string `json:"id"`
// The username of the mentioned user.
Username string `json:"username"`
// The location of the mentioned user's profile.
URL string `json:"url"`
// The webfinger acct: URI of the mentioned user. Equivalent to username for local users, or username@domain for remote users.
Acct string `json:"acct"`
}

View File

@ -0,0 +1,45 @@
/*
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
// Notification represents a notification of an event relevant to the user. See https://docs.joinmastodon.org/entities/notification/
type Notification struct {
// REQUIRED
// The id of the notification in the database.
ID string `json:"id"`
// The type of event that resulted in the notification.
// follow = Someone followed you
// follow_request = Someone requested to follow you
// mention = Someone mentioned you in their status
// reblog = Someone boosted one of your statuses
// favourite = Someone favourited one of your statuses
// poll = A poll you have voted in or created has ended
// status = Someone you enabled notifications for has posted a status
Type string `json:"type"`
// The timestamp of the notification (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// The account that performed the action that generated the notification.
Account *Account `json:"account"`
// OPTIONAL
// Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.
Status *Status `json:"status"`
}

View File

@ -0,0 +1,37 @@
/*
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
// OAuthAuthorize represents a request sent to https://example.org/oauth/authorize
// See here: https://docs.joinmastodon.org/methods/apps/oauth/
type OAuthAuthorize struct {
// Forces the user to re-login, which is necessary for authorizing with multiple accounts from the same instance.
ForceLogin string `form:"force_login,omitempty"`
// Should be set equal to `code`.
ResponseType string `form:"response_type"`
// Client ID, obtained during app registration.
ClientID string `form:"client_id"`
// Set a URI to redirect the user to.
// If this parameter is set to urn:ietf:wg:oauth:2.0:oob then the authorization code will be shown instead.
// Must match one of the redirect URIs declared during app registration.
RedirectURI string `form:"redirect_uri"`
// List of requested OAuth scopes, separated by spaces (or by pluses, if using query parameters).
// Must be a subset of scopes declared during app registration. If not provided, defaults to read.
Scope string `form:"scope,omitempty"`
}

View File

@ -0,0 +1,64 @@
/*
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
// Poll represents the mastodon-api poll type, as described here: https://docs.joinmastodon.org/entities/poll/
type Poll struct {
// The ID of the poll in the database.
ID string `json:"id"`
// When the poll ends. (ISO 8601 Datetime), or null if the poll does not end
ExpiresAt string `json:"expires_at"`
// Is the poll currently expired?
Expired bool `json:"expired"`
// Does the poll allow multiple-choice answers?
Multiple bool `json:"multiple"`
// How many votes have been received.
VotesCount int `json:"votes_count"`
// How many unique accounts have voted on a multiple-choice poll. Null if multiple is false.
VotersCount int `json:"voters_count,omitempty"`
// When called with a user token, has the authorized user voted?
Voted bool `json:"voted,omitempty"`
// When called with a user token, which options has the authorized user chosen? Contains an array of index values for options.
OwnVotes []int `json:"own_votes,omitempty"`
// Possible answers for the poll.
Options []PollOptions `json:"options"`
// Custom emoji to be used for rendering poll options.
Emojis []Emoji `json:"emojis"`
}
// PollOptions represents the current vote counts for different poll options
type PollOptions struct {
// The text value of the poll option. String.
Title string `json:"title"`
// The number of received votes for this option. Number, or null if results are not published yet.
VotesCount int `json:"votes_count,omitempty"`
}
// PollRequest represents a mastodon-api poll attached to a status POST request, as defined here: https://docs.joinmastodon.org/methods/statuses/
// It should be used at the path https://example.org/api/v1/statuses
type PollRequest struct {
// Array of possible answers. If provided, media_ids cannot be used, and poll[expires_in] must be provided.
Options []string `form:"options"`
// Duration the poll should be open, in seconds. If provided, media_ids cannot be used, and poll[options] must be provided.
ExpiresIn int `form:"expires_in"`
// Allow multiple choices?
Multiple bool `form:"multiple"`
// Hide vote counts until the poll ends?
HideTotals bool `form:"hide_totals"`
}

View File

@ -0,0 +1,40 @@
/*
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
// Preferences represents a user's preferences. See https://docs.joinmastodon.org/entities/preferences/
type Preferences struct {
// Default visibility for new posts.
// public = Public post
// unlisted = Unlisted post
// private = Followers-only post
// direct = Direct post
PostingDefaultVisibility string `json:"posting:default:visibility"`
// Default sensitivity flag for new posts.
PostingDefaultSensitive bool `json:"posting:default:sensitive"`
// Default language for new posts. (ISO 639-1 language two-letter code), or null
PostingDefaultLanguage string `json:"posting:default:language,omitempty"`
// Whether media attachments should be automatically displayed or blurred/hidden.
// default = Hide media marked as sensitive
// show_all = Always show all media by default, regardless of sensitivity
// hide_all = Always hide all media by default, regardless of sensitivity
ReadingExpandMedia string `json:"reading:expand:media"`
// Whether CWs should be expanded by default.
ReadingExpandSpoilers bool `json:"reading:expand:spoilers"`
}

View File

@ -0,0 +1,45 @@
/*
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
// PushSubscription represents a subscription to the push streaming server. See https://docs.joinmastodon.org/entities/pushsubscription/
type PushSubscription struct {
// The id of the push subscription in the database.
ID string `json:"id"`
// Where push alerts will be sent to.
Endpoint string `json:"endpoint"`
// The streaming server's VAPID key.
ServerKey string `json:"server_key"`
// Which alerts should be delivered to the endpoint.
Alerts *PushSubscriptionAlerts `json:"alerts"`
}
// PushSubscriptionAlerts represents the specific alerts that this push subscription will give.
type PushSubscriptionAlerts struct {
// Receive a push notification when someone has followed you?
Follow bool `json:"follow"`
// Receive a push notification when a status you created has been favourited by someone else?
Favourite bool `json:"favourite"`
// Receive a push notification when someone else has mentioned you in a status?
Mention bool `json:"mention"`
// Receive a push notification when a status you created has been boosted by someone else?
Reblog bool `json:"reblog"`
// Receive a push notification when a poll you voted in or created has ended?
Poll bool `json:"poll"`
}

View File

@ -0,0 +1,49 @@
/*
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
// Relationship represents a relationship between accounts. See https://docs.joinmastodon.org/entities/relationship/
type Relationship struct {
// The account id.
ID string `json:"id"`
// Are you following this user?
Following bool `json:"following"`
// Are you receiving this user's boosts in your home timeline?
ShowingReblogs bool `json:"showing_reblogs"`
// Have you enabled notifications for this user?
Notifying bool `json:"notifying"`
// Are you followed by this user?
FollowedBy bool `json:"followed_by"`
// Are you blocking this user?
Blocking bool `json:"blocking"`
// Is this user blocking you?
BlockedBy bool `json:"blocked_by"`
// Are you muting this user?
Muting bool `json:"muting"`
// Are you muting notifications from this user?
MutingNotifications bool `json:"muting_notifications"`
// Do you have a pending follow request for this user?
Requested bool `json:"requested"`
// Are you blocking this user's domain?
DomainBlocking bool `json:"domain_blocking"`
// Are you featuring this user on your profile?
Endorsed bool `json:"endorsed"`
// Your note on this account.
Note string `json:"note"`
}

View File

@ -0,0 +1,29 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mastotypes
// Results represents the results of a search. See https://docs.joinmastodon.org/entities/results/
type Results struct {
// Accounts which match the given query
Accounts []Account `json:"accounts"`
// Statuses which match the given query
Statuses []Status `json:"statuses"`
// Hashtags which match the given query
Hashtags []Tag `json:"hashtags"`
}

View File

@ -0,0 +1,39 @@
/*
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
// ScheduledStatus represents a status that will be published at a future scheduled date. See https://docs.joinmastodon.org/entities/scheduledstatus/
type ScheduledStatus struct {
ID string `json:"id"`
ScheduledAt string `json:"scheduled_at"`
Params *StatusParams `json:"params"`
MediaAttachments []Attachment `json:"media_attachments"`
}
// StatusParams represents parameters for a scheduled status. See https://docs.joinmastodon.org/entities/scheduledstatus/
type StatusParams struct {
Text string `json:"text"`
InReplyToID string `json:"in_reply_to_id,omitempty"`
MediaIDs []string `json:"media_ids,omitempty"`
Sensitive bool `json:"sensitive,omitempty"`
SpoilerText string `json:"spoiler_text,omitempty"`
Visibility string `json:"visibility"`
ScheduledAt string `json:"scheduled_at,omitempty"`
ApplicationID string `json:"application_id"`
}

View File

@ -0,0 +1,41 @@
/*
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
// Source represents display or publishing preferences of user's own account.
// Returned as an additional entity when verifying and updated credentials, as an attribute of Account.
// See https://docs.joinmastodon.org/entities/source/
type Source struct {
// The default post privacy to be used for new statuses.
// public = Public post
// unlisted = Unlisted post
// private = Followers-only post
// direct = Direct post
Privacy Visibility `json:"privacy,omitempty"`
// Whether new statuses should be marked sensitive by default.
Sensitive bool `json:"sensitive,omitempty"`
// The default posting language for new statuses.
Language string `json:"language,omitempty"`
// Profile bio.
Note string `json:"note"`
// Metadata about the account.
Fields []Field `json:"fields"`
// The number of pending follow requests.
FollowRequestsCount int `json:"follow_requests_count,omitempty"`
}

View File

@ -0,0 +1,119 @@
/*
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
// Status represents a mastodon-api Status type, as defined here: https://docs.joinmastodon.org/entities/status/
type Status struct {
// ID of the status in the database.
ID string `json:"id"`
// The date when this status was created (ISO 8601 Datetime)
CreatedAt string `json:"created_at"`
// ID of the status being replied.
InReplyToID string `json:"in_reply_to_id,omitempty"`
// ID of the account being replied to.
InReplyToAccountID string `json:"in_reply_to_account_id,omitempty"`
// Is this status marked as sensitive content?
Sensitive bool `json:"sensitive"`
// Subject or summary line, below which status content is collapsed until expanded.
SpoilerText string `json:"spoiler_text,omitempty"`
// Visibility of this status.
Visibility Visibility `json:"visibility"`
// Primary language of this status. (ISO 639 Part 1 two-letter language code)
Language string `json:"language"`
// URI of the status used for federation.
URI string `json:"uri"`
// A link to the status's HTML representation.
URL string `json:"url"`
// How many replies this status has received.
RepliesCount int `json:"replies_count"`
// How many boosts this status has received.
ReblogsCount int `json:"reblogs_count"`
// How many favourites this status has received.
FavouritesCount int `json:"favourites_count"`
// Have you favourited this status?
Favourited bool `json:"favourited"`
// Have you boosted this status?
Reblogged bool `json:"reblogged"`
// Have you muted notifications for this status's conversation?
Muted bool `json:"muted"`
// Have you bookmarked this status?
Bookmarked bool `json:"bookmarked"`
// Have you pinned this status? Only appears if the status is pinnable.
Pinned bool `json:"pinned"`
// HTML-encoded status content.
Content string `json:"content"`
// The status being reblogged.
Reblog *Status `json:"reblog,omitempty"`
// The application used to post this status.
Application *Application `json:"application"`
// The account that authored this status.
Account *Account `json:"account"`
// Media that is attached to this status.
MediaAttachments []Attachment `json:"media_attachments"`
// Mentions of users within the status content.
Mentions []Mention `json:"mentions"`
// Hashtags used within the status content.
Tags []Tag `json:"tags"`
// Custom emoji to be used when rendering status content.
Emojis []Emoji `json:"emojis"`
// Preview card for links included within status content.
Card *Card `json:"card"`
// The poll attached to the status.
Poll *Poll `json:"poll"`
// Plain-text source of a status. Returned instead of content when status is deleted,
// so the user may redraft from the source text without the client having to reverse-engineer
// the original text from the HTML content.
Text string `json:"text"`
}
// StatusCreateRequest represents a mastodon-api status POST request, as defined here: https://docs.joinmastodon.org/methods/statuses/
// It should be used at the path https://mastodon.example/api/v1/statuses
type StatusCreateRequest struct {
// Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.
Status string `form:"status"`
// Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used.
MediaIDs []string `form:"media_ids"`
// Poll to include with this status.
Poll *PollRequest `form:"poll"`
// ID of the status being replied to, if status is a reply
InReplyToID string `form:"in_reply_to_id"`
// Mark status and attached media as sensitive?
Sensitive bool `form:"sensitive"`
// Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
SpoilerText string `form:"spoiler_text"`
// Visibility of the posted status. Enumerable oneOf public, unlisted, private, direct.
Visibility Visibility `form:"visibility"`
// ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future.
ScheduledAt string `form:"scheduled_at"`
// ISO 639 language code for this status.
Language string `form:"language"`
}
type Visibility string
const (
// visible to everyone
VisibilityPublic Visibility = "public"
// visible to everyone but only on home timelines or in lists
VisibilityUnlisted Visibility = "unlisted"
// visible to followers only
VisibilityPrivate Visibility = "private"
// visible only to tagged recipients
VisibilityDirect Visibility = "direct"
)

View File

@ -0,0 +1,23 @@
/*
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
// Tag represents a hashtag used within the content of a status. See https://docs.joinmastodon.org/entities/tag/
type Tag struct {
}

View File

@ -0,0 +1,31 @@
/*
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
// Token represents an OAuth token used for authenticating with the API and performing actions.. See https://docs.joinmastodon.org/entities/token/
type Token struct {
// An OAuth token to be used for authorization.
AccessToken string `json:"access_token"`
// The OAuth token type. Mastodon uses Bearer tokens.
TokenType string `json:"token_type"`
// The OAuth scopes granted by this token, space-separated.
Scope string `json:"scope"`
// When the token was generated. (UNIX timestamp seconds)
CreatedAt int64 `json:"created_at"`
}

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() {