diff --git a/cmd/gotosocial/main.go b/cmd/gotosocial/main.go
index 091678b29..7f185d8d9 100644
--- a/cmd/gotosocial/main.go
+++ b/cmd/gotosocial/main.go
@@ -153,8 +153,8 @@ func main() {
 			},
 			&cli.StringFlag{
 				Name:    flagNames.StorageBasePath,
-				Usage:   "Full path to an already-created directory where gts should store/retrieve media files",
-				Value:   "/opt/gotosocial",
+				Usage:   "Full path to an already-created directory where gts should store/retrieve media files. Subfolders will be created within this dir.",
+				Value:   "/gotosocial/storage/media",
 				EnvVars: []string{envNames.StorageBasePath},
 			},
 			&cli.StringFlag{
diff --git a/internal/apimodule/account/account.go b/internal/apimodule/account/account.go
index 2d9ddbb72..a94169eb2 100644
--- a/internal/apimodule/account/account.go
+++ b/internal/apimodule/account/account.go
@@ -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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.MediaAttachment{},
 	}
 
 	for _, m := range models {
diff --git a/internal/apimodule/account/accountcreate.go b/internal/apimodule/account/accountcreate.go
index 58b98c0e4..266d820af 100644
--- a/internal/apimodule/account/accountcreate.go
+++ b/internal/apimodule/account/accountcreate.go
@@ -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
diff --git a/internal/apimodule/account/accountcreate_test.go b/internal/apimodule/account/accountcreate_test.go
index d5470b919..8677e3573 100644
--- a/internal/apimodule/account/accountcreate_test.go
+++ b/internal/apimodule/account/accountcreate_test.go
@@ -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 = >smodel.Account{
 		ID:       uuid.NewString(),
 		Username: "test_user",
 	}
 
 	// can use this test application throughout
-	suite.testApplication = &model.Application{
+	suite.testApplication = >smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.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 := >smodel.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 := >smodel.User{}
 	err = suite.db.GetWhere("unconfirmed_email", suite.newUserFormHappyPath.Get("email"), usr)
 	assert.Nil(suite.T(), err)
 	assert.NotNil(suite.T(), usr)
diff --git a/internal/apimodule/account/accountget.go b/internal/apimodule/account/accountget.go
index 5ee93386d..cd4aed22e 100644
--- a/internal/apimodule/account/accountget.go
+++ b/internal/apimodule/account/accountget.go
@@ -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 := >smodel.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
diff --git a/internal/apimodule/account/accountupdate.go b/internal/apimodule/account/accountupdate.go
index 6686d3a50..ba245b929 100644
--- a/internal/apimodule/account/accountupdate.go
+++ b/internal/apimodule/account/accountupdate.go
@@ -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, >smodel.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, >smodel.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, >smodel.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, >smodel.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, >smodel.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, >smodel.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, >smodel.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, >smodel.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 := >smodel.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)
diff --git a/internal/apimodule/account/accountupdate_test.go b/internal/apimodule/account/accountupdate_test.go
index 651b4d29d..7ca2190d8 100644
--- a/internal/apimodule/account/accountupdate_test.go
+++ b/internal/apimodule/account/accountupdate_test.go
@@ -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 = >smodel.Account{
 		ID:       uuid.NewString(),
 		Username: "test_user",
 	}
 
 	// can use this test application throughout
-	suite.testApplication = &model.Application{
+	suite.testApplication = >smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.MediaAttachment{},
 	}
 	for _, m := range models {
 		if err := suite.db.DropTable(m); err != nil {
diff --git a/internal/apimodule/account/accountverify.go b/internal/apimodule/account/accountverify.go
index fe8d24b22..584ab6122 100644
--- a/internal/apimodule/account/accountverify.go
+++ b/internal/apimodule/account/accountverify.go
@@ -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()})
diff --git a/internal/apimodule/app/app.go b/internal/apimodule/app/app.go
index 534f4cd3e..08292acd1 100644
--- a/internal/apimodule/app/app.go
+++ b/internal/apimodule/app/app.go
@@ -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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Application{},
 	}
 
 	for _, m := range models {
diff --git a/internal/apimodule/app/appcreate.go b/internal/apimodule/app/appcreate.go
index 1adcef573..ec52a9d37 100644
--- a/internal/apimodule/app/appcreate.go
+++ b/internal/apimodule/app/appcreate.go
@@ -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 := >smodel.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)
 }
diff --git a/internal/apimodule/auth/auth.go b/internal/apimodule/auth/auth.go
index 3a85a4364..b70adeb43 100644
--- a/internal/apimodule/auth/auth.go
+++ b/internal/apimodule/auth/auth.go
@@ -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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Application{},
 	}
 
 	for _, m := range models {
diff --git a/internal/apimodule/auth/auth_test.go b/internal/apimodule/auth/auth_test.go
index 0ec9b4a41..351c086e4 100644
--- a/internal/apimodule/auth/auth_test.go
+++ b/internal/apimodule/auth/auth_test.go
@@ -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 = >smodel.Account{
 		ID:       acctID,
 		Username: "test_user",
 	}
-	suite.testUser = &model.User{
+	suite.testUser = >smodel.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 = >smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Application{},
 	}
 	for _, m := range models {
 		if err := suite.db.DropTable(m); err != nil {
diff --git a/internal/apimodule/auth/authorize.go b/internal/apimodule/auth/authorize.go
index 4a27cc20e..bf525e09e 100644
--- a/internal/apimodule/auth/authorize.go
+++ b/internal/apimodule/auth/authorize.go
@@ -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 := >smodel.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 := >smodel.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 := >smodel.Account{
 		ID: user.AccountID,
 	}
 
diff --git a/internal/apimodule/auth/middleware.go b/internal/apimodule/auth/middleware.go
index 32fc24d52..4ca1f47a2 100644
--- a/internal/apimodule/auth/middleware.go
+++ b/internal/apimodule/auth/middleware.go
@@ -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 := >smodel.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 := >smodel.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 := >smodel.Application{}
 		if err := m.db.GetWhere("client_id", cid, app); err != nil {
 			l.Tracef("no app found for client %s", cid)
 		}
diff --git a/internal/apimodule/auth/signin.go b/internal/apimodule/auth/signin.go
index 34146cbfc..a6994c90e 100644
--- a/internal/apimodule/auth/signin.go
+++ b/internal/apimodule/auth/signin.go
@@ -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 := >smodel.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)
diff --git a/internal/apimodule/fileserver/fileserver.go b/internal/apimodule/fileserver/fileserver.go
index bbafff76f..c82c9bbf1 100644
--- a/internal/apimodule/fileserver/fileserver.go
+++ b/internal/apimodule/fileserver/fileserver.go
@@ -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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.MediaAttachment{},
 	}
 
 	for _, m := range models {
diff --git a/internal/apimodule/status/status.go b/internal/apimodule/status/status.go
index a6b97fe21..02ae77f7c 100644
--- a/internal/apimodule/status/status.go
+++ b/internal/apimodule/status/status.go
@@ -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{},
+		>smodel.User{},
+		>smodel.Account{},
+		>smodel.Follow{},
+		>smodel.FollowRequest{},
+		>smodel.Status{},
+		>smodel.Application{},
+		>smodel.EmailDomainBlock{},
+		>smodel.MediaAttachment{},
+		>smodel.Emoji{},
+		>smodel.Tag{},
+		>smodel.Mention{},
 	}
 
 	for _, m := range models {
diff --git a/internal/apimodule/status/statuscreate.go b/internal/apimodule/status/statuscreate.go
index 0981caaf6..5687bacbf 100644
--- a/internal/apimodule/status/statuscreate.go
+++ b/internal/apimodule/status/statuscreate.go
@@ -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 := >smodel.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 := >smodel.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 := >smodel.Status{}
+	repliedAccount := >smodel.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 := >smodel.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 {
diff --git a/internal/apimodule/status/statuscreate_test.go b/internal/apimodule/status/statuscreate_test.go
index 6a6aa9eee..446eab93b 100644
--- a/internal/apimodule/status/statuscreate_test.go
+++ b/internal/apimodule/status/statuscreate_test.go
@@ -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) {
diff --git a/internal/db/db.go b/internal/db/db.go
index bf516dfc7..140bca88c 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -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,
diff --git a/internal/db/model/README.md b/internal/db/gtsmodel/README.md
similarity index 100%
rename from internal/db/model/README.md
rename to internal/db/gtsmodel/README.md
diff --git a/internal/db/model/account.go b/internal/db/gtsmodel/account.go
similarity index 96%
rename from internal/db/model/account.go
rename to internal/db/gtsmodel/account.go
index 3601f53b8..82f5b5c6f 100644
--- a/internal/db/model/account.go
+++ b/internal/db/gtsmodel/account.go
@@ -16,11 +16,11 @@
    along with this program.  If not, see .
 */
 
-// 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
diff --git a/internal/db/model/activitystreams.go b/internal/db/gtsmodel/activitystreams.go
similarity index 99%
rename from internal/db/model/activitystreams.go
rename to internal/db/gtsmodel/activitystreams.go
index b6c9df662..059588a57 100644
--- a/internal/db/model/activitystreams.go
+++ b/internal/db/gtsmodel/activitystreams.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 // ActivityStreamsObject refers to https://www.w3.org/TR/activitystreams-vocabulary/#object-types
 type ActivityStreamsObject string
diff --git a/internal/db/model/application.go b/internal/db/gtsmodel/application.go
similarity index 72%
rename from internal/db/model/application.go
rename to internal/db/gtsmodel/application.go
index 439155264..8e1398beb 100644
--- a/internal/db/model/application.go
+++ b/internal/db/gtsmodel/application.go
@@ -16,9 +16,7 @@
    along with this program.  If not, see .
 */
 
-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,
-	}
-}
diff --git a/internal/db/model/block.go b/internal/db/gtsmodel/block.go
similarity index 97%
rename from internal/db/model/block.go
rename to internal/db/gtsmodel/block.go
index d106e25e2..fae43fbef 100644
--- a/internal/db/model/block.go
+++ b/internal/db/gtsmodel/block.go
@@ -1,4 +1,4 @@
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/domainblock.go b/internal/db/gtsmodel/domainblock.go
similarity index 99%
rename from internal/db/model/domainblock.go
rename to internal/db/gtsmodel/domainblock.go
index e6e89bc20..dcfb2acee 100644
--- a/internal/db/model/domainblock.go
+++ b/internal/db/gtsmodel/domainblock.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/emaildomainblock.go b/internal/db/gtsmodel/emaildomainblock.go
similarity index 98%
rename from internal/db/model/emaildomainblock.go
rename to internal/db/gtsmodel/emaildomainblock.go
index 6610a2075..4cda68b02 100644
--- a/internal/db/model/emaildomainblock.go
+++ b/internal/db/gtsmodel/emaildomainblock.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/emoji.go b/internal/db/gtsmodel/emoji.go
similarity index 98%
rename from internal/db/model/emoji.go
rename to internal/db/gtsmodel/emoji.go
index 0aaa1d724..d704ef5b4 100644
--- a/internal/db/model/emoji.go
+++ b/internal/db/gtsmodel/emoji.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/follow.go b/internal/db/gtsmodel/follow.go
similarity index 98%
rename from internal/db/model/follow.go
rename to internal/db/gtsmodel/follow.go
index 36e19e72e..90080da6e 100644
--- a/internal/db/model/follow.go
+++ b/internal/db/gtsmodel/follow.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/followrequest.go b/internal/db/gtsmodel/followrequest.go
similarity index 99%
rename from internal/db/model/followrequest.go
rename to internal/db/gtsmodel/followrequest.go
index 50d8a5f03..1401a26f1 100644
--- a/internal/db/model/followrequest.go
+++ b/internal/db/gtsmodel/followrequest.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/mediaattachment.go b/internal/db/gtsmodel/mediaattachment.go
similarity index 88%
rename from internal/db/model/mediaattachment.go
rename to internal/db/gtsmodel/mediaattachment.go
index 3aff18d80..a906f8350 100644
--- a/internal/db/model/mediaattachment.go
+++ b/internal/db/gtsmodel/mediaattachment.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-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
diff --git a/internal/db/model/mention.go b/internal/db/gtsmodel/mention.go
similarity index 98%
rename from internal/db/model/mention.go
rename to internal/db/gtsmodel/mention.go
index 74dd0011c..6c1993740 100644
--- a/internal/db/model/mention.go
+++ b/internal/db/gtsmodel/mention.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/gtsmodel/poll.go b/internal/db/gtsmodel/poll.go
new file mode 100644
index 000000000..bc0fefaa7
--- /dev/null
+++ b/internal/db/gtsmodel/poll.go
@@ -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 .
+*/
+
+package gtsmodel
+
+
diff --git a/internal/db/model/status.go b/internal/db/gtsmodel/status.go
similarity index 97%
rename from internal/db/model/status.go
rename to internal/db/gtsmodel/status.go
index b0e9ab084..ea198ae75 100644
--- a/internal/db/model/status.go
+++ b/internal/db/gtsmodel/status.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-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
diff --git a/internal/db/model/tag.go b/internal/db/gtsmodel/tag.go
similarity index 98%
rename from internal/db/model/tag.go
rename to internal/db/gtsmodel/tag.go
index f57a063bc..acc0549de 100644
--- a/internal/db/model/tag.go
+++ b/internal/db/gtsmodel/tag.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import "time"
 
diff --git a/internal/db/model/user.go b/internal/db/gtsmodel/user.go
similarity index 99%
rename from internal/db/model/user.go
rename to internal/db/gtsmodel/user.go
index 61e9954d5..a72569945 100644
--- a/internal/db/model/user.go
+++ b/internal/db/gtsmodel/user.go
@@ -16,7 +16,7 @@
    along with this program.  If not, see .
 */
 
-package model
+package gtsmodel
 
 import (
 	"net"
diff --git a/internal/db/mock_DB.go b/internal/db/mock_DB.go
index 8d0932c1f..df2e41907 100644
--- a/internal/db/mock_DB.go
+++ b/internal/db/mock_DB.go
@@ -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)
 		}
 	}
 
diff --git a/internal/db/pg.go b/internal/db/pg.go
index 623fc5f04..3e25b8fcd 100644
--- a/internal/db/pg.go
+++ b/internal/db/pg.go
@@ -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 := >smodel.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(>smodel.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(>smodel.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(>smodel.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 := >smodel.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 := >smodel.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(>smodel.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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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, >smodel.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, >smodel.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 := >smodel.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
 			}
diff --git a/internal/distributor/distributor.go b/internal/distributor/distributor.go
index 1717da517..027b32279 100644
--- a/internal/distributor/distributor.go
+++ b/internal/distributor/distributor.go
@@ -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{}
 }
diff --git a/internal/gotosocial/actions.go b/internal/gotosocial/actions.go
index 03d90217e..1b3dbf69b 100644
--- a/internal/gotosocial/actions.go
+++ b/internal/gotosocial/actions.go
@@ -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
diff --git a/internal/mastotypes/converter.go b/internal/mastotypes/converter.go
new file mode 100644
index 000000000..b227f0c22
--- /dev/null
+++ b/internal/mastotypes/converter.go
@@ -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 .
+*/
+
+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 := >smodel.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 := >smodel.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 := >smodel.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 := >smodel.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
+}
diff --git a/pkg/mastotypes/README.md b/internal/mastotypes/mastomodel/README.md
similarity index 100%
rename from pkg/mastotypes/README.md
rename to internal/mastotypes/mastomodel/README.md
diff --git a/pkg/mastotypes/account.go b/internal/mastotypes/mastomodel/account.go
similarity index 100%
rename from pkg/mastotypes/account.go
rename to internal/mastotypes/mastomodel/account.go
diff --git a/pkg/mastotypes/activity.go b/internal/mastotypes/mastomodel/activity.go
similarity index 100%
rename from pkg/mastotypes/activity.go
rename to internal/mastotypes/mastomodel/activity.go
diff --git a/pkg/mastotypes/admin.go b/internal/mastotypes/mastomodel/admin.go
similarity index 100%
rename from pkg/mastotypes/admin.go
rename to internal/mastotypes/mastomodel/admin.go
diff --git a/pkg/mastotypes/announcement.go b/internal/mastotypes/mastomodel/announcement.go
similarity index 100%
rename from pkg/mastotypes/announcement.go
rename to internal/mastotypes/mastomodel/announcement.go
diff --git a/pkg/mastotypes/announcementreaction.go b/internal/mastotypes/mastomodel/announcementreaction.go
similarity index 100%
rename from pkg/mastotypes/announcementreaction.go
rename to internal/mastotypes/mastomodel/announcementreaction.go
diff --git a/pkg/mastotypes/application.go b/internal/mastotypes/mastomodel/application.go
similarity index 100%
rename from pkg/mastotypes/application.go
rename to internal/mastotypes/mastomodel/application.go
diff --git a/pkg/mastotypes/attachment.go b/internal/mastotypes/mastomodel/attachment.go
similarity index 96%
rename from pkg/mastotypes/attachment.go
rename to internal/mastotypes/mastomodel/attachment.go
index 4d4d0955a..bda79a8ee 100644
--- a/pkg/mastotypes/attachment.go
+++ b/internal/mastotypes/mastomodel/attachment.go
@@ -45,8 +45,10 @@ type Attachment struct {
 	URL string `json:"url"`
 	// The location of a scaled-down preview of the attachment.
 	PreviewURL string `json:"preview_url"`
-	// The location of the full-size original attachment on the remote website.
+	// The location of the full-size original attachment on the remote server.
 	RemoteURL string `json:"remote_url,omitempty"`
+	// The location of a scaled-down preview of the attachment on the remote server.
+	PreviewRemoteURL string `json:"preview_remote_url,omitempty"`
 	// A shorter URL for the attachment.
 	TextURL string `json:"text_url,omitempty"`
 	// Metadata returned by Paperclip.
diff --git a/pkg/mastotypes/card.go b/internal/mastotypes/mastomodel/card.go
similarity index 100%
rename from pkg/mastotypes/card.go
rename to internal/mastotypes/mastomodel/card.go
diff --git a/pkg/mastotypes/context.go b/internal/mastotypes/mastomodel/context.go
similarity index 100%
rename from pkg/mastotypes/context.go
rename to internal/mastotypes/mastomodel/context.go
diff --git a/pkg/mastotypes/conversation.go b/internal/mastotypes/mastomodel/conversation.go
similarity index 100%
rename from pkg/mastotypes/conversation.go
rename to internal/mastotypes/mastomodel/conversation.go
diff --git a/pkg/mastotypes/emoji.go b/internal/mastotypes/mastomodel/emoji.go
similarity index 100%
rename from pkg/mastotypes/emoji.go
rename to internal/mastotypes/mastomodel/emoji.go
diff --git a/pkg/mastotypes/error.go b/internal/mastotypes/mastomodel/error.go
similarity index 100%
rename from pkg/mastotypes/error.go
rename to internal/mastotypes/mastomodel/error.go
diff --git a/pkg/mastotypes/featuredtag.go b/internal/mastotypes/mastomodel/featuredtag.go
similarity index 100%
rename from pkg/mastotypes/featuredtag.go
rename to internal/mastotypes/mastomodel/featuredtag.go
diff --git a/pkg/mastotypes/field.go b/internal/mastotypes/mastomodel/field.go
similarity index 100%
rename from pkg/mastotypes/field.go
rename to internal/mastotypes/mastomodel/field.go
diff --git a/pkg/mastotypes/filter.go b/internal/mastotypes/mastomodel/filter.go
similarity index 100%
rename from pkg/mastotypes/filter.go
rename to internal/mastotypes/mastomodel/filter.go
diff --git a/pkg/mastotypes/history.go b/internal/mastotypes/mastomodel/history.go
similarity index 100%
rename from pkg/mastotypes/history.go
rename to internal/mastotypes/mastomodel/history.go
diff --git a/pkg/mastotypes/identityproof.go b/internal/mastotypes/mastomodel/identityproof.go
similarity index 100%
rename from pkg/mastotypes/identityproof.go
rename to internal/mastotypes/mastomodel/identityproof.go
diff --git a/pkg/mastotypes/instance.go b/internal/mastotypes/mastomodel/instance.go
similarity index 100%
rename from pkg/mastotypes/instance.go
rename to internal/mastotypes/mastomodel/instance.go
diff --git a/pkg/mastotypes/list.go b/internal/mastotypes/mastomodel/list.go
similarity index 100%
rename from pkg/mastotypes/list.go
rename to internal/mastotypes/mastomodel/list.go
diff --git a/pkg/mastotypes/marker.go b/internal/mastotypes/mastomodel/marker.go
similarity index 100%
rename from pkg/mastotypes/marker.go
rename to internal/mastotypes/mastomodel/marker.go
diff --git a/pkg/mastotypes/mention.go b/internal/mastotypes/mastomodel/mention.go
similarity index 100%
rename from pkg/mastotypes/mention.go
rename to internal/mastotypes/mastomodel/mention.go
diff --git a/pkg/mastotypes/notification.go b/internal/mastotypes/mastomodel/notification.go
similarity index 100%
rename from pkg/mastotypes/notification.go
rename to internal/mastotypes/mastomodel/notification.go
diff --git a/pkg/mastotypes/oauth.go b/internal/mastotypes/mastomodel/oauth.go
similarity index 100%
rename from pkg/mastotypes/oauth.go
rename to internal/mastotypes/mastomodel/oauth.go
diff --git a/pkg/mastotypes/poll.go b/internal/mastotypes/mastomodel/poll.go
similarity index 100%
rename from pkg/mastotypes/poll.go
rename to internal/mastotypes/mastomodel/poll.go
diff --git a/pkg/mastotypes/preferences.go b/internal/mastotypes/mastomodel/preferences.go
similarity index 100%
rename from pkg/mastotypes/preferences.go
rename to internal/mastotypes/mastomodel/preferences.go
diff --git a/pkg/mastotypes/pushsubscription.go b/internal/mastotypes/mastomodel/pushsubscription.go
similarity index 100%
rename from pkg/mastotypes/pushsubscription.go
rename to internal/mastotypes/mastomodel/pushsubscription.go
diff --git a/pkg/mastotypes/relationship.go b/internal/mastotypes/mastomodel/relationship.go
similarity index 100%
rename from pkg/mastotypes/relationship.go
rename to internal/mastotypes/mastomodel/relationship.go
diff --git a/pkg/mastotypes/results.go b/internal/mastotypes/mastomodel/results.go
similarity index 100%
rename from pkg/mastotypes/results.go
rename to internal/mastotypes/mastomodel/results.go
diff --git a/pkg/mastotypes/scheduledstatus.go b/internal/mastotypes/mastomodel/scheduledstatus.go
similarity index 100%
rename from pkg/mastotypes/scheduledstatus.go
rename to internal/mastotypes/mastomodel/scheduledstatus.go
diff --git a/pkg/mastotypes/source.go b/internal/mastotypes/mastomodel/source.go
similarity index 100%
rename from pkg/mastotypes/source.go
rename to internal/mastotypes/mastomodel/source.go
diff --git a/pkg/mastotypes/status.go b/internal/mastotypes/mastomodel/status.go
similarity index 100%
rename from pkg/mastotypes/status.go
rename to internal/mastotypes/mastomodel/status.go
diff --git a/pkg/mastotypes/tag.go b/internal/mastotypes/mastomodel/tag.go
similarity index 100%
rename from pkg/mastotypes/tag.go
rename to internal/mastotypes/mastomodel/tag.go
diff --git a/pkg/mastotypes/token.go b/internal/mastotypes/mastomodel/token.go
similarity index 100%
rename from pkg/mastotypes/token.go
rename to internal/mastotypes/mastomodel/token.go
diff --git a/internal/mastotypes/mock_Converter.go b/internal/mastotypes/mock_Converter.go
new file mode 100644
index 000000000..881bc48aa
--- /dev/null
+++ b/internal/mastotypes/mock_Converter.go
@@ -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
+}
diff --git a/internal/media/media.go b/internal/media/media.go
index d25fd258d..104342e6e 100644
--- a/internal/media/media.go
+++ b/internal/media/media.go
@@ -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 := >smodel.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,
diff --git a/internal/media/media_test.go b/internal/media/media_test.go
index ae5896c38..3ce9dce61 100644
--- a/internal/media/media_test.go
+++ b/internal/media/media_test.go
@@ -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{},
+		>smodel.Account{},
+		>smodel.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{},
+		>smodel.Account{},
+		>smodel.MediaAttachment{},
 	}
 	for _, m := range models {
 		if err := suite.db.DropTable(m); err != nil {
diff --git a/internal/media/mock_MediaHandler.go b/internal/media/mock_MediaHandler.go
index 0299d307e..6f04f1fe7 100644
--- a/internal/media/mock_MediaHandler.go
+++ b/internal/media/mock_MediaHandler.go
@@ -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)
 		}
 	}
 
diff --git a/internal/oauth/server.go b/internal/oauth/server.go
index 8bac8fc2f..538288922 100644
--- a/internal/oauth/server.go
+++ b/internal/oauth/server.go
@@ -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")
 		}
diff --git a/internal/util/parse.go b/internal/util/parse.go
index 92baac6bf..9f3f7fad5 100644
--- a/internal/util/parse.go
+++ b/internal/util/parse.go
@@ -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
diff --git a/internal/util/status.go b/internal/util/status.go
index a6dbb9583..bc091b3d8 100644
--- a/internal/util/status.go
+++ b/internal/util/status.go
@@ -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{}
diff --git a/internal/util/status_test.go b/internal/util/status_test.go
index e2079659b..72bd3e885 100644
--- a/internal/util/status_test.go
+++ b/internal/util/status_test.go
@@ -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() {
diff --git a/testrig/db.go b/testrig/db.go
index fc2401ec4..176e8dada 100644
--- a/testrig/db.go
+++ b/testrig/db.go
@@ -2,23 +2,23 @@ package testrig
 
 import (
 	"github.com/superseriousbusiness/gotosocial/internal/db"
-	"github.com/superseriousbusiness/gotosocial/internal/db/model"
+	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
 )
 
 var testModels []interface{} = []interface{}{
-	&model.Account{},
-	&model.Application{},
-	&model.Block{},
-	&model.DomainBlock{},
-	&model.EmailDomainBlock{},
-	&model.Follow{},
-	&model.FollowRequest{},
-	&model.MediaAttachment{},
-	&model.Mention{},
-	&model.Status{},
-	&model.Tag{},
-	&model.User{},
+	>smodel.Account{},
+	>smodel.Application{},
+	>smodel.Block{},
+	>smodel.DomainBlock{},
+	>smodel.EmailDomainBlock{},
+	>smodel.Follow{},
+	>smodel.FollowRequest{},
+	>smodel.MediaAttachment{},
+	>smodel.Mention{},
+	>smodel.Status{},
+	>smodel.Tag{},
+	>smodel.User{},
 	&oauth.Token{},
 	&oauth.Client{},
 }
@@ -61,6 +61,12 @@ func StandardDBSetup(db db.DB) error {
 		}
 	}
 
+	for _, v := range TestStatuses() {
+		if err := db.Put(v); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
diff --git a/testrig/models.go b/testrig/models.go
index bacb22f9c..f28ed74ed 100644
--- a/testrig/models.go
+++ b/testrig/models.go
@@ -6,20 +6,20 @@ import (
 	"net"
 	"time"
 
-	"github.com/superseriousbusiness/gotosocial/internal/db/model"
+	"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
 )
 
 func TestTokens() map[string]*oauth.Token {
 	tokens := map[string]*oauth.Token{
 		"local_account_1": {
-			ID: "64cf4214-33ab-4220-b5ca-4a6a12263b20",
-			ClientID: "73b48d42-029d-4487-80fc-329a5cf67869",
-			UserID: "44e36b79-44a4-4bd8-91e9-097f477fe97b",
-			RedirectURI: "http://localhost:8080",
-			Scope: "read write follow push",
-			Access: "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
-			AccessCreateAt: time.Now(),
+			ID:              "64cf4214-33ab-4220-b5ca-4a6a12263b20",
+			ClientID:        "73b48d42-029d-4487-80fc-329a5cf67869",
+			UserID:          "44e36b79-44a4-4bd8-91e9-097f477fe97b",
+			RedirectURI:     "http://localhost:8080",
+			Scope:           "read write follow push",
+			Access:          "NZAZOTC0OWITMDU0NC0ZODG4LWE4NJITMWUXM2M4MTRHZDEX",
+			AccessCreateAt:  time.Now(),
 			AccessExpiresAt: time.Now().Add(72 * time.Hour),
 		},
 	}
@@ -38,8 +38,8 @@ func TestClients() map[string]*oauth.Client {
 	return clients
 }
 
-func TestApplications() map[string]*model.Application {
-	apps := map[string]*model.Application{
+func TestApplications() map[string]*gtsmodel.Application {
+	apps := map[string]*gtsmodel.Application{
 		"application_1": {
 			ID:           "f88697b8-ee3d-46c2-ac3f-dbb85566c3cc",
 			Name:         "really cool gts application",
@@ -54,8 +54,8 @@ func TestApplications() map[string]*model.Application {
 	return apps
 }
 
-func TestUsers() map[string]*model.User {
-	users := map[string]*model.User{
+func TestUsers() map[string]*gtsmodel.User {
+	users := map[string]*gtsmodel.User{
 		"unconfirmed_account": {
 			ID:                     "0f7b1d24-1e49-4ee0-bc7e-fd87b7289eea",
 			Email:                  "",
@@ -181,8 +181,8 @@ func TestUsers() map[string]*model.User {
 	return users
 }
 
-func TestAccounts() map[string]*model.Account {
-	accounts := map[string]*model.Account{
+func TestAccounts() map[string]*gtsmodel.Account {
+	accounts := map[string]*gtsmodel.Account{
 		"unconfirmed_account": {
 			ID:                    "59e197f5-87cd-4be8-ac7c-09082ccc4b4d",
 			Username:              "weed_lord420",
@@ -197,7 +197,7 @@ func TestAccounts() map[string]*model.Account {
 			HeaderUpdatedAt:       time.Time{},
 			HeaderRemoteURL:       "",
 			DisplayName:           "",
-			Fields:                []model.Field{},
+			Fields:                []gtsmodel.Field{},
 			Note:                  "",
 			Memorial:              false,
 			MovedToAccountID:      "",
@@ -207,7 +207,7 @@ func TestAccounts() map[string]*model.Account {
 			Reason:                "hi, please let me in! I'm looking for somewhere neato bombeato to hang out.",
 			Locked:                false,
 			Discoverable:          false,
-			Privacy:               model.VisibilityPublic,
+			Privacy:               gtsmodel.VisibilityPublic,
 			Sensitive:             false,
 			Language:              "en",
 			URI:                   "http://localhost:8080/users/weed_lord420",
@@ -218,7 +218,7 @@ func TestAccounts() map[string]*model.Account {
 			SharedInboxURL:        "",
 			FollowersURL:          "http://localhost:8080/users/weed_lord420/followers",
 			FeaturedCollectionURL: "http://localhost:8080/users/weed_lord420/collections/featured",
-			ActorType:             model.ActivityStreamsPerson,
+			ActorType:             gtsmodel.ActivityStreamsPerson,
 			AlsoKnownAs:           "",
 			PrivateKey:            &rsa.PrivateKey{},
 			PublicKey:             &rsa.PublicKey{},
@@ -242,7 +242,7 @@ func TestAccounts() map[string]*model.Account {
 			HeaderUpdatedAt:       time.Time{},
 			HeaderRemoteURL:       "",
 			DisplayName:           "",
-			Fields:                []model.Field{},
+			Fields:                []gtsmodel.Field{},
 			Note:                  "",
 			Memorial:              false,
 			MovedToAccountID:      "",
@@ -252,7 +252,7 @@ func TestAccounts() map[string]*model.Account {
 			Reason:                "",
 			Locked:                false,
 			Discoverable:          true,
-			Privacy:               model.VisibilityPublic,
+			Privacy:               gtsmodel.VisibilityPublic,
 			Sensitive:             false,
 			Language:              "en",
 			URI:                   "http://localhost:8080/users/admin",
@@ -263,7 +263,7 @@ func TestAccounts() map[string]*model.Account {
 			SharedInboxURL:        "",
 			FollowersURL:          "http://localhost:8080/users/admin/followers",
 			FeaturedCollectionURL: "http://localhost:8080/users/admin/collections/featured",
-			ActorType:             model.ActivityStreamsPerson,
+			ActorType:             gtsmodel.ActivityStreamsPerson,
 			AlsoKnownAs:           "",
 			PrivateKey:            &rsa.PrivateKey{},
 			PublicKey:             &rsa.PublicKey{},
@@ -287,7 +287,7 @@ func TestAccounts() map[string]*model.Account {
 			HeaderUpdatedAt:       time.Time{},
 			HeaderRemoteURL:       "",
 			DisplayName:           "original zork (he/they)",
-			Fields:                []model.Field{},
+			Fields:                []gtsmodel.Field{},
 			Note:                  "hey yo this is my profile!",
 			Memorial:              false,
 			MovedToAccountID:      "",
@@ -297,7 +297,7 @@ func TestAccounts() map[string]*model.Account {
 			Reason:                "I wanna be on this damned webbed site so bad! Please! Wow",
 			Locked:                false,
 			Discoverable:          true,
-			Privacy:               model.VisibilityPublic,
+			Privacy:               gtsmodel.VisibilityPublic,
 			Sensitive:             false,
 			Language:              "en",
 			URI:                   "http://localhost:8080/users/the_mighty_zork",
@@ -308,7 +308,7 @@ func TestAccounts() map[string]*model.Account {
 			SharedInboxURL:        "",
 			FollowersURL:          "http://localhost:8080/users/the_mighty_zork/followers",
 			FeaturedCollectionURL: "http://localhost:8080/users/the_mighty_zork/collections/featured",
-			ActorType:             model.ActivityStreamsPerson,
+			ActorType:             gtsmodel.ActivityStreamsPerson,
 			AlsoKnownAs:           "",
 			PrivateKey:            &rsa.PrivateKey{},
 			PublicKey:             &rsa.PublicKey{},
@@ -332,7 +332,7 @@ func TestAccounts() map[string]*model.Account {
 			HeaderUpdatedAt:       time.Time{},
 			HeaderRemoteURL:       "",
 			DisplayName:           "happy little turtle :3",
-			Fields:                []model.Field{},
+			Fields:                []gtsmodel.Field{},
 			Note:                  "i post about things that concern me",
 			Memorial:              false,
 			MovedToAccountID:      "",
@@ -342,7 +342,7 @@ func TestAccounts() map[string]*model.Account {
 			Reason:                "",
 			Locked:                true,
 			Discoverable:          false,
-			Privacy:               model.VisibilityFollowersOnly,
+			Privacy:               gtsmodel.VisibilityFollowersOnly,
 			Sensitive:             false,
 			Language:              "en",
 			URI:                   "http://localhost:8080/users/1happyturtle",
@@ -353,7 +353,7 @@ func TestAccounts() map[string]*model.Account {
 			SharedInboxURL:        "",
 			FollowersURL:          "http://localhost:8080/users/1happyturtle/followers",
 			FeaturedCollectionURL: "http://localhost:8080/users/1happyturtle/collections/featured",
-			ActorType:             model.ActivityStreamsPerson,
+			ActorType:             gtsmodel.ActivityStreamsPerson,
 			AlsoKnownAs:           "",
 			PrivateKey:            &rsa.PrivateKey{},
 			PublicKey:             &rsa.PublicKey{},
@@ -378,7 +378,7 @@ func TestAccounts() map[string]*model.Account {
 			// HeaderUpdatedAt:       time.Time{},
 			// HeaderRemoteURL:       "",
 			DisplayName:           "big gerald",
-			Fields:                []model.Field{},
+			Fields:                []gtsmodel.Field{},
 			Note:                  "i post about like, i dunno, stuff, or whatever!!!!",
 			Memorial:              false,
 			MovedToAccountID:      "",
@@ -397,7 +397,7 @@ func TestAccounts() map[string]*model.Account {
 			SharedInboxURL:        "",
 			FollowersURL:          "https://fossbros-anonymous.io/users/foss_satan/followers",
 			FeaturedCollectionURL: "https://fossbros-anonymous.io/users/foss_satan/collections/featured",
-			ActorType:             model.ActivityStreamsPerson,
+			ActorType:             gtsmodel.ActivityStreamsPerson,
 			AlsoKnownAs:           "",
 			PrivateKey:            &rsa.PrivateKey{},
 			PublicKey:             nil,
@@ -440,53 +440,168 @@ func TestAccounts() map[string]*model.Account {
 	return accounts
 }
 
-func TestStatuses() map[string]*model.Status {
-	return map[string]*model.Status{
-		"local_account_1_status_1": {
-			ID: "91b1e795-74ff-4672-a4c4-476616710e2d",
-			URI: "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
-			URL: "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
-			Content: "hello everyone!",
-			CreatedAt:             time.Now().Add(-47 * time.Hour),
-			UpdatedAt:             time.Now().Add(-47 * time.Hour),
-			Local: true,
-			AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
-			InReplyToID: "",
-			BoostOfID: "",
-			ContentWarning: "introduction post",
-			Visibility: model.VisibilityPublic,
-			Sensitive: true,
-			Language: "en",
-			VisibilityAdvanced: &model.VisibilityAdvanced{
+func TestStatuses() map[string]*gtsmodel.Status {
+	return map[string]*gtsmodel.Status{
+		"admin_account_status_1": {
+			ID:             "502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+			URI:            "http://localhost:8080/users/admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+			URL:            "http://localhost:8080/@admin/statuses/502ccd6f-0edf-48d7-9016-2dfa4d3714cd",
+			Content:        "hello world! first post on the instance!",
+			CreatedAt:      time.Now().Add(-71 * time.Hour),
+			UpdatedAt:      time.Now().Add(-71 * time.Hour),
+			Local:          true,
+			AccountID:      "0fb02eae-2214-473f-9667-0a43f22d75ff",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "",
+			Visibility:     gtsmodel.VisibilityPublic,
+			Sensitive:      false,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
 				Federated: true,
 				Boostable: true,
 				Replyable: true,
-				Likeable: true,
+				Likeable:  true,
 			},
-			ActivityStreamsType: model.ActivityStreamsNote,
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+		},
+		"admin_account_status_2": {
+			ID:             "0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+			URI:            "http://localhost:8080/users/admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+			URL:            "http://localhost:8080/@admin/statuses/0fb3f1ac-5cd8-48ac-9050-3d95dc7e44e9",
+			Content:        "🐕🐕🐕🐕🐕",
+			CreatedAt:      time.Now().Add(-70 * time.Hour),
+			UpdatedAt:      time.Now().Add(-70 * time.Hour),
+			Local:          true,
+			AccountID:      "0fb02eae-2214-473f-9667-0a43f22d75ff",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "open to see some puppies",
+			Visibility:     gtsmodel.VisibilityPublic,
+			Sensitive:      true,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
+				Federated: true,
+				Boostable: true,
+				Replyable: true,
+				Likeable:  true,
+			},
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+		},
+		"local_account_1_status_1": {
+			ID:             "91b1e795-74ff-4672-a4c4-476616710e2d",
+			URI:            "http://localhost:8080/users/the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
+			URL:            "http://localhost:8080/@the_mighty_zork/statuses/91b1e795-74ff-4672-a4c4-476616710e2d",
+			Content:        "hello everyone!",
+			CreatedAt:      time.Now().Add(-47 * time.Hour),
+			UpdatedAt:      time.Now().Add(-47 * time.Hour),
+			Local:          true,
+			AccountID:      "580072df-4d03-4684-a412-89fd6f7d77e6",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "introduction post",
+			Visibility:     gtsmodel.VisibilityPublic,
+			Sensitive:      true,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
+				Federated: true,
+				Boostable: true,
+				Replyable: true,
+				Likeable:  true,
+			},
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
 		},
 		"local_account_1_status_2": {
-			ID: "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
-			URI: "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
-			URL: "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
-			Content: "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
-			CreatedAt:             time.Now().Add(-47 * time.Hour),
-			UpdatedAt:             time.Now().Add(-47 * time.Hour),
-			Local: true,
-			AccountID: "580072df-4d03-4684-a412-89fd6f7d77e6",
-			InReplyToID: "",
-			BoostOfID: "",
+			ID:             "3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+			URI:            "http://localhost:8080/users/the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+			URL:            "http://localhost:8080/@the_mighty_zork/statuses/3dd328d9-8bb1-48f5-bc96-5ccc1c696b4c",
+			Content:        "this is an unlocked local-only post that shouldn't federate, but it's still boostable, replyable, and likeable",
+			CreatedAt:      time.Now().Add(-46 * time.Hour),
+			UpdatedAt:      time.Now().Add(-46 * time.Hour),
+			Local:          true,
+			AccountID:      "580072df-4d03-4684-a412-89fd6f7d77e6",
+			InReplyToID:    "",
+			BoostOfID:      "",
 			ContentWarning: "",
-			Visibility: model.VisibilityUnlocked,
-			Sensitive: false,
-			Language: "en",
-			VisibilityAdvanced: &model.VisibilityAdvanced{
+			Visibility:     gtsmodel.VisibilityUnlocked,
+			Sensitive:      false,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
 				Federated: false,
 				Boostable: true,
 				Replyable: true,
-				Likeable: true,
+				Likeable:  true,
 			},
-			ActivityStreamsType: model.ActivityStreamsNote,
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+		},
+		"local_account_1_status_3": {
+			ID:             "5e41963f-8ab9-4147-9f00-52d56e19da65",
+			URI:            "http://localhost:8080/users/the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
+			URL:            "http://localhost:8080/@the_mighty_zork/statuses/5e41963f-8ab9-4147-9f00-52d56e19da65",
+			Content:        "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it",
+			CreatedAt:      time.Now().Add(-45 * time.Hour),
+			UpdatedAt:      time.Now().Add(-45 * time.Hour),
+			Local:          true,
+			AccountID:      "580072df-4d03-4684-a412-89fd6f7d77e6",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "test: you shouldn't be able to interact with this post in any way",
+			Visibility:     gtsmodel.VisibilityMutualsOnly,
+			Sensitive:      false,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
+				Federated: true,
+				Boostable: false,
+				Replyable: false,
+				Likeable:  false,
+			},
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+		},
+		"local_account_2_status_1": {
+			ID:             "8945ccf2-3873-45e9-aa13-fd7163f19775",
+			URI:            "http://localhost:8080/users/1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
+			URL:            "http://localhost:8080/@1happyturtle/statuses/8945ccf2-3873-45e9-aa13-fd7163f19775",
+			Content:        "🐢 hi everyone i post about turtles 🐢",
+			CreatedAt:      time.Now().Add(-189 * time.Hour),
+			UpdatedAt:      time.Now().Add(-189 * time.Hour),
+			Local:          true,
+			AccountID:      "eecaad73-5703-426d-9312-276641daa31e",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "introduction post",
+			Visibility:     gtsmodel.VisibilityPublic,
+			Sensitive:      true,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
+				Federated: true,
+				Boostable: true,
+				Replyable: true,
+				Likeable:  true,
+			},
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
+		},
+		"local_account_2_status_2": {
+			ID:             "c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+			URI:            "http://localhost:8080/users/1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+			URL:            "http://localhost:8080/@1happyturtle/statuses/c7e25a86-f0d3-4705-a73c-c597f687d3dd",
+			Content:        "🐢 this one is federated, likeable, and boostable but not replyable 🐢",
+			CreatedAt:      time.Now().Add(-1 * time.Minute),
+			UpdatedAt:      time.Now().Add(-1 * time.Minute),
+			Local:          true,
+			AccountID:      "eecaad73-5703-426d-9312-276641daa31e",
+			InReplyToID:    "",
+			BoostOfID:      "",
+			ContentWarning: "",
+			Visibility:     gtsmodel.VisibilityPublic,
+			Sensitive:      true,
+			Language:       "en",
+			VisibilityAdvanced: >smodel.VisibilityAdvanced{
+				Federated: true,
+				Boostable: true,
+				Replyable: false,
+				Likeable:  true,
+			},
+			ActivityStreamsType: gtsmodel.ActivityStreamsNote,
 		},
 	}
 }