mirror of
1
Fork 0

linting + organizing

This commit is contained in:
tsmethurst 2021-04-20 18:14:23 +02:00
parent 32c5fd987a
commit dafc3b5b92
60 changed files with 746 additions and 390 deletions

View File

@ -37,25 +37,31 @@ import (
)
const (
idKey = "id"
basePath = "/api/v1/accounts"
basePathWithID = basePath + "/:" + idKey
verifyPath = basePath + "/verify_credentials"
updateCredentialsPath = basePath + "/update_credentials"
// IDKey is the key to use for retrieving account ID in requests
IDKey = "id"
// BasePath is the base API path for this module
BasePath = "/api/v1/accounts"
// BasePathWithID is the base path for this module with the ID key
BasePathWithID = BasePath + "/:" + IDKey
// VerifyPath is for verifying account credentials
VerifyPath = BasePath + "/verify_credentials"
// UpdateCredentialsPath is for updating account credentials
UpdateCredentialsPath = BasePath + "/update_credentials"
)
type accountModule struct {
// Module implements the ClientAPIModule interface for account-related actions
type Module struct {
config *config.Config
db db.DB
oauthServer oauth.Server
mediaHandler media.MediaHandler
mediaHandler media.Handler
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, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &accountModule{
func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler media.Handler, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &Module{
config: config,
db: db,
oauthServer: oauthServer,
@ -66,14 +72,15 @@ func New(config *config.Config, db db.DB, oauthServer oauth.Server, mediaHandler
}
// Route attaches all routes from this module to the given router
func (m *accountModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, basePath, m.accountCreatePOSTHandler)
r.AttachHandler(http.MethodGet, basePathWithID, m.muxHandler)
r.AttachHandler(http.MethodPatch, basePathWithID, m.muxHandler)
func (m *Module) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, BasePath, m.AccountCreatePOSTHandler)
r.AttachHandler(http.MethodGet, BasePathWithID, m.muxHandler)
r.AttachHandler(http.MethodPatch, BasePathWithID, m.muxHandler)
return nil
}
func (m *accountModule) CreateTables(db db.DB) error {
// CreateTables creates the required tables for this module in the given database
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.User{},
&gtsmodel.Account{},
@ -93,18 +100,18 @@ func (m *accountModule) CreateTables(db db.DB) error {
return nil
}
func (m *accountModule) muxHandler(c *gin.Context) {
func (m *Module) muxHandler(c *gin.Context) {
ru := c.Request.RequestURI
switch c.Request.Method {
case http.MethodGet:
if strings.HasPrefix(ru, verifyPath) {
m.accountVerifyGETHandler(c)
if strings.HasPrefix(ru, VerifyPath) {
m.AccountVerifyGETHandler(c)
} else {
m.accountGETHandler(c)
m.AccountGETHandler(c)
}
case http.MethodPatch:
if strings.HasPrefix(ru, updateCredentialsPath) {
m.accountUpdateCredentialsPATCHHandler(c)
if strings.HasPrefix(ru, UpdateCredentialsPath) {
m.AccountUpdateCredentialsPATCHHandler(c)
}
}
}

View File

@ -34,10 +34,10 @@ import (
"github.com/superseriousbusiness/oauth2/v4"
)
// accountCreatePOSTHandler handles create account requests, validates them,
// AccountCreatePOSTHandler handles create account requests, validates them,
// and puts them in the database if they're valid.
// It should be served as a POST at /api/v1/accounts
func (m *accountModule) accountCreatePOSTHandler(c *gin.Context) {
func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "accountCreatePOSTHandler")
authed, err := oauth.MustAuth(c, true, true, false, false)
if err != nil {
@ -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 *gtsmodel.Application) (*mastotypes.Token, error) {
func (m *Module) accountCreate(form *mastotypes.AccountCreateRequest, signUpIP net.IP, token oauth2.TokenInfo, app *gtsmodel.Application) (*mastotypes.Token, error) {
l := m.log.WithField("func", "accountCreate")
// don't store a reason if we don't require one

View File

@ -26,12 +26,12 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
)
// accountGetHandler serves the account information held by the server in response to a GET
// AccountGETHandler serves the account information held by the server in response to a GET
// request. It should be served as a GET at /api/v1/accounts/:id.
//
// See: https://docs.joinmastodon.org/methods/accounts/
func (m *accountModule) accountGETHandler(c *gin.Context) {
targetAcctID := c.Param(idKey)
func (m *Module) AccountGETHandler(c *gin.Context) {
targetAcctID := c.Param(IDKey)
if targetAcctID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
return

View File

@ -34,7 +34,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
// accountUpdateCredentialsPATCHHandler allows a user to modify their account/profile settings.
// AccountUpdateCredentialsPATCHHandler allows a user to modify their account/profile settings.
// It should be served as a PATCH at /api/v1/accounts/update_credentials
//
// TODO: this can be optimized massively by building up a picture of what we want the new account
@ -42,7 +42,7 @@ import (
// which is not gonna make the database very happy when lots of requests are going through.
// This way it would also be safer because the update won't happen until *all* the fields are validated.
// Otherwise we risk doing a partial update and that's gonna cause probllleeemmmsss.
func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
l := m.log.WithField("func", "accountUpdateCredentialsPATCHHandler")
authed, err := oauth.MustAuth(c, true, false, false, true)
if err != nil {
@ -196,7 +196,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) (*gtsmodel.MediaAttachment, error) {
func (m *Module) 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)
@ -229,7 +229,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) (*gtsmodel.MediaAttachment, error) {
func (m *Module) UpdateAccountHeader(header *multipart.FileHeader, accountID string) (*gtsmodel.MediaAttachment, error) {
var err error
if int(header.Size) > m.config.MediaConfig.MaxImageSize {
err = fmt.Errorf("header with size %d exceeded max image size of %d bytes", header.Size, m.config.MediaConfig.MaxImageSize)

View File

@ -25,10 +25,10 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// accountVerifyGETHandler serves a user's account details to them IF they reached this
// AccountVerifyGETHandler serves a user's account details to them IF they reached this
// handler while in possession of a valid token, according to the oauth middleware.
// It should be served as a GET at /api/v1/accounts/verify_credentials
func (m *accountModule) accountVerifyGETHandler(c *gin.Context) {
func (m *Module) AccountVerifyGETHandler(c *gin.Context) {
l := m.log.WithField("func", "accountVerifyGETHandler")
authed, err := oauth.MustAuth(c, true, false, false, true)
if err != nil {

View File

@ -39,6 +39,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
@ -63,10 +64,10 @@ type AccountCreateTestSuite struct {
testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mediaHandler media.Handler
mastoConverter mastotypes.Converter
db db.DB
accountModule *accountModule
accountModule *account.Module
newUserFormHappyPath url.Values
}
@ -164,7 +165,7 @@ func (suite *AccountCreateTestSuite) SetupSuite() {
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.mastoConverter, suite.log).(*accountModule)
suite.accountModule = account.New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.mastoConverter, suite.log).(*account.Module)
}
func (suite *AccountCreateTestSuite) TearDownSuite() {
@ -250,9 +251,9 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerSuccessful() {
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
@ -324,9 +325,9 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerNoAuth() {
// setup
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
@ -349,8 +350,8 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerNoForm() {
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
suite.accountModule.accountCreatePOSTHandler(ctx)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -371,11 +372,11 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerWeakPassword()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
// set a weak password
ctx.Request.Form.Set("password", "weak")
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -396,11 +397,11 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerWeirdLocale() {
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
// set an invalid locale
ctx.Request.Form.Set("locale", "neverneverland")
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -421,12 +422,12 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerRegistrationsCl
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
// close registrations
suite.config.AccountsConfig.OpenRegistration = false
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -447,13 +448,13 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerReasonNotProvid
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
// remove reason
ctx.Request.Form.Set("reason", "")
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -474,13 +475,13 @@ func (suite *AccountCreateTestSuite) TestAccountCreatePOSTHandlerInsufficientRea
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), nil) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", account.BasePath), nil) // the endpoint we're hitting
ctx.Request.Form = suite.newUserFormHappyPath
// remove reason
ctx.Request.Form.Set("reason", "just cuz")
suite.accountModule.accountCreatePOSTHandler(ctx)
suite.accountModule.AccountCreatePOSTHandler(ctx)
// check response
suite.EqualValues(http.StatusBadRequest, recorder.Code)
@ -526,9 +527,9 @@ func (suite *AccountCreateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccountLocal)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", updateCredentialsPath), body) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", account.UpdateCredentialsPath), body) // the endpoint we're hitting
ctx.Request.Header.Set("Content-Type", writer.FormDataContentType())
suite.accountModule.accountUpdateCredentialsPATCHHandler(ctx)
suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx)
// check response

View File

@ -37,6 +37,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel"
@ -58,10 +59,10 @@ type AccountUpdateTestSuite struct {
testToken oauth2.TokenInfo
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mediaHandler media.Handler
mastoConverter mastotypes.Converter
db db.DB
accountModule *accountModule
accountModule *account.Module
newUserFormHappyPath url.Values
}
@ -159,7 +160,7 @@ func (suite *AccountUpdateTestSuite) SetupSuite() {
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.mastoConverter, suite.log).(*accountModule)
suite.accountModule = account.New(suite.config, suite.db, suite.mockOauthServer, suite.mediaHandler, suite.mastoConverter, suite.log).(*account.Module)
}
func (suite *AccountUpdateTestSuite) TearDownSuite() {
@ -278,9 +279,9 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccountLocal)
ctx.Set(oauth.SessionAuthorizedToken, suite.testToken)
ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", updateCredentialsPath), body) // the endpoint we're hitting
ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", account.UpdateCredentialsPath), body) // the endpoint we're hitting
ctx.Request.Header.Set("Content-Type", writer.FormDataContentType())
suite.accountModule.accountUpdateCredentialsPATCHHandler(ctx)
suite.accountModule.AccountUpdateCredentialsPATCHHandler(ctx)
// check response

View File

@ -33,21 +33,24 @@ import (
)
const (
basePath = "/api/v1/admin"
emojiPath = basePath + "/custom_emojis"
// BasePath is the base API path for this module
BasePath = "/api/v1/admin"
// EmojiPath is used for posting/deleting custom emojis
EmojiPath = BasePath + "/custom_emojis"
)
type adminModule struct {
// Module implements the ClientAPIModule interface for admin-related actions (reports, emojis, etc)
type Module struct {
config *config.Config
db db.DB
mediaHandler media.MediaHandler
mediaHandler media.Handler
mastoConverter mastotypes.Converter
log *logrus.Logger
}
// New returns a new account module
func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &adminModule{
// New returns a new admin module
func New(config *config.Config, db db.DB, mediaHandler media.Handler, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &Module{
config: config,
db: db,
mediaHandler: mediaHandler,
@ -57,12 +60,13 @@ func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, masto
}
// Route attaches all routes from this module to the given router
func (m *adminModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, emojiPath, m.emojiCreatePOSTHandler)
func (m *Module) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, EmojiPath, m.emojiCreatePOSTHandler)
return nil
}
func (m *adminModule) CreateTables(db db.DB) error {
// CreateTables creates the necessary tables for this module in the given database
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.User{},
&gtsmodel.Account{},

View File

@ -33,7 +33,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (m *adminModule) emojiCreatePOSTHandler(c *gin.Context) {
func (m *Module) emojiCreatePOSTHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "emojiCreatePOSTHandler",
"request_uri": c.Request.RequestURI,

View File

@ -31,9 +31,11 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/router"
)
const appsPath = "/api/v1/apps"
// BasePath is the base path for this api module
const BasePath = "/api/v1/apps"
type appModule struct {
// Module implements the ClientAPIModule interface for requests relating to registering/removing applications
type Module struct {
server oauth.Server
db db.DB
mastoConverter mastotypes.Converter
@ -42,7 +44,7 @@ type appModule struct {
// New returns a new auth module
func New(srv oauth.Server, db db.DB, mastoConverter mastotypes.Converter, log *logrus.Logger) apimodule.ClientAPIModule {
return &appModule{
return &Module{
server: srv,
db: db,
mastoConverter: mastoConverter,
@ -51,12 +53,13 @@ func New(srv oauth.Server, db db.DB, mastoConverter mastotypes.Converter, log *l
}
// Route satisfies the RESTAPIModule interface
func (m *appModule) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, appsPath, m.appsPOSTHandler)
func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, BasePath, m.AppsPOSTHandler)
return nil
}
func (m *appModule) CreateTables(db db.DB) error {
// CreateTables creates the necessary tables for this module in the given database
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&oauth.Client{},
&oauth.Token{},

View File

@ -29,9 +29,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// appsPOSTHandler should be served at https://example.org/api/v1/apps
// AppsPOSTHandler should be served at https://example.org/api/v1/apps
// It is equivalent to: https://docs.joinmastodon.org/methods/apps/
func (m *appModule) appsPOSTHandler(c *gin.Context) {
func (m *Module) AppsPOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "AppsPOSTHandler")
l.Trace("entering AppsPOSTHandler")

View File

@ -1,5 +0,0 @@
# auth
This package provides uses the [GoToSocial oauth2](https://github.com/gotosocial/oauth2) module (forked from [go-oauth2](https://github.com/go-oauth2/oauth2)) to provide [oauth2](https://www.oauth.com/) functionality to the GoToSocial client API.
It also provides a handler/middleware for attaching to the Gin engine for validating authenticated users.

View File

@ -16,12 +16,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Package auth is a module that provides oauth functionality to a router.
// It adds the following paths:
// /auth/sign_in
// /oauth/token
// /oauth/authorize
// It also includes the oauthTokenMiddleware, which can be attached to a router to authenticate every request by Bearer token.
package auth
import (
@ -37,12 +31,16 @@ import (
)
const (
authSignInPath = "/auth/sign_in"
oauthTokenPath = "/oauth/token"
oauthAuthorizePath = "/oauth/authorize"
// AuthSignInPath is the API path for users to sign in through
AuthSignInPath = "/auth/sign_in"
// OauthTokenPath is the API path to use for granting token requests to users with valid credentials
OauthTokenPath = "/oauth/token"
// OauthAuthorizePath is the API path for authorization requests (eg., authorize this app to act on my behalf as a user)
OauthAuthorizePath = "/oauth/authorize"
)
type authModule struct {
// Module implements the ClientAPIModule interface for
type Module struct {
server oauth.Server
db db.DB
log *logrus.Logger
@ -50,7 +48,7 @@ type authModule struct {
// New returns a new auth module
func New(srv oauth.Server, db db.DB, log *logrus.Logger) apimodule.ClientAPIModule {
return &authModule{
return &Module{
server: srv,
db: db,
log: log,
@ -58,20 +56,21 @@ func New(srv oauth.Server, db db.DB, log *logrus.Logger) apimodule.ClientAPIModu
}
// Route satisfies the RESTAPIModule interface
func (m *authModule) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, authSignInPath, m.signInGETHandler)
s.AttachHandler(http.MethodPost, authSignInPath, m.signInPOSTHandler)
func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, AuthSignInPath, m.SignInGETHandler)
s.AttachHandler(http.MethodPost, AuthSignInPath, m.SignInPOSTHandler)
s.AttachHandler(http.MethodPost, oauthTokenPath, m.tokenPOSTHandler)
s.AttachHandler(http.MethodPost, OauthTokenPath, m.TokenPOSTHandler)
s.AttachHandler(http.MethodGet, oauthAuthorizePath, m.authorizeGETHandler)
s.AttachHandler(http.MethodPost, oauthAuthorizePath, m.authorizePOSTHandler)
s.AttachHandler(http.MethodGet, OauthAuthorizePath, m.AuthorizeGETHandler)
s.AttachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler)
s.AttachMiddleware(m.oauthTokenMiddleware)
s.AttachMiddleware(m.OauthTokenMiddleware)
return nil
}
func (m *authModule) CreateTables(db db.DB) error {
// CreateTables creates the necessary tables for this module in the given database
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&oauth.Client{},
&oauth.Token{},

View File

@ -31,10 +31,10 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel"
)
// authorizeGETHandler should be served as GET at https://example.org/oauth/authorize
// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize
// The idea here is to present an oauth authorize page to the user, with a button
// that they have to click to accept. See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user
func (m *authModule) authorizeGETHandler(c *gin.Context) {
func (m *Module) AuthorizeGETHandler(c *gin.Context) {
l := m.log.WithField("func", "AuthorizeGETHandler")
s := sessions.Default(c)
@ -46,7 +46,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
if err := parseAuthForm(c, l); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
} else {
c.Redirect(http.StatusFound, authSignInPath)
c.Redirect(http.StatusFound, AuthSignInPath)
}
return
}
@ -108,11 +108,11 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) {
})
}
// authorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize
// At this point we assume that the user has A) logged in and B) accepted that the app should act for them,
// so we should proceed with the authentication flow and generate an oauth token for them if we can.
// See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user
func (m *authModule) authorizePOSTHandler(c *gin.Context) {
func (m *Module) AuthorizePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "AuthorizePOSTHandler")
s := sessions.Default(c)

View File

@ -24,12 +24,12 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
// oauthTokenMiddleware checks if the client has presented a valid oauth Bearer token.
// OauthTokenMiddleware checks if the client has presented a valid oauth Bearer token.
// If so, it will check the User that the token belongs to, and set that in the context of
// the request. Then, it will look up the account for that user, and set that in the request too.
// If user or account can't be found, then the handler won't *fail*, in case the server wants to allow
// public requests that don't have a Bearer token set (eg., for public instance information and so on).
func (m *authModule) oauthTokenMiddleware(c *gin.Context) {
func (m *Module) OauthTokenMiddleware(c *gin.Context) {
l := m.log.WithField("func", "ValidatePassword")
l.Trace("entering OauthTokenMiddleware")

View File

@ -28,23 +28,24 @@ import (
"golang.org/x/crypto/bcrypt"
)
// login just wraps a form-submitted username (we want an email) and password
type login struct {
Email string `form:"username"`
Password string `form:"password"`
}
// signInGETHandler should be served at https://example.org/auth/sign_in.
// SignInGETHandler should be served at https://example.org/auth/sign_in.
// The idea is to present a sign in page to the user, where they can enter their username and password.
// The form will then POST to the sign in page, which will be handled by SignInPOSTHandler
func (m *authModule) signInGETHandler(c *gin.Context) {
func (m *Module) SignInGETHandler(c *gin.Context) {
m.log.WithField("func", "SignInGETHandler").Trace("serving sign in html")
c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{})
}
// signInPOSTHandler should be served at https://example.org/auth/sign_in.
// SignInPOSTHandler should be served at https://example.org/auth/sign_in.
// The idea is to present a sign in page to the user, where they can enter their username and password.
// The handler will then redirect to the auth handler served at /auth
func (m *authModule) signInPOSTHandler(c *gin.Context) {
func (m *Module) SignInPOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "SignInPOSTHandler")
s := sessions.Default(c)
form := &login{}
@ -54,7 +55,7 @@ func (m *authModule) signInPOSTHandler(c *gin.Context) {
}
l.Tracef("parsed form: %+v", form)
userid, err := m.validatePassword(form.Email, form.Password)
userid, err := m.ValidatePassword(form.Email, form.Password)
if err != nil {
c.String(http.StatusForbidden, err.Error())
return
@ -67,14 +68,14 @@ func (m *authModule) signInPOSTHandler(c *gin.Context) {
}
l.Trace("redirecting to auth page")
c.Redirect(http.StatusFound, oauthAuthorizePath)
c.Redirect(http.StatusFound, OauthAuthorizePath)
}
// validatePassword takes an email address and a password.
// ValidatePassword takes an email address and a password.
// The goal is to authenticate the password against the one for that email
// address stored in the database. If OK, we return the userid (a uuid) for that user,
// so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db.
func (m *authModule) validatePassword(email string, password string) (userid string, err error) {
func (m *Module) ValidatePassword(email string, password string) (userid string, err error) {
l := m.log.WithField("func", "ValidatePassword")
// make sure an email/password was provided and bail if not

View File

@ -24,10 +24,10 @@ import (
"github.com/gin-gonic/gin"
)
// tokenPOSTHandler should be served as a POST at https://example.org/oauth/token
// TokenPOSTHandler should be served as a POST at https://example.org/oauth/token
// The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs.
// See https://docs.joinmastodon.org/methods/apps/oauth/#obtain-a-token
func (m *authModule) tokenPOSTHandler(c *gin.Context) {
func (m *Module) TokenPOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "TokenPOSTHandler")
l.Trace("entered TokenPOSTHandler")
if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil {

View File

@ -32,12 +32,14 @@ import (
)
const (
// AccountIDKey is the url key for account id (an account uuid)
AccountIDKey = "account_id"
// MediaTypeKey is the url key for media type (usually something like attachment or header etc)
MediaTypeKey = "media_type"
// MediaSizeKey is the url key for the desired media size--original/small/static
MediaSizeKey = "media_size"
// FileNameKey is the actual filename being sought. Will usually be a UUID then something like .jpeg
FileNameKey = "file_name"
FilesPath = "files"
)
// FileServer implements the RESTAPIModule interface.
@ -67,6 +69,7 @@ func (m *FileServer) Route(s router.Router) error {
return nil
}
// CreateTables populates necessary tables in the given DB
func (m *FileServer) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},

View File

@ -49,7 +49,7 @@ type ServeFileTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
// standard suite models

View File

@ -32,10 +32,12 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/router"
)
// BasePath is the base API path for making media requests
const BasePath = "/api/v1/media"
type MediaModule struct {
mediaHandler media.MediaHandler
// Module implements the ClientAPIModule interface for media
type Module struct {
mediaHandler media.Handler
config *config.Config
db db.DB
mastoConverter mastotypes.Converter
@ -43,8 +45,8 @@ type MediaModule struct {
}
// New returns a new auth module
func New(db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, config *config.Config, log *logrus.Logger) apimodule.ClientAPIModule {
return &MediaModule{
func New(db db.DB, mediaHandler media.Handler, mastoConverter mastotypes.Converter, config *config.Config, log *logrus.Logger) apimodule.ClientAPIModule {
return &Module{
mediaHandler: mediaHandler,
config: config,
db: db,
@ -54,12 +56,13 @@ func New(db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Co
}
// Route satisfies the RESTAPIModule interface
func (m *MediaModule) Route(s router.Router) error {
func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodPost, BasePath, m.MediaCreatePOSTHandler)
return nil
}
func (m *MediaModule) CreateTables(db db.DB) error {
// CreateTables populates necessary tables in the given DB
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},
}

View File

@ -33,7 +33,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *MediaModule) MediaCreatePOSTHandler(c *gin.Context) {
// MediaCreatePOSTHandler handles requests to create/upload media attachments
func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "statusCreatePOSTHandler")
authed, err := oauth.MustAuth(c, true, true, true, true) // posting new media is serious business so we want *everything*
if err != nil {

View File

@ -52,7 +52,7 @@ type MediaCreateTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
// standard suite models
@ -64,7 +64,7 @@ type MediaCreateTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// item being tested
mediaModule *mediamodule.MediaModule
mediaModule *mediamodule.Module
}
/*
@ -82,7 +82,7 @@ func (suite *MediaCreateTestSuite) SetupSuite() {
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
// setup module being tested
suite.mediaModule = mediamodule.New(suite.db, suite.mediaHandler, suite.mastoConverter, suite.config, suite.log).(*mediamodule.MediaModule)
suite.mediaModule = mediamodule.New(suite.db, suite.mediaHandler, suite.mastoConverter, suite.config, suite.log).(*mediamodule.Module)
}
func (suite *MediaCreateTestSuite) TearDownSuite() {
@ -115,7 +115,7 @@ func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful()
// set up the context for the request
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])

View File

@ -20,8 +20,9 @@ package security
import "github.com/gin-gonic/gin"
// flocBlock prevents google chrome cohort tracking by writing the Permissions-Policy header after all other parts of the request have been completed.
// FlocBlock is a middleware that prevents google chrome cohort tracking by
// writing the Permissions-Policy header after all other parts of the request have been completed.
// See: https://plausible.io/blog/google-floc
func (m *module) flocBlock(c *gin.Context) {
func (m *Module) FlocBlock(c *gin.Context) {
c.Header("Permissions-Policy", "interest-cohort=()")
}

View File

@ -26,25 +26,27 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/router"
)
// module implements the apiclient interface
type module struct {
// Module implements the ClientAPIModule interface for security middleware
type Module struct {
config *config.Config
log *logrus.Logger
}
// New returns a new security module
func New(config *config.Config, log *logrus.Logger) apimodule.ClientAPIModule {
return &module{
return &Module{
config: config,
log: log,
}
}
func (m *module) Route(s router.Router) error {
s.AttachMiddleware(m.flocBlock)
// Route attaches security middleware to the given router
func (m *Module) Route(s router.Router) error {
s.AttachMiddleware(m.FlocBlock)
return nil
}
func (m *module) CreateTables(db db.DB) error {
// CreateTables doesn't do diddly squat at the moment, it's just for fulfilling the interface
func (m *Module) CreateTables(db db.DB) error {
return nil
}

View File

@ -36,42 +36,60 @@ import (
)
const (
// IDKey is for status UUIDs
IDKey = "id"
// BasePath is the base path for serving the status API
BasePath = "/api/v1/statuses"
// BasePathWithID is just the base path with the ID key in it.
// Use this anywhere you need to know the ID of the status being queried.
BasePathWithID = BasePath + "/:" + IDKey
// ContextPath is used for fetching context of posts
ContextPath = BasePathWithID + "/context"
// FavouritedPath is for seeing who's faved a given status
FavouritedPath = BasePathWithID + "/favourited_by"
// FavouritePath is for posting a fave on a status
FavouritePath = BasePathWithID + "/favourite"
// UnfavouritePath is for removing a fave from a status
UnfavouritePath = BasePathWithID + "/unfavourite"
// RebloggedPath is for seeing who's boosted a given status
RebloggedPath = BasePathWithID + "/reblogged_by"
// ReblogPath is for boosting/reblogging a given status
ReblogPath = BasePathWithID + "/reblog"
// UnreblogPath is for undoing a boost/reblog of a given status
UnreblogPath = BasePathWithID + "/unreblog"
// BookmarkPath is for creating a bookmark on a given status
BookmarkPath = BasePathWithID + "/bookmark"
// UnbookmarkPath is for removing a bookmark from a given status
UnbookmarkPath = BasePathWithID + "/unbookmark"
// MutePath is for muting a given status so that notifications will no longer be received about it.
MutePath = BasePathWithID + "/mute"
// UnmutePath is for undoing an existing mute
UnmutePath = BasePathWithID + "/unmute"
// PinPath is for pinning a status to an account profile so that it's the first thing people see
PinPath = BasePathWithID + "/pin"
// UnpinPath is for undoing a pin and returning a status to the ever-swirling drain of time and entropy
UnpinPath = BasePathWithID + "/unpin"
)
type StatusModule struct {
// Module implements the ClientAPIModule interface for every related to posting/deleting/interacting with statuses
type Module struct {
config *config.Config
db db.DB
mediaHandler media.MediaHandler
mediaHandler media.Handler
mastoConverter mastotypes.Converter
distributor distributor.Distributor
log *logrus.Logger
}
// New returns a new account module
func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, mastoConverter mastotypes.Converter, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule {
return &StatusModule{
func New(config *config.Config, db db.DB, mediaHandler media.Handler, mastoConverter mastotypes.Converter, distributor distributor.Distributor, log *logrus.Logger) apimodule.ClientAPIModule {
return &Module{
config: config,
db: db,
mediaHandler: mediaHandler,
@ -82,7 +100,7 @@ func New(config *config.Config, db db.DB, mediaHandler media.MediaHandler, masto
}
// Route attaches all routes from this module to the given router
func (m *StatusModule) Route(r router.Router) error {
func (m *Module) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, BasePath, m.StatusCreatePOSTHandler)
r.AttachHandler(http.MethodDelete, BasePathWithID, m.StatusDELETEHandler)
@ -93,7 +111,8 @@ func (m *StatusModule) Route(r router.Router) error {
return nil
}
func (m *StatusModule) CreateTables(db db.DB) error {
// CreateTables populates necessary tables in the given DB
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.User{},
&gtsmodel.Account{},
@ -121,7 +140,8 @@ func (m *StatusModule) CreateTables(db db.DB) error {
return nil
}
func (m *StatusModule) muxHandler(c *gin.Context) {
// muxHandler is a little workaround to overcome the limitations of Gin
func (m *Module) muxHandler(c *gin.Context) {
m.log.Debug("entering mux handler")
ru := c.Request.RequestURI

View File

@ -53,7 +53,8 @@ type advancedVisibilityFlagsForm struct {
Likeable *bool `form:"likeable"`
}
func (m *StatusModule) StatusCreatePOSTHandler(c *gin.Context) {
// StatusCreatePOSTHandler deals with the creation of new statuses
func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "statusCreatePOSTHandler")
authed, err := oauth.MustAuth(c, true, true, true, true) // posting a status is serious business so we want *everything*
if err != nil {
@ -318,7 +319,7 @@ func parseVisibility(form *advancedStatusCreateForm, accountDefaultVis gtsmodel.
return nil
}
func (m *StatusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (m *Module) parseReplyToID(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.InReplyToID == "" {
return nil
}
@ -336,9 +337,8 @@ func (m *StatusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
if err := m.db.GetByID(form.InReplyToID, repliedStatus); err != nil {
if _, ok := err.(db.ErrNoEntries); ok {
return fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID)
} else {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
if !repliedStatus.VisibilityAdvanced.Replyable {
@ -349,9 +349,8 @@ func (m *StatusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
if err := m.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
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)
}
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 {
@ -367,7 +366,7 @@ func (m *StatusModule) parseReplyToID(form *advancedStatusCreateForm, thisAccoun
return nil
}
func (m *StatusModule) parseMediaIDs(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (m *Module) parseMediaIDs(form *advancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.MediaIDs == nil {
return nil
}
@ -408,7 +407,7 @@ func parseLanguage(form *advancedStatusCreateForm, accountDefaultLanguage string
return nil
}
func (m *StatusModule) parseMentions(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *Module) parseMentions(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
menchies := []string{}
gtsMenchies, err := m.db.MentionStringsToMentions(util.DeriveMentions(form.Status), accountID, status.ID)
if err != nil {
@ -427,7 +426,7 @@ func (m *StatusModule) parseMentions(form *advancedStatusCreateForm, accountID s
return nil
}
func (m *StatusModule) parseTags(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *Module) parseTags(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
tags := []string{}
gtsTags, err := m.db.TagStringsToTags(util.DeriveHashtags(form.Status), accountID, status.ID)
if err != nil {
@ -446,7 +445,7 @@ func (m *StatusModule) parseTags(form *advancedStatusCreateForm, accountID strin
return nil
}
func (m *StatusModule) parseEmojis(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (m *Module) parseEmojis(form *advancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
emojis := []string{}
gtsEmojis, err := m.db.EmojiStringsToEmojis(util.DeriveEmojis(form.Status), accountID, status.ID)
if err != nil {

View File

@ -29,7 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *StatusModule) StatusDELETEHandler(c *gin.Context) {
// StatusDELETEHandler verifies and handles deletion of a status
func (m *Module) StatusDELETEHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "StatusDELETEHandler",
"request_uri": c.Request.RequestURI,

View File

@ -29,7 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *StatusModule) StatusFavePOSTHandler(c *gin.Context) {
// StatusFavePOSTHandler handles fave requests against a given status ID
func (m *Module) StatusFavePOSTHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "StatusFavePOSTHandler",
"request_uri": c.Request.RequestURI,

View File

@ -29,7 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *StatusModule) StatusFavedByGETHandler(c *gin.Context) {
// StatusFavedByGETHandler is for serving a list of accounts that have faved a given status
func (m *Module) StatusFavedByGETHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "statusGETHandler",
"request_uri": c.Request.RequestURI,

View File

@ -28,7 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *StatusModule) StatusGETHandler(c *gin.Context) {
// StatusGETHandler is for handling requests to just get one status based on its ID
func (m *Module) StatusGETHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "statusGETHandler",
"request_uri": c.Request.RequestURI,

View File

@ -29,7 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (m *StatusModule) StatusUnfavePOSTHandler(c *gin.Context) {
// StatusUnfavePOSTHandler is for undoing a fave on a status with a given ID
func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {
l := m.log.WithFields(logrus.Fields{
"func": "StatusUnfavePOSTHandler",
"request_uri": c.Request.RequestURI,

View File

@ -52,7 +52,7 @@ type StatusCreateTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
distributor distributor.Distributor
@ -65,7 +65,7 @@ type StatusCreateTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// module being tested
statusModule *status.StatusModule
statusModule *status.Module
}
/*
@ -85,7 +85,7 @@ func (suite *StatusCreateTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.Module)
}
func (suite *StatusCreateTestSuite) TearDownSuite() {
@ -121,7 +121,7 @@ func (suite *StatusCreateTestSuite) TearDownTest() {
func (suite *StatusCreateTestSuite) TestPostNewStatus() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
@ -175,7 +175,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
@ -216,7 +216,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
// Try to reply to a status that doesn't exist
func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
@ -247,7 +247,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
// Post a reply to the status of a local user that allows replies.
func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
@ -287,7 +287,7 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
// Take a media file which is currently not associated with a status, and attach it to a new status.
func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()

View File

@ -52,7 +52,7 @@ type StatusFaveTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
distributor distributor.Distributor
@ -66,7 +66,7 @@ type StatusFaveTestSuite struct {
testStatuses map[string]*gtsmodel.Status
// module being tested
statusModule *status.StatusModule
statusModule *status.Module
}
/*
@ -86,7 +86,7 @@ func (suite *StatusFaveTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.Module)
}
func (suite *StatusFaveTestSuite) TearDownSuite() {
@ -120,7 +120,7 @@ func (suite *StatusFaveTestSuite) TearDownTest() {
func (suite *StatusFaveTestSuite) TestPostFave() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
targetStatus := suite.testStatuses["admin_account_status_2"]
@ -168,7 +168,7 @@ func (suite *StatusFaveTestSuite) TestPostFave() {
func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable

View File

@ -52,7 +52,7 @@ type StatusFavedByTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
distributor distributor.Distributor
@ -66,7 +66,7 @@ type StatusFavedByTestSuite struct {
testStatuses map[string]*gtsmodel.Status
// module being tested
statusModule *status.StatusModule
statusModule *status.Module
}
// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
@ -82,7 +82,7 @@ func (suite *StatusFavedByTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.Module)
}
func (suite *StatusFavedByTestSuite) TearDownSuite() {
@ -114,7 +114,7 @@ func (suite *StatusFavedByTestSuite) TearDownTest() {
func (suite *StatusFavedByTestSuite) TestGetFavedBy() {
t := suite.testTokens["local_account_2"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
targetStatus := suite.testStatuses["admin_account_status_1"] // this status is faved by local_account_1

View File

@ -43,7 +43,7 @@ type StatusGetTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
distributor distributor.Distributor
@ -56,7 +56,7 @@ type StatusGetTestSuite struct {
testAttachments map[string]*gtsmodel.MediaAttachment
// module being tested
statusModule *status.StatusModule
statusModule *status.Module
}
/*
@ -76,7 +76,7 @@ func (suite *StatusGetTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.Module)
}
func (suite *StatusGetTestSuite) TearDownSuite() {

View File

@ -52,7 +52,7 @@ type StatusUnfaveTestSuite struct {
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
mediaHandler media.Handler
oauthServer oauth.Server
distributor distributor.Distributor
@ -66,7 +66,7 @@ type StatusUnfaveTestSuite struct {
testStatuses map[string]*gtsmodel.Status
// module being tested
statusModule *status.StatusModule
statusModule *status.Module
}
/*
@ -86,7 +86,7 @@ func (suite *StatusUnfaveTestSuite) SetupSuite() {
suite.distributor = testrig.NewTestDistributor()
// setup module being tested
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.StatusModule)
suite.statusModule = status.New(suite.config, suite.db, suite.mediaHandler, suite.mastoConverter, suite.distributor, suite.log).(*status.Module)
}
func (suite *StatusUnfaveTestSuite) TearDownSuite() {
@ -120,7 +120,7 @@ func (suite *StatusUnfaveTestSuite) TearDownTest() {
func (suite *StatusUnfaveTestSuite) TestPostUnfave() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// this is the status we wanna unfave: in the testrig it's already faved by this account
targetStatus := suite.testStatuses["admin_account_status_1"]
@ -169,7 +169,7 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() {
func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
oauthToken := oauth.TokenToOauthToken(t)
// this is the status we wanna unfave: in the testrig it's not faved by this account
targetStatus := suite.testStatuses["admin_account_status_2"]

View File

@ -251,6 +251,7 @@ type Flags struct {
StatusesMaxMediaFiles string
}
// Defaults contains all the default values for a gotosocial config
type Defaults struct {
LogLevel string
ApplicationName string

View File

@ -96,6 +96,8 @@ func Default() *Config {
}
}
// GetDefaults returns a populated Defaults struct with most of the values set to reasonable defaults.
// Note that if you use this function, you still need to set Host and, if desired, ConfigPath.
func GetDefaults() Defaults {
return Defaults{
LogLevel: "info",
@ -136,6 +138,7 @@ func GetDefaults() Defaults {
}
}
// GetTestDefaults returns a Defaults struct with values set that are suitable for local testing.
func GetTestDefaults() Defaults {
return Defaults{
LogLevel: "trace",

View File

@ -25,7 +25,6 @@ import (
"sync"
"github.com/go-fed/activity/pub"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
@ -49,7 +48,19 @@ func newFederatingDB(db DB, config *config.Config) pub.Database {
/*
GO-FED DB INTERFACE-IMPLEMENTING FUNCTIONS
*/
func (f *federatingDB) Lock(ctx context.Context, id *url.URL) error {
// Lock takes a lock for the object at the specified id. If an error
// is returned, the lock must not have been taken.
//
// The lock must be able to succeed for an id that does not exist in
// the database. This means acquiring the lock does not guarantee the
// entry exists in the database.
//
// Locks are encouraged to be lightweight and in the Go layer, as some
// processes require tight loops acquiring and releasing locks.
//
// Used to ensure race conditions in multiple requests do not occur.
func (f *federatingDB) Lock(c context.Context, id *url.URL) error {
// Before any other Database methods are called, the relevant `id`
// entries are locked to allow for fine-grained concurrency.
@ -65,7 +76,11 @@ func (f *federatingDB) Lock(ctx context.Context, id *url.URL) error {
return nil
}
func (f *federatingDB) Unlock(ctx context.Context, id *url.URL) error {
// Unlock makes the lock for the object at the specified id available.
// If an error is returned, the lock must have still been freed.
//
// Used to ensure race conditions in multiple requests do not occur.
func (f *federatingDB) Unlock(c context.Context, id *url.URL) error {
// Once Go-Fed is done calling Database methods, the relevant `id`
// entries are unlocked.
@ -78,82 +93,168 @@ func (f *federatingDB) Unlock(ctx context.Context, id *url.URL) error {
return nil
}
func (f *federatingDB) InboxContains(ctx context.Context, inbox *url.URL, id *url.URL) (bool, error) {
// InboxContains returns true if the OrderedCollection at 'inbox'
// contains the specified 'id'.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error) {
return false, nil
}
func (f *federatingDB) GetInbox(ctx context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
// GetInbox returns the first ordered collection page of the outbox at
// the specified IRI, for prepending new items.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) GetInbox(c context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
return nil, nil
}
func (f *federatingDB) SetInbox(ctx context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error {
// SetInbox saves the inbox value given from GetInbox, with new items
// prepended. Note that the new items must not be added as independent
// database entries. Separate calls to Create will do that.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) SetInbox(c context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error {
return nil
}
func (f *federatingDB) Owns(ctx context.Context, id *url.URL) (owns bool, err error) {
return id.Host == f.config.Host, nil
}
func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
return nil, nil
}
func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
return nil, nil
}
func (f *federatingDB) OutboxForInbox(ctx context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
return nil, nil
}
func (f *federatingDB) Exists(ctx context.Context, id *url.URL) (exists bool, err error) {
// Owns returns true if the database has an entry for the IRI and it
// exists in the database.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Owns(c context.Context, id *url.URL) (owns bool, err error) {
return false, nil
}
func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type, err error) {
// ActorForOutbox fetches the actor's IRI for the given outbox IRI.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
return nil, nil
}
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
t, err := streams.NewTypeResolver()
if err != nil {
return err
}
if err := t.Resolve(ctx, asType); err != nil {
return err
}
asType.GetTypeName()
// ActorForInbox fetches the actor's IRI for the given outbox IRI.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
return nil, nil
}
// OutboxForInbox fetches the corresponding actor's outbox IRI for the
// actor's inbox IRI.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
return nil, nil
}
// Exists returns true if the database has an entry for the specified
// id. It may not be owned by this application instance.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err error) {
return false, nil
}
// Get returns the database entry for the specified id.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, err error) {
return nil, nil
}
// Create adds a new entry to the database which must be able to be
// keyed by its id.
//
// Note that Activity values received from federated peers may also be
// created in the database this way if the Federating Protocol is
// enabled. The client may freely decide to store only the id instead of
// the entire value.
//
// The library makes this call only after acquiring a lock first.
//
// Under certain conditions and network activities, Create may be called
// multiple times for the same ActivityStreams object.
func (f *federatingDB) Create(c context.Context, asType vocab.Type) error {
return nil
}
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
// Update sets an existing entry to the database based on the value's
// id.
//
// Note that Activity values received from federated peers may also be
// updated in the database this way if the Federating Protocol is
// enabled. The client may freely decide to store only the id instead of
// the entire value.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Update(c context.Context, asType vocab.Type) error {
return nil
}
func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error {
// Delete removes the entry with the given id.
//
// Delete is only called for federated objects. Deletes from the Social
// Protocol instead call Update to create a Tombstone.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
return nil
}
func (f *federatingDB) GetOutbox(ctx context.Context, outboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
// GetOutbox returns the first ordered collection page of the outbox
// at the specified IRI, for prepending new items.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) GetOutbox(c context.Context, outboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error) {
return nil, nil
}
func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error {
// SetOutbox saves the outbox value given from GetOutbox, with new items
// prepended. Note that the new items must not be added as independent
// database entries. Separate calls to Create will do that.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) SetOutbox(c context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error {
return nil
}
func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (id *url.URL, err error) {
// NewID creates a new IRI id for the provided activity or object. The
// implementation does not need to set the 'id' property and simply
// needs to determine the value.
//
// The go-fed library will handle setting the 'id' property on the
// activity or object provided with the value returned.
func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err error) {
return nil, nil
}
func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
// Followers obtains the Followers Collection for an actor with the
// given id.
//
// If modified, the library will then call Update.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Followers(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
return nil, nil
}
func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
// Following obtains the Following Collection for an actor with the
// given id.
//
// If modified, the library will then call Update.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Following(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
return nil, nil
}
func (f *federatingDB) Liked(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
// Liked obtains the Liked Collection for an actor with the
// given id.
//
// If modified, the library will then call Update.
//
// The library makes this call only after acquiring a lock first.
func (f *federatingDB) Liked(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
return nil, nil
}

View File

@ -22,29 +22,29 @@ package gtsmodel
type ActivityStreamsObject string
const (
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article
// ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article
ActivityStreamsArticle ActivityStreamsObject = "Article"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio
// ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio
ActivityStreamsAudio ActivityStreamsObject = "Audio"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document
// ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document
ActivityStreamsDocument ActivityStreamsObject = "Event"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
// ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
ActivityStreamsEvent ActivityStreamsObject = "Event"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image
// ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image
ActivityStreamsImage ActivityStreamsObject = "Image"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
// ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note
ActivityStreamsNote ActivityStreamsObject = "Note"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page
// ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page
ActivityStreamsPage ActivityStreamsObject = "Page"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
// ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
ActivityStreamsPlace ActivityStreamsObject = "Place"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile
// ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile
ActivityStreamsProfile ActivityStreamsObject = "Profile"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship
// ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship
ActivityStreamsRelationship ActivityStreamsObject = "Relationship"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
// ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
ActivityStreamsTombstone ActivityStreamsObject = "Tombstone"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video
// ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video
ActivityStreamsVideo ActivityStreamsObject = "Video"
)
@ -52,15 +52,15 @@ const (
type ActivityStreamsActor string
const (
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application
// ActivityStreamsApplication https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application
ActivityStreamsApplication ActivityStreamsActor = "Application"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group
// ActivityStreamsGroup https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group
ActivityStreamsGroup ActivityStreamsActor = "Group"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization
// ActivityStreamsOrganization https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization
ActivityStreamsOrganization ActivityStreamsActor = "Organization"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
// ActivityStreamsPerson https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
ActivityStreamsPerson ActivityStreamsActor = "Person"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service
// ActivityStreamsService https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service
ActivityStreamsService ActivityStreamsActor = "Service"
)
@ -68,60 +68,60 @@ const (
type ActivityStreamsActivity string
const (
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept
// ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept
ActivityStreamsAccept ActivityStreamsActivity = "Accept"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add
// ActivityStreamsAdd https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add
ActivityStreamsAdd ActivityStreamsActivity = "Add"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-announce
// ActivityStreamsAnnounce https://www.w3.org/TR/activitystreams-vocabulary/#dfn-announce
ActivityStreamsAnnounce ActivityStreamsActivity = "Announce"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-arrive
// ActivityStreamsArrive https://www.w3.org/TR/activitystreams-vocabulary/#dfn-arrive
ActivityStreamsArrive ActivityStreamsActivity = "Arrive"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-block
// ActivityStreamsBlock https://www.w3.org/TR/activitystreams-vocabulary/#dfn-block
ActivityStreamsBlock ActivityStreamsActivity = "Block"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create
// ActivityStreamsCreate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create
ActivityStreamsCreate ActivityStreamsActivity = "Create"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete
// ActivityStreamsDelete https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete
ActivityStreamsDelete ActivityStreamsActivity = "Delete"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-dislike
// ActivityStreamsDislike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-dislike
ActivityStreamsDislike ActivityStreamsActivity = "Dislike"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag
// ActivityStreamsFlag https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag
ActivityStreamsFlag ActivityStreamsActivity = "Flag"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow
// ActivityStreamsFollow https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow
ActivityStreamsFollow ActivityStreamsActivity = "Follow"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore
// ActivityStreamsIgnore https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore
ActivityStreamsIgnore ActivityStreamsActivity = "Ignore"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-invite
// ActivityStreamsInvite https://www.w3.org/TR/activitystreams-vocabulary/#dfn-invite
ActivityStreamsInvite ActivityStreamsActivity = "Invite"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join
// ActivityStreamsJoin https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join
ActivityStreamsJoin ActivityStreamsActivity = "Join"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-leave
// ActivityStreamsLeave https://www.w3.org/TR/activitystreams-vocabulary/#dfn-leave
ActivityStreamsLeave ActivityStreamsActivity = "Leave"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like
// ActivityStreamsLike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like
ActivityStreamsLike ActivityStreamsActivity = "Like"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-listen
// ActivityStreamsListen https://www.w3.org/TR/activitystreams-vocabulary/#dfn-listen
ActivityStreamsListen ActivityStreamsActivity = "Listen"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move
// ActivityStreamsMove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move
ActivityStreamsMove ActivityStreamsActivity = "Move"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-offer
// ActivityStreamsOffer https://www.w3.org/TR/activitystreams-vocabulary/#dfn-offer
ActivityStreamsOffer ActivityStreamsActivity = "Offer"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question
// ActivityStreamsQuestion https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question
ActivityStreamsQuestion ActivityStreamsActivity = "Question"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-reject
// ActivityStreamsReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-reject
ActivityStreamsReject ActivityStreamsActivity = "Reject"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-read
// ActivityStreamsRead https://www.w3.org/TR/activitystreams-vocabulary/#dfn-read
ActivityStreamsRead ActivityStreamsActivity = "Read"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-remove
// ActivityStreamsRemove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-remove
ActivityStreamsRemove ActivityStreamsActivity = "Remove"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativereject
// ActivityStreamsTentativeReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativereject
ActivityStreamsTentativeReject ActivityStreamsActivity = "TentativeReject"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativeaccept
// ActivityStreamsTentativeAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativeaccept
ActivityStreamsTentativeAccept ActivityStreamsActivity = "TentativeAccept"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-travel
// ActivityStreamsTravel https://www.w3.org/TR/activitystreams-vocabulary/#dfn-travel
ActivityStreamsTravel ActivityStreamsActivity = "Travel"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-undo
// ActivityStreamsUndo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-undo
ActivityStreamsUndo ActivityStreamsActivity = "Undo"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-update
// ActivityStreamsUpdate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-update
ActivityStreamsUpdate ActivityStreamsActivity = "Update"
// https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view
// ActivityStreamsView https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view
ActivityStreamsView ActivityStreamsActivity = "View"
)

View File

@ -20,6 +20,7 @@ package gtsmodel
import "time"
// Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens.
type Emoji struct {
// database ID of this emoji
ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull"`

View File

@ -93,13 +93,13 @@ type Thumbnail struct {
type ProcessingStatus int
const (
// ProcessingStatusReceived: the attachment has been received and is awaiting processing. No thumbnail available yet.
// ProcessingStatusReceived indicates the attachment has been received and is awaiting processing. No thumbnail available yet.
ProcessingStatusReceived ProcessingStatus = 0
// ProcessingStatusProcessing: the attachment is currently being processed. Thumbnail is available but full media is not.
// ProcessingStatusProcessing indicates the attachment is currently being processed. Thumbnail is available but full media is not.
ProcessingStatusProcessing ProcessingStatus = 1
// ProcessingStatusProcessed: the attachment has been fully processed and is ready to be served.
// ProcessingStatusProcessed indicates the attachment has been fully processed and is ready to be served.
ProcessingStatusProcessed ProcessingStatus = 2
// ProcessingStatusError: something went wrong processing the attachment and it won't be tried again--these can be deleted.
// ProcessingStatusError indicates something went wrong processing the attachment and it won't be tried again--these can be deleted.
ProcessingStatusError ProcessingStatus = 666
)
@ -142,6 +142,8 @@ type Original struct {
Aspect float64
}
// Focus describes the 'center' of the image for display purposes.
// X and Y should each be between -1 and 1
type Focus struct {
X float32
Y float32

View File

@ -95,17 +95,17 @@ type Status struct {
type Visibility string
const (
// This status will be visible to everyone on all timelines.
// VisibilityPublic means this status will be visible to everyone on all timelines.
VisibilityPublic Visibility = "public"
// This status will be visible to everyone, but will only show on home timeline to followers, and in lists.
// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists.
VisibilityUnlocked Visibility = "unlocked"
// This status is viewable to followers only.
// VisibilityFollowersOnly means this status is viewable to followers only.
VisibilityFollowersOnly Visibility = "followers_only"
// This status is visible to mutual followers only.
// VisibilityMutualsOnly means this status is visible to mutual followers only.
VisibilityMutualsOnly Visibility = "mutuals_only"
// This status is visible only to mentioned recipients
// VisibilityDirect means this status is visible only to mentioned recipients
VisibilityDirect Visibility = "direct"
// Default visibility to use when no other setting can be found
// VisibilityDefault is used when no other setting can be found
VisibilityDefault Visibility = "public"
)

View File

@ -574,9 +574,8 @@ func (ps *postgresService) Blocked(account1 string, account2 string) (bool, erro
if err == pg.ErrNoRows {
blocked = false
return blocked, nil
} else {
return blocked, err
}
return blocked, err
}
blocked = true
return blocked, nil
@ -597,9 +596,8 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
l.Debug("target user could not be selected")
if err == pg.ErrNoRows {
return false, ErrNoEntries{}
} else {
return false, err
}
return false, err
}
// if target user is disabled, not yet approved, or not confirmed then don't show the status
@ -635,11 +633,10 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
if err == pg.ErrNoRows {
l.Debug("requesting account is local but there's no corresponding user")
return false, nil
} else {
}
l.Debugf("requesting account is local but there was an error getting the corresponding user: %s", err)
return false, err
}
}
// okay, user exists, so make sure it has full privileges/is confirmed/approved
if requestingUser.Disabled || !requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() {
l.Debug("requesting account is local but corresponding user is either disabled, not approved, or not confirmed")
@ -751,9 +748,8 @@ func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmode
if err != nil {
if err == pg.ErrNoRows {
return false, nil
} else {
return false, err
}
return false, err
}
// make sure account 2 follows account 1
@ -761,9 +757,8 @@ func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmode
if err != nil {
if err == pg.ErrNoRows {
return false, nil
} else {
return false, err
}
return false, err
}
return f1 && f2, nil

View File

@ -95,12 +95,14 @@ func (d *distributor) Stop() error {
return nil
}
// FromClientAPI wraps a message that travels from the client API into the distributor
type FromClientAPI struct {
APObjectType gtsmodel.ActivityStreamsObject
APActivityType gtsmodel.ActivityStreamsActivity
Activity interface{}
}
// ToClientAPI wraps a message that travels from the distributor into the client API
type ToClientAPI struct {
APObjectType gtsmodel.ActivityStreamsObject
APActivityType gtsmodel.ActivityStreamsActivity

View File

@ -44,76 +44,260 @@ type Federator struct {
db db.DB
}
// AuthenticateGetInbox determines whether the request is for a GET call to the Actor's Inbox.
/*
GO FED FEDERATING PROTOCOL INTERFACE
FederatingProtocol contains behaviors an application needs to satisfy for the
full ActivityPub S2S implementation to be supported by this library.
It is only required if the client application wants to support the server-to-
server, or federating, protocol.
It is passed to the library as a dependency injection from the client
application.
*/
// PostInboxRequestBodyHook callback after parsing the request body for a federated request
// to the Actor's inbox.
//
// Can be used to set contextual information based on the Activity
// received.
//
// Only called if the Federated Protocol is enabled.
//
// Warning: Neither authentication nor authorization has taken place at
// this time. Doing anything beyond setting contextual information is
// strongly discouraged.
//
// If an error is returned, it is passed back to the caller of
// PostInbox. In this case, the DelegateActor implementation must not
// write a response to the ResponseWriter as is expected that the caller
// to PostInbox will do so when handling the error.
func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
// TODO
return nil, nil
}
// AuthenticatePostInbox delegates the authentication of a POST to an
// inbox.
//
// If an error is returned, it is passed back to the caller of
// PostInbox. In this case, the implementation must not write a
// response to the ResponseWriter as is expected that the client will
// do so when handling the error. The 'authenticated' is ignored.
//
// If no error is returned, but authentication or authorization fails,
// then authenticated must be false and error nil. It is expected that
// the implementation handles writing to the ResponseWriter in this
// case.
//
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// TODO
return nil, false, nil
}
// Blocked should determine whether to permit a set of actors given by
// their ids are able to interact with this particular end user due to
// being blocked or other application-specific logic.
//
// If an error is returned, it is passed back to the caller of
// PostInbox.
//
// If no error is returned, but authentication or authorization fails,
// then blocked must be true and error nil. An http.StatusForbidden
// will be written in the wresponse.
//
// Finally, if the authentication and authorization succeeds, then
// blocked must be false and error nil. The request will continue
// to be processed.
func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
// TODO
return false, nil
}
// FederatingCallbacks returns the application logic that handles
// ActivityStreams received from federating peers.
//
// Note that certain types of callbacks will be 'wrapped' with default
// behaviors supported natively by the library. Other callbacks
// compatible with streams.TypeResolver can be specified by 'other'.
//
// For example, setting the 'Create' field in the
// FederatingWrappedCallbacks lets an application dependency inject
// additional behaviors they want to take place, including the default
// behavior supplied by this library. This is guaranteed to be compliant
// with the ActivityPub Social protocol.
//
// To override the default behavior, instead supply the function in
// 'other', which does not guarantee the application will be compliant
// with the ActivityPub Social Protocol.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension. The unhandled ones are passed to DefaultCallback.
func (f *Federator) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
// TODO
return pub.FederatingWrappedCallbacks{}, nil, nil
}
// DefaultCallback is called for types that go-fed can deserialize but
// are not handled by the application's callbacks returned in the
// Callbacks method.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension, so the unhandled ones are passed to
// DefaultCallback.
func (f *Federator) DefaultCallback(ctx context.Context, activity pub.Activity) error {
// TODO
return nil
}
// MaxInboxForwardingRecursionDepth determines how deep to search within
// an activity to determine if inbox forwarding needs to occur.
//
// Zero or negative numbers indicate infinite recursion.
func (f *Federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
// MaxDeliveryRecursionDepth determines how deep to search within
// collections owned by peers when they are targeted to receive a
// delivery.
//
// Zero or negative numbers indicate infinite recursion.
func (f *Federator) MaxDeliveryRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
// FilterForwarding allows the implementation to apply business logic
// such as blocks, spam filtering, and so on to a list of potential
// Collections and OrderedCollections of recipients when inbox
// forwarding has been triggered.
//
// The activity is provided as a reference for more intelligent
// logic to be used, but the implementation must not modify it.
func (f *Federator) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
// TODO
return nil, nil
}
// GetInbox returns the OrderedCollection inbox of the actor for this
// context. It is up to the implementation to provide the correct
// collection for the kind of authorization given in the request.
//
// AuthenticateGetInbox will be called prior to this.
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
func (f *Federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
// TODO
return nil, nil
}
/*
GOFED COMMON BEHAVIOR INTERFACE
Contains functions required for both the Social API and Federating Protocol.
It is passed to the library as a dependency injection from the client
application.
*/
// AuthenticateGetInbox delegates the authentication of a GET to an
// inbox.
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
//
// If an error is returned, it is passed back to the caller of
// GetInbox. In this case, the implementation must not write a
// response to the ResponseWriter as is expected that the client will
// do so when handling the error. The 'authenticated' is ignored.
//
// If no error is returned, but authentication or authorization fails,
// then authenticated must be false and error nil. It is expected that
// the implementation handles writing to the ResponseWriter in this
// case.
//
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (f *Federator) AuthenticateGetInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// TODO
// use context.WithValue() and context.Value() to set and get values through here
return nil, false, nil
}
// AuthenticateGetOutbox determines whether the request is for a GET call to the Actor's Outbox.
// AuthenticateGetOutbox delegates the authentication of a GET to an
// outbox.
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
//
// If an error is returned, it is passed back to the caller of
// GetOutbox. In this case, the implementation must not write a
// response to the ResponseWriter as is expected that the client will
// do so when handling the error. The 'authenticated' is ignored.
//
// If no error is returned, but authentication or authorization fails,
// then authenticated must be false and error nil. It is expected that
// the implementation handles writing to the ResponseWriter in this
// case.
//
// Finally, if the authentication and authorization succeeds, then
// authenticated must be true and error nil. The request will continue
// to be processed.
func (f *Federator) AuthenticateGetOutbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// TODO
return nil, false, nil
}
// GetOutbox returns a proper paginated view of the Outbox for serving in a response.
// GetOutbox returns the OrderedCollection inbox of the actor for this
// context. It is up to the implementation to provide the correct
// collection for the kind of authorization given in the request.
//
// AuthenticateGetOutbox will be called prior to this.
//
// Always called, regardless whether the Federated Protocol or Social
// API is enabled.
func (f *Federator) GetOutbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
// TODO
return nil, nil
}
// NewTransport returns a new pub.Transport for federating with peer software.
// NewTransport returns a new Transport on behalf of a specific actor.
//
// The actorBoxIRI will be either the inbox or outbox of an actor who is
// attempting to do the dereferencing or delivery. Any authentication
// scheme applied on the request must be based on this actor. The
// request must contain some sort of credential of the user, such as a
// HTTP Signature.
//
// The gofedAgent passed in should be used by the Transport
// implementation in the User-Agent, as well as the application-specific
// user agent string. The gofedAgent will indicate this library's use as
// well as the library's version number.
//
// Any server-wide rate-limiting that needs to occur should happen in a
// Transport implementation. This factory function allows this to be
// created, so peer servers are not DOS'd.
//
// Any retry logic should also be handled by the Transport
// implementation.
//
// Note that the library will not maintain a long-lived pointer to the
// returned Transport so that any private credentials are able to be
// garbage collected.
func (f *Federator) NewTransport(ctx context.Context, actorBoxIRI *url.URL, gofedAgent string) (pub.Transport, error) {
// TODO
return nil, nil
}
func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Request, activity pub.Activity) (context.Context, error) {
// TODO
return nil, nil
}
func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWriter, r *http.Request) (context.Context, bool, error) {
// TODO
return nil, false, nil
}
func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, error) {
// TODO
return false, nil
}
func (f *Federator) FederatingCallbacks(ctx context.Context) (pub.FederatingWrappedCallbacks, []interface{}, error) {
// TODO
return pub.FederatingWrappedCallbacks{}, nil, nil
}
func (f *Federator) DefaultCallback(ctx context.Context, activity pub.Activity) error {
// TODO
return nil
}
func (f *Federator) MaxInboxForwardingRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
func (f *Federator) MaxDeliveryRecursionDepth(ctx context.Context) int {
// TODO
return 0
}
func (f *Federator) FilterForwarding(ctx context.Context, potentialRecipients []*url.URL, a pub.Activity) ([]*url.URL, error) {
// TODO
return nil, nil
}
func (f *Federator) GetInbox(ctx context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
// TODO
return nil, nil
}
/*
GOFED CLOCK INTERFACE
Determines the time.
*/
// Now returns the current time.
func (f *Federator) Now() time.Time {
return time.Now()
}

View File

@ -105,15 +105,16 @@ type StatusCreateRequest struct {
Language string `form:"language"`
}
// Visibility denotes the visibility of this status to other users
type Visibility string
const (
// visible to everyone
// VisibilityPublic means visible to everyone
VisibilityPublic Visibility = "public"
// visible to everyone but only on home timelines or in lists
// VisibilityUnlisted means visible to everyone but only on home timelines or in lists
VisibilityUnlisted Visibility = "unlisted"
// visible to followers only
// VisibilityPrivate means visible to followers only
VisibilityPrivate Visibility = "private"
// visible only to tagged recipients
// VisibilityDirect means visible only to tagged recipients
VisibilityDirect Visibility = "direct"
)

View File

@ -33,27 +33,27 @@ import (
)
const (
// Key for small/thumbnail versions of media
// MediaSmall is the key for small/thumbnail versions of media
MediaSmall = "small"
// Key for original/fullsize versions of media and emoji
// MediaOriginal is the key for original/fullsize versions of media and emoji
MediaOriginal = "original"
// Key for static (non-animated) versions of emoji
// MediaStatic is the key for static (non-animated) versions of emoji
MediaStatic = "static"
// Key for media attachments
// MediaAttachment is the key for media attachments
MediaAttachment = "attachment"
// Key for profile header
// MediaHeader is the key for profile header requests
MediaHeader = "header"
// Key for profile avatar
// MediaAvatar is the key for profile avatar requests
MediaAvatar = "avatar"
// Key for emoji type
// MediaEmoji is the key for emoji type requests
MediaEmoji = "emoji"
// Maximum permitted bytes of an emoji upload (50kb)
// EmojiMaxBytes is the maximum permitted bytes of an emoji upload (50kb)
EmojiMaxBytes = 51200
)
// MediaHandler provides an interface for parsing, storing, and retrieving media objects like photos, videos, and gifs.
type MediaHandler interface {
// Handler provides an interface for parsing, storing, and retrieving media objects like photos, videos, and gifs.
type Handler interface {
// ProcessHeaderOrAvatar 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.
@ -77,7 +77,8 @@ type mediaHandler struct {
log *logrus.Logger
}
func New(config *config.Config, database db.DB, storage storage.Storage, log *logrus.Logger) MediaHandler {
// New returns a new handler with the given config, db, storage, and logger
func New(config *config.Config, database db.DB, storage storage.Storage, log *logrus.Logger) Handler {
return &mediaHandler{
config: config,
db: database,
@ -90,6 +91,9 @@ func New(config *config.Config, database db.DB, storage storage.Storage, log *lo
INTERFACE FUNCTIONS
*/
// ProcessHeaderOrAvatar 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.
func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
l := mh.log.WithField("func", "SetHeaderForAccountID")
@ -125,6 +129,9 @@ func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID strin
return ma, nil
}
// ProcessLocalAttachment takes a new attachment and the requesting 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 media,
// and then returns information to the caller about the attachment.
func (mh *mediaHandler) ProcessLocalAttachment(attachment []byte, accountID string) (*gtsmodel.MediaAttachment, error) {
contentType, err := parseContentType(attachment)
if err != nil {
@ -160,6 +167,9 @@ func (mh *mediaHandler) ProcessLocalAttachment(attachment []byte, accountID stri
return nil, fmt.Errorf("content type %s not (yet) supported", contentType)
}
// ProcessLocalEmoji takes a new emoji and a shortcode, cleans it up, puts it in storage, and creates a new
// *gts.Emoji for it, then returns it to the caller. It's the caller's responsibility to put the returned struct
// in the database.
func (mh *mediaHandler) ProcessLocalEmoji(emojiBytes []byte, shortcode string) (*gtsmodel.Emoji, error) {
var clean []byte
var err error

View File

@ -64,6 +64,7 @@ func (cs *clientStore) Delete(ctx context.Context, id string) error {
return cs.db.DeleteByID(id, poc)
}
// Client is a handy little wrapper for typical oauth client details
type Client struct {
ID string
Secret string

View File

@ -46,7 +46,7 @@ const (
// of a User who has successfully passed Bearer token authorization.
// The interface returned from grabbing this key should be parsed as a *gtsmodel.Account
SessionAuthorizedAccount = "authorized_account"
// SessionAuthorizedAccount is the key set in the gin context for the Application
// SessionAuthorizedApplication is the key set in the gin context for the Application
// of a Client who has successfully passed Bearer token authorization.
// The interface returned from grabbing this key should be parsed as a *gtsmodel.Application
SessionAuthorizedApplication = "authorized_app"
@ -66,6 +66,10 @@ type s struct {
log *logrus.Logger
}
// Authed wraps an authorized token, application, user, and account.
// It is used in the functions GetAuthed and MustAuth.
// Because the user might *not* be authed, any of the fields in this struct
// might be nil, so make sure to check that when you're using this struct anywhere.
type Authed struct {
Token oauth2.TokenInfo
Application *gtsmodel.Application
@ -208,6 +212,7 @@ func (s *s) GenerateUserAccessToken(ti oauth2.TokenInfo, clientSecret string, us
return accessToken, nil
}
// New returns a new oauth server that implements the Server interface
func New(database db.DB, log *logrus.Logger) Server {
ts := newTokenStore(context.Background(), database, log)
cs := newClientStore(database)

View File

@ -98,7 +98,7 @@ func (pts *tokenStore) Create(ctx context.Context, info oauth2.TokenInfo) error
if !ok {
return errors.New("info param was not a models.Token")
}
if err := pts.db.Put(OAuthTokenToPGToken(t)); err != nil {
if err := pts.db.Put(TokenToPGToken(t)); err != nil {
return fmt.Errorf("error in tokenstore create: %s", err)
}
return nil
@ -130,7 +130,7 @@ func (pts *tokenStore) GetByCode(ctx context.Context, code string) (oauth2.Token
if err := pts.db.GetWhere("code", code, pgt); err != nil {
return nil, err
}
return PGTokenToOauthToken(pgt), nil
return TokenToOauthToken(pgt), nil
}
// GetByAccess selects a token from the DB based on the Access field
@ -144,7 +144,7 @@ func (pts *tokenStore) GetByAccess(ctx context.Context, access string) (oauth2.T
if err := pts.db.GetWhere("access", access, pgt); err != nil {
return nil, err
}
return PGTokenToOauthToken(pgt), nil
return TokenToOauthToken(pgt), nil
}
// GetByRefresh selects a token from the DB based on the Refresh field
@ -158,7 +158,7 @@ func (pts *tokenStore) GetByRefresh(ctx context.Context, refresh string) (oauth2
if err := pts.db.GetWhere("refresh", refresh, pgt); err != nil {
return nil, err
}
return PGTokenToOauthToken(pgt), nil
return TokenToOauthToken(pgt), nil
}
/*
@ -194,8 +194,8 @@ type Token struct {
RefreshExpiresAt time.Time `pg:"type:timestamp"`
}
// OAuthTokenToPGToken is a lil util function that takes a gotosocial token and gives back a token for inserting into postgres
func OAuthTokenToPGToken(tkn *models.Token) *Token {
// TokenToPGToken is a lil util function that takes a gotosocial token and gives back a token for inserting into postgres
func TokenToPGToken(tkn *models.Token) *Token {
now := time.Now()
// For the following, we want to make sure we're not adding a time.Now() to an *empty* ExpiresIn, otherwise that's
@ -236,8 +236,8 @@ func OAuthTokenToPGToken(tkn *models.Token) *Token {
}
}
// PGTokenToOauthToken is a lil util function that takes a postgres token and gives back a gotosocial token
func PGTokenToOauthToken(pgt *Token) *models.Token {
// TokenToOauthToken is a lil util function that takes a postgres token and gives back a gotosocial token
func TokenToOauthToken(pgt *Token) *models.Token {
now := time.Now()
return &models.Token{

View File

@ -128,16 +128,19 @@ func ValidateSignUpReason(reason string, reasonRequired bool) error {
return nil
}
// ValidateDisplayName checks that a requested display name is valid
func ValidateDisplayName(displayName string) error {
// TODO: add some validation logic here -- length, characters, etc
return nil
}
// ValidateNote checks that a given profile/account note/bio is valid
func ValidateNote(note string) error {
// TODO: add some validation logic here -- length, characters, etc
return nil
}
// ValidatePrivacy checks that the desired privacy setting is valid
func ValidatePrivacy(privacy string) error {
// TODO: add some validation logic here -- length, characters, etc
return nil

View File

@ -20,6 +20,7 @@ package testrig
import "github.com/superseriousbusiness/gotosocial/internal/distributor"
// NewTestDistributor returns a Distributor suitable for testing purposes
func NewTestDistributor() distributor.Distributor {
return distributor.New(NewTestLog())
}

View File

@ -26,6 +26,6 @@ import (
// NewTestMediaHandler returns a media handler with the default test config, the default test logger,
// and the given db and storage.
func NewTestMediaHandler(db db.DB, storage storage.Storage) media.MediaHandler {
func NewTestMediaHandler(db db.DB, storage storage.Storage) media.Handler {
return media.New(NewTestConfig(), db, storage, NewTestLog())
}

View File

@ -20,6 +20,7 @@ package testrig
import "github.com/superseriousbusiness/gotosocial/internal/router"
// NewTestRouter returns a Router suitable for testing
func NewTestRouter() router.Router {
r, err := router.New(NewTestConfig(), NewTestLog())
if err != nil {

View File

@ -36,15 +36,15 @@ func NewTestStorage() storage.Storage {
// StandardStorageSetup populates the storage with standard test entries from the given directory.
func StandardStorageSetup(s storage.Storage, relativePath string) {
storedA := NewTestStoredAttachments()
storedA := newTestStoredAttachments()
a := NewTestAttachments()
for k, paths := range storedA {
attachmentInfo, ok := a[k]
if !ok {
panic(fmt.Errorf("key %s not found in test attachments", k))
}
filenameOriginal := paths.original
filenameSmall := paths.small
filenameOriginal := paths.Original
filenameSmall := paths.Small
pathOriginal := attachmentInfo.File.Path
pathSmall := attachmentInfo.Thumbnail.Path
bOriginal, err := os.ReadFile(fmt.Sprintf("%s/%s", relativePath, filenameOriginal))
@ -63,15 +63,15 @@ func StandardStorageSetup(s storage.Storage, relativePath string) {
}
}
storedE := NewTestStoredEmoji()
storedE := newTestStoredEmoji()
e := NewTestEmojis()
for k, paths := range storedE {
emojiInfo, ok := e[k]
if !ok {
panic(fmt.Errorf("key %s not found in test emojis", k))
}
filenameOriginal := paths.original
filenameStatic := paths.static
filenameOriginal := paths.Original
filenameStatic := paths.Static
pathOriginal := emojiInfo.ImagePath
pathStatic := emojiInfo.ImageStaticPath
bOriginal, err := os.ReadFile(fmt.Sprintf("%s/%s", relativePath, filenameOriginal))

View File

@ -700,39 +700,39 @@ func NewTestEmojis() map[string]*gtsmodel.Emoji {
}
type filenames struct {
original string
small string
static string
Original string
Small string
Static string
}
// NewTestStoredAttachments returns a map of filenames, keyed according to which attachment they pertain to.
func NewTestStoredAttachments() map[string]filenames {
// newTestStoredAttachments returns a map of filenames, keyed according to which attachment they pertain to.
func newTestStoredAttachments() map[string]filenames {
return map[string]filenames{
"admin_account_status_1_attachment_1": {
original: "welcome-original.jpeg",
small: "welcome-small.jpeg",
Original: "welcome-original.jpeg",
Small: "welcome-small.jpeg",
},
"local_account_1_status_4_attachment_1": {
original: "trent-original.gif",
small: "trent-small.jpeg",
Original: "trent-original.gif",
Small: "trent-small.jpeg",
},
"local_account_1_unattached_1": {
original: "ohyou-original.jpeg",
small: "ohyou-small.jpeg",
Original: "ohyou-original.jpeg",
Small: "ohyou-small.jpeg",
},
"local_account_1_avatar": {
original: "zork-original.jpeg",
small: "zork-small.jpeg",
Original: "zork-original.jpeg",
Small: "zork-small.jpeg",
},
}
}
// NewtestStoredEmoji returns a map of filenames, keyed according to which emoji they pertain to
func NewTestStoredEmoji() map[string]filenames {
// newTestStoredEmoji returns a map of filenames, keyed according to which emoji they pertain to
func newTestStoredEmoji() map[string]filenames {
return map[string]filenames{
"rainbow": {
original: "rainbow-original.png",
static: "rainbow-static.png",
Original: "rainbow-original.png",
Static: "rainbow-static.png",
},
}
}