mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-03 01:38:06 -06:00
worky worky quite contrerky
This commit is contained in:
parent
d09a8a1f05
commit
eb57c95c14
25 changed files with 544 additions and 216 deletions
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/pg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
|
@ -103,7 +104,7 @@ func (suite *AuthTestSuite) SetupTest() {
|
|||
|
||||
log := logrus.New()
|
||||
log.SetLevel(logrus.TraceLevel)
|
||||
db, err := db.NewPostgresService(context.Background(), suite.config, log)
|
||||
db, err := pg.NewPostgresService(context.Background(), suite.config, log)
|
||||
if err != nil {
|
||||
logrus.Panicf("error creating database connection: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) {
|
|||
l := m.log.WithField("func", "OauthTokenMiddleware")
|
||||
l.Trace("entering OauthTokenMiddleware")
|
||||
|
||||
ti, err := m.server.ValidationBearerToken(c.Request)
|
||||
ti, err := m.server.ValidationBearerToken(c.Copy().Request)
|
||||
if err != nil {
|
||||
l.Tracef("could not validate token: %s", err)
|
||||
return
|
||||
|
|
@ -74,4 +74,5 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) {
|
|||
c.Set(oauth.SessionAuthorizedApplication, app)
|
||||
l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedApplication, app)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,16 +20,46 @@ package auth
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type tokenBody struct {
|
||||
ClientID *string `form:"client_id" json:"client_id" xml:"client_id"`
|
||||
ClientSecret *string `form:"client_secret" json:"client_secret" xml:"client_secret"`
|
||||
Code *string `form:"code" json:"code" xml:"code"`
|
||||
GrantType *string `form:"grant_type" json:"grant_type" xml:"grant_type"`
|
||||
RedirectURI *string `form:"redirect_uri" json:"redirect_uri" xml:"redirect_uri"`
|
||||
}
|
||||
|
||||
// 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 *Module) TokenPOSTHandler(c *gin.Context) {
|
||||
l := m.log.WithField("func", "TokenPOSTHandler")
|
||||
l.Trace("entered TokenPOSTHandler")
|
||||
|
||||
form := &tokenBody{}
|
||||
if err := c.ShouldBind(form); err == nil {
|
||||
c.Request.Form = url.Values{}
|
||||
if form.ClientID != nil {
|
||||
c.Request.Form.Set("client_id", *form.ClientID)
|
||||
}
|
||||
if form.ClientSecret != nil {
|
||||
c.Request.Form.Set("client_secret", *form.ClientSecret)
|
||||
}
|
||||
if form.Code != nil {
|
||||
c.Request.Form.Set("code", *form.Code)
|
||||
}
|
||||
if form.GrantType != nil {
|
||||
c.Request.Form.Set("grant_type", *form.GrantType)
|
||||
}
|
||||
if form.RedirectURI != nil {
|
||||
c.Request.Form.Set("redirect_uri", *form.RedirectURI)
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,30 +35,32 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
|
|||
authed, err := oauth.Authed(c, true, true, true, true) // posting new media is serious business so we want *everything*
|
||||
if err != nil {
|
||||
l.Debugf("couldn't auth: %s", err)
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// extract the media create form from the request context
|
||||
l.Tracef("parsing request form: %s", c.Request.Form)
|
||||
var form model.AttachmentRequest
|
||||
form := &model.AttachmentRequest{}
|
||||
if err := c.ShouldBind(&form); err != nil {
|
||||
l.Debugf("could not parse form from request: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"})
|
||||
l.Debugf("error parsing form: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("could not parse form: %s", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Give the fields on the request form a first pass to make sure the request is superficially valid.
|
||||
l.Tracef("validating form %+v", form)
|
||||
if err := validateCreateMedia(&form, m.config.MediaConfig); err != nil {
|
||||
if err := validateCreateMedia(form, m.config.MediaConfig); err != nil {
|
||||
l.Debugf("error validating form: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
mastoAttachment, err := m.processor.MediaCreate(authed, &form)
|
||||
l.Debug("calling processor media create func")
|
||||
mastoAttachment, err := m.processor.MediaCreate(authed, form)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
l.Debugf("error creating attachment: %s", err)
|
||||
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +69,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
|
|||
|
||||
func validateCreateMedia(form *model.AttachmentRequest, config *config.MediaConfig) error {
|
||||
// check there actually is a file attached and it's not size 0
|
||||
if form.File == nil || form.File.Size == 0 {
|
||||
if form.File == nil {
|
||||
return errors.New("no attachment given")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ type Application struct {
|
|||
// And here: https://docs.joinmastodon.org/client/token/
|
||||
type ApplicationCreateRequest struct {
|
||||
// A name for your application
|
||||
ClientName string `form:"client_name" binding:"required"`
|
||||
ClientName string `form:"client_name" json:"client_name" xml:"client_name" binding:"required"`
|
||||
// Where the user should be redirected after authorization.
|
||||
// To display the authorization code to the user instead of redirecting
|
||||
// to a web page, use urn:ietf:wg:oauth:2.0:oob in this parameter.
|
||||
RedirectURIs string `form:"redirect_uris" binding:"required"`
|
||||
RedirectURIs string `form:"redirect_uris" json:"redirect_uris" xml:"redirect_uris" binding:"required"`
|
||||
// Space separated list of scopes. If none is provided, defaults to read.
|
||||
Scopes string `form:"scopes"`
|
||||
Scopes string `form:"scopes" json:"scopes" xml:"scopes"`
|
||||
// A URL to the homepage of your app
|
||||
Website string `form:"website"`
|
||||
Website string `form:"website" json:"website" xml:"website"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import "mime/multipart"
|
|||
// See: https://docs.joinmastodon.org/methods/statuses/media/
|
||||
type AttachmentRequest struct {
|
||||
File *multipart.FileHeader `form:"file" binding:"required"`
|
||||
Description string `form:"description" json:"description" xml:"description"`
|
||||
Focus string `form:"focus" json:"focus" xml:"focus"`
|
||||
Description string `form:"description"`
|
||||
Focus string `form:"focus"`
|
||||
}
|
||||
|
||||
// AttachmentRequest represents the form data parameters submitted by a client during a media update/PUT request.
|
||||
|
|
@ -63,7 +63,7 @@ type Attachment struct {
|
|||
// See https://docs.joinmastodon.org/methods/statuses/media/#focal-points points for more.
|
||||
Meta MediaMeta `json:"meta,omitempty"`
|
||||
// Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load.
|
||||
Description string `json:"description"`
|
||||
Description string `json:"description,omitempty"`
|
||||
// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet.
|
||||
// See https://github.com/woltapp/blurhash
|
||||
Blurhash string `json:"blurhash,omitempty"`
|
||||
|
|
|
|||
8
internal/api/security/extraheaders.go
Normal file
8
internal/api/security/extraheaders.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package security
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// ExtraHeaders adds any additional required headers to the response
|
||||
func (m *Module) ExtraHeaders(c *gin.Context) {
|
||||
c.Header("Server", "Mastodon")
|
||||
}
|
||||
|
|
@ -42,5 +42,6 @@ func New(config *config.Config, log *logrus.Logger) api.ClientModule {
|
|||
// Route attaches security middleware to the given router
|
||||
func (m *Module) Route(s router.Router) error {
|
||||
s.AttachMiddleware(m.FlocBlock)
|
||||
s.AttachMiddleware(m.ExtraHeaders)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
package pg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -37,6 +37,8 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
|
@ -53,7 +55,7 @@ type postgresService struct {
|
|||
|
||||
// NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface.
|
||||
// Under the hood, it uses https://github.com/go-pg/pg to create and maintain a database connection.
|
||||
func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (DB, error) {
|
||||
func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) {
|
||||
opts, err := derivePGOptions(c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create postgres service: %s", err)
|
||||
|
|
@ -95,7 +97,7 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge
|
|||
cancel: cancel,
|
||||
}
|
||||
|
||||
federatingDB := NewFederatingDB(ps, c, log)
|
||||
federatingDB := federation.NewFederatingDB(ps, c, log)
|
||||
ps.federationDB = federatingDB
|
||||
|
||||
// we can confidently return this useable postgres service now
|
||||
|
|
@ -109,8 +111,8 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge
|
|||
// derivePGOptions takes an application config and returns either a ready-to-use *pg.Options
|
||||
// with sensible defaults, or an error if it's not satisfied by the provided config.
|
||||
func derivePGOptions(c *config.Config) (*pg.Options, error) {
|
||||
if strings.ToUpper(c.DBConfig.Type) != DBTypePostgres {
|
||||
return nil, fmt.Errorf("expected db type of %s but got %s", DBTypePostgres, c.DBConfig.Type)
|
||||
if strings.ToUpper(c.DBConfig.Type) != db.DBTypePostgres {
|
||||
return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, c.DBConfig.Type)
|
||||
}
|
||||
|
||||
// validate port
|
||||
|
|
@ -219,7 +221,7 @@ func (ps *postgresService) CreateSchema(ctx context.Context) error {
|
|||
func (ps *postgresService) GetByID(id string, i interface{}) error {
|
||||
if err := ps.conn.Model(i).Where("id = ?", id).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
|
||||
|
|
@ -230,7 +232,7 @@ func (ps *postgresService) GetByID(id string, i interface{}) error {
|
|||
func (ps *postgresService) GetWhere(key string, value interface{}, i interface{}) error {
|
||||
if err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -244,7 +246,7 @@ func (ps *postgresService) GetWhere(key string, value interface{}, i interface{}
|
|||
func (ps *postgresService) GetAll(i interface{}) error {
|
||||
if err := ps.conn.Model(i).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -259,7 +261,7 @@ func (ps *postgresService) Put(i interface{}) error {
|
|||
func (ps *postgresService) Upsert(i interface{}, conflictColumn string) error {
|
||||
if _, err := ps.conn.Model(i).OnConflict(fmt.Sprintf("(%s) DO UPDATE", conflictColumn)).Insert(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -269,7 +271,7 @@ func (ps *postgresService) Upsert(i interface{}, conflictColumn string) error {
|
|||
func (ps *postgresService) UpdateByID(id string, i interface{}) error {
|
||||
if _, err := ps.conn.Model(i).Where("id = ?", id).OnConflict("(id) DO UPDATE").Insert(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -284,7 +286,7 @@ func (ps *postgresService) UpdateOneByID(id string, key string, value interface{
|
|||
func (ps *postgresService) DeleteByID(id string, i interface{}) error {
|
||||
if _, err := ps.conn.Model(i).Where("id = ?", id).Delete(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -294,7 +296,7 @@ func (ps *postgresService) DeleteByID(id string, i interface{}) error {
|
|||
func (ps *postgresService) DeleteWhere(key string, value interface{}, i interface{}) error {
|
||||
if _, err := ps.conn.Model(i).Where("? = ?", pg.Safe(key), value).Delete(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -365,13 +367,13 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.A
|
|||
}
|
||||
if err := ps.conn.Model(user).Where("id = ?", userID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -381,7 +383,7 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *gtsmodel.A
|
|||
func (ps *postgresService) GetLocalAccountByUsername(username string, account *gtsmodel.Account) error {
|
||||
if err := ps.conn.Model(account).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -391,7 +393,7 @@ func (ps *postgresService) GetLocalAccountByUsername(username string, account *g
|
|||
func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, followRequests *[]gtsmodel.FollowRequest) error {
|
||||
if err := ps.conn.Model(followRequests).Where("target_account_id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -401,7 +403,7 @@ func (ps *postgresService) GetFollowRequestsForAccountID(accountID string, follo
|
|||
func (ps *postgresService) GetFollowingByAccountID(accountID string, following *[]gtsmodel.Follow) error {
|
||||
if err := ps.conn.Model(following).Where("account_id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -411,7 +413,7 @@ func (ps *postgresService) GetFollowingByAccountID(accountID string, following *
|
|||
func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *[]gtsmodel.Follow) error {
|
||||
if err := ps.conn.Model(followers).Where("target_account_id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -421,7 +423,7 @@ func (ps *postgresService) GetFollowersByAccountID(accountID string, followers *
|
|||
func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error {
|
||||
if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -438,7 +440,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
|
|||
}
|
||||
if err := q.Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -448,7 +450,7 @@ func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuse
|
|||
func (ps *postgresService) GetLastStatusForAccountID(accountID string, status *gtsmodel.Status) error {
|
||||
if err := ps.conn.Model(status).Order("created_at DESC").Limit(1).Where("account_id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -574,18 +576,18 @@ func (ps *postgresService) GetHeaderForAccountID(header *gtsmodel.MediaAttachmen
|
|||
acct := >smodel.Account{}
|
||||
if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if acct.HeaderMediaAttachmentID == "" {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
|
||||
if err := ps.conn.Model(header).Where("id = ?", acct.HeaderMediaAttachmentID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -596,18 +598,18 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *gtsmodel.MediaAttachmen
|
|||
acct := >smodel.Account{}
|
||||
if err := ps.conn.Model(acct).Where("id = ?", accountID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if acct.AvatarMediaAttachmentID == "" {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
|
||||
if err := ps.conn.Model(avatar).Where("id = ?", acct.AvatarMediaAttachmentID).Select(); err != nil {
|
||||
if err == pg.ErrNoRows {
|
||||
return ErrNoEntries{}
|
||||
return db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -645,7 +647,7 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
|
|||
if err := ps.conn.Model(targetUser).Where("account_id = ?", targetAccount.ID).Select(); err != nil {
|
||||
l.Debug("target user could not be selected")
|
||||
if err == pg.ErrNoRows {
|
||||
return false, ErrNoEntries{}
|
||||
return false, db.ErrNoEntries{}
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db_test
|
||||
|
||||
// TODO: write tests for postgres
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
package federation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -26,28 +26,33 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
// FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface.
|
||||
// It doesn't care what the underlying implementation of the DB interface is, as long as it works.
|
||||
type federatingDB struct {
|
||||
locks *sync.Map
|
||||
db DB
|
||||
config *config.Config
|
||||
log *logrus.Logger
|
||||
locks *sync.Map
|
||||
db db.DB
|
||||
config *config.Config
|
||||
log *logrus.Logger
|
||||
typeConverter typeutils.TypeConverter
|
||||
}
|
||||
|
||||
func NewFederatingDB(db DB, config *config.Config, log *logrus.Logger) pub.Database {
|
||||
func NewFederatingDB(db db.DB, config *config.Config, log *logrus.Logger) pub.Database {
|
||||
return &federatingDB{
|
||||
locks: new(sync.Map),
|
||||
db: db,
|
||||
config: config,
|
||||
log: log,
|
||||
locks: new(sync.Map),
|
||||
db: db,
|
||||
config: config,
|
||||
log: log,
|
||||
typeConverter: typeutils.NewConverter(config, db),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +112,7 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con
|
|||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "InboxContains",
|
||||
"id": id.String(),
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
l.Debug("entering INBOXCONTAINS function")
|
||||
|
|
@ -116,25 +121,30 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con
|
|||
return false, fmt.Errorf("%s is not an inbox URI", inbox.String())
|
||||
}
|
||||
|
||||
if !util.IsStatusesPath(id) {
|
||||
return false, fmt.Errorf("%s is not a status URI", id.String())
|
||||
activityI := c.Value(util.APActivity)
|
||||
if activityI == nil {
|
||||
return false, fmt.Errorf("no activity was set for id %s", id.String())
|
||||
}
|
||||
_, statusID, err := util.ParseStatusesPath(inbox)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("status URI %s was not parseable: %s", id.String(), err)
|
||||
activity, ok := activityI.(pub.Activity)
|
||||
if !ok || activity == nil {
|
||||
return false, fmt.Errorf("could not parse contextual activity for id %s", id.String())
|
||||
}
|
||||
|
||||
if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
// we don't have it
|
||||
return false, nil
|
||||
}
|
||||
// actual error
|
||||
return false, fmt.Errorf("error getting status from db: %s", err)
|
||||
}
|
||||
l.Debugf("activity type %s for id %s", activity.GetTypeName(), id.String())
|
||||
|
||||
// we must have it
|
||||
return true, nil
|
||||
return false, nil
|
||||
|
||||
// if err := f.db.GetByID(statusID, >smodel.Status{}); err != nil {
|
||||
// if _, ok := err.(db.ErrNoEntries); ok {
|
||||
// // we don't have it
|
||||
// return false, nil
|
||||
// }
|
||||
// // actual error
|
||||
// return false, fmt.Errorf("error getting status from db: %s", err)
|
||||
// }
|
||||
|
||||
// // we must have it
|
||||
// return true, nil
|
||||
}
|
||||
|
||||
// GetInbox returns the first ordered collection page of the outbox at
|
||||
|
|
@ -142,7 +152,7 @@ func (f *federatingDB) InboxContains(c context.Context, inbox, id *url.URL) (con
|
|||
//
|
||||
// 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
|
||||
return streams.NewActivityStreamsOrderedCollectionPage(), nil
|
||||
}
|
||||
|
||||
// SetInbox saves the inbox value given from GetInbox, with new items
|
||||
|
|
@ -161,17 +171,18 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
|||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Owns",
|
||||
"id": id.String(),
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
l.Debug("entering OWNS function")
|
||||
|
||||
// if the id host isn't this instance host, we don't own this IRI
|
||||
if id.Host != f.config.Host {
|
||||
l.Debugf("we DO NOT own activity because the host is %s not %s", id.Host, f.config.Host)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// apparently we own it, so what *is* it?
|
||||
// apparently it belongs to this host, so what *is* it?
|
||||
|
||||
// check if it's a status, eg /users/example_username/statuses/SOME_UUID_OF_A_STATUS
|
||||
if util.IsStatusesPath(id) {
|
||||
|
|
@ -180,13 +191,14 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
|||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||
}
|
||||
if err := f.db.GetWhere("uri", uid, >smodel.Status{}); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
// there are no entries for this status
|
||||
return false, nil
|
||||
}
|
||||
// an actual error happened
|
||||
return false, fmt.Errorf("database error fetching status with id %s: %s", uid, err)
|
||||
}
|
||||
l.Debug("we DO own this")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
@ -197,13 +209,14 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
|||
return false, fmt.Errorf("error parsing statuses path for url %s: %s", id.String(), err)
|
||||
}
|
||||
if err := f.db.GetLocalAccountByUsername(username, >smodel.Account{}); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
// there are no entries for this username
|
||||
return false, nil
|
||||
}
|
||||
// an actual error happened
|
||||
return false, fmt.Errorf("database error fetching account with username %s: %s", username, err)
|
||||
}
|
||||
l.Debug("we DO own this")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +229,7 @@ func (f *federatingDB) Owns(c context.Context, id *url.URL) (bool, error) {
|
|||
func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "ActorForOutbox",
|
||||
"func": "ActorForOutbox",
|
||||
"inboxIRI": outboxIRI.String(),
|
||||
},
|
||||
)
|
||||
|
|
@ -227,7 +240,7 @@ func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (ac
|
|||
}
|
||||
acct := >smodel.Account{}
|
||||
if err := f.db.GetWhere("outbox_uri", outboxIRI.String(), acct); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", outboxIRI.String())
|
||||
}
|
||||
return nil, fmt.Errorf("db error searching for actor with outbox %s", outboxIRI.String())
|
||||
|
|
@ -241,7 +254,7 @@ func (f *federatingDB) ActorForOutbox(c context.Context, outboxIRI *url.URL) (ac
|
|||
func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "ActorForInbox",
|
||||
"func": "ActorForInbox",
|
||||
"inboxIRI": inboxIRI.String(),
|
||||
},
|
||||
)
|
||||
|
|
@ -252,7 +265,7 @@ func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (acto
|
|||
}
|
||||
acct := >smodel.Account{}
|
||||
if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
|
||||
}
|
||||
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
|
||||
|
|
@ -267,7 +280,7 @@ func (f *federatingDB) ActorForInbox(c context.Context, inboxIRI *url.URL) (acto
|
|||
func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "OutboxForInbox",
|
||||
"func": "OutboxForInbox",
|
||||
"inboxIRI": inboxIRI.String(),
|
||||
},
|
||||
)
|
||||
|
|
@ -278,7 +291,7 @@ func (f *federatingDB) OutboxForInbox(c context.Context, inboxIRI *url.URL) (out
|
|||
}
|
||||
acct := >smodel.Account{}
|
||||
if err := f.db.GetWhere("inbox_uri", inboxIRI.String(), acct); err != nil {
|
||||
if _, ok := err.(ErrNoEntries); ok {
|
||||
if _, ok := err.(db.ErrNoEntries); ok {
|
||||
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", inboxIRI.String())
|
||||
}
|
||||
return nil, fmt.Errorf("db error searching for actor with inbox %s", inboxIRI.String())
|
||||
|
|
@ -294,7 +307,7 @@ func (f *federatingDB) Exists(c context.Context, id *url.URL) (exists bool, err
|
|||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Exists",
|
||||
"id": id.String(),
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
l.Debug("entering EXISTS function")
|
||||
|
|
@ -309,10 +322,19 @@ func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, er
|
|||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Get",
|
||||
"id": id.String(),
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
l.Debug("entering GET function")
|
||||
|
||||
if util.IsUserPath(id) {
|
||||
acct := >smodel.Account{}
|
||||
if err := f.db.GetWhere("uri", id.String(), acct); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.typeConverter.AccountToAS(acct)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -331,11 +353,37 @@ func (f *federatingDB) Get(c context.Context, id *url.URL) (value vocab.Type, er
|
|||
func (f *federatingDB) Create(c context.Context, asType vocab.Type) error {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Create",
|
||||
"func": "Create",
|
||||
"asType": asType.GetTypeName(),
|
||||
},
|
||||
)
|
||||
l.Debugf("received CREATE asType %+v", asType)
|
||||
|
||||
|
||||
|
||||
|
||||
switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) {
|
||||
case gtsmodel.ActivityStreamsCreate:
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to create")
|
||||
}
|
||||
|
||||
object := create.GetActivityStreamsObject()
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
switch gtsmodel.ActivityStreamsObject(objectIter.GetType().GetTypeName()) {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
note := objectIter.GetActivityStreamsNote()
|
||||
status, err := f.typeConverter.ASStatusToStatus(note)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting note to status: %s", err)
|
||||
}
|
||||
if err := f.db.Put(status); err != nil {
|
||||
return fmt.Errorf("database error inserting status: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +399,7 @@ func (f *federatingDB) Create(c context.Context, asType vocab.Type) error {
|
|||
func (f *federatingDB) Update(c context.Context, asType vocab.Type) error {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Update",
|
||||
"func": "Update",
|
||||
"asType": asType.GetTypeName(),
|
||||
},
|
||||
)
|
||||
|
|
@ -369,7 +417,7 @@ func (f *federatingDB) Delete(c context.Context, id *url.URL) error {
|
|||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Delete",
|
||||
"id": id.String(),
|
||||
"id": id.String(),
|
||||
},
|
||||
)
|
||||
l.Debugf("received DELETE id %s", id.String())
|
||||
|
|
@ -403,7 +451,7 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err
|
|||
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "NewID",
|
||||
"func": "NewID",
|
||||
"asType": t.GetTypeName(),
|
||||
},
|
||||
)
|
||||
|
|
@ -16,6 +16,6 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package db
|
||||
package federation
|
||||
|
||||
// TODO: write tests for pgfed
|
||||
|
|
@ -41,7 +41,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/s2s/webfinger"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/security"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/pg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
|
|
@ -78,7 +78,7 @@ var models []interface{} = []interface{}{
|
|||
|
||||
// Run creates and starts a gotosocial server
|
||||
var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
|
||||
dbService, err := db.NewPostgresService(ctx, c, log)
|
||||
dbService, err := pg.NewPostgresService(ctx, c, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating dbservice: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/pg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/storage"
|
||||
)
|
||||
|
|
@ -78,7 +79,7 @@ func (suite *MediaTestSuite) SetupSuite() {
|
|||
}
|
||||
suite.config = c
|
||||
// use an actual database for this, because it's just easier than mocking one out
|
||||
database, err := db.NewPostgresService(context.Background(), c, log)
|
||||
database, err := pg.NewPostgresService(context.Background(), c, log)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db/pg"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/oauth2/v4/models"
|
||||
)
|
||||
|
|
@ -62,7 +63,7 @@ func (suite *PgClientStoreTestSuite) SetupTest() {
|
|||
Database: "postgres",
|
||||
ApplicationName: "gotosocial",
|
||||
}
|
||||
db, err := db.NewPostgresService(context.Background(), c, log)
|
||||
db, err := pg.NewPostgresService(context.Background(), c, log)
|
||||
if err != nil {
|
||||
logrus.Panicf("error creating database connection: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/memstore"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -123,6 +124,14 @@ func New(config *config.Config, logger *logrus.Logger) (Router, error) {
|
|||
|
||||
// create the actual engine here -- this is the core request routing handler for gts
|
||||
engine := gin.Default()
|
||||
engine.Use(cors.New(cors.Config{
|
||||
AllowAllOrigins: true,
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
engine.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
|
||||
// create a new session store middleware
|
||||
store, err := sessionStore()
|
||||
|
|
@ -143,10 +152,10 @@ func New(config *config.Config, logger *logrus.Logger) (Router, error) {
|
|||
// create the actual http server here
|
||||
s := &http.Server{
|
||||
Handler: engine,
|
||||
ReadTimeout: 1 * time.Second,
|
||||
WriteTimeout: 1 * time.Second,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 5 * time.Second,
|
||||
IdleTimeout: 30 * time.Second,
|
||||
ReadHeaderTimeout: 2 * time.Second,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
}
|
||||
var m *autocert.Manager
|
||||
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ func (c *controller) NewTransport(pubKeyID string, privkey crypto.PrivateKey) (p
|
|||
getHeaders := []string{"(request-target)", "host", "date"}
|
||||
postHeaders := []string{"(request-target)", "host", "date", "digest"}
|
||||
|
||||
getSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature)
|
||||
getSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, getHeaders, httpsig.Signature, 120)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating get signer: %s", err)
|
||||
}
|
||||
|
||||
postSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature)
|
||||
postSigner, _, err := httpsig.NewSigner(prefs, digestAlgo, postHeaders, httpsig.Signature, 120)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating post signer: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
)
|
||||
|
|
@ -56,6 +57,79 @@ func extractName(i withDisplayName) (string, error) {
|
|||
return "", errors.New("activityStreamsName not found")
|
||||
}
|
||||
|
||||
func extractInReplyToURI(i withInReplyTo) (*url.URL, error) {
|
||||
inReplyToProp := i.GetActivityStreamsInReplyTo()
|
||||
for i := inReplyToProp.Begin(); i != inReplyToProp.End(); i = i.Next() {
|
||||
if i.IsIRI() {
|
||||
if i.GetIRI() != nil {
|
||||
return i.GetIRI(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("couldn't find iri for in reply to")
|
||||
}
|
||||
|
||||
func extractTos(i withTo) ([]*url.URL, error) {
|
||||
to := []*url.URL{}
|
||||
toProp := i.GetActivityStreamsTo()
|
||||
for i := toProp.Begin(); i != toProp.End(); i = i.Next() {
|
||||
if i.IsIRI() {
|
||||
if i.GetIRI() != nil {
|
||||
to = append(to, i.GetIRI())
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(to) == 0 {
|
||||
return nil, errors.New("found no to entries")
|
||||
}
|
||||
return to, nil
|
||||
}
|
||||
|
||||
func extractCCs(i withCC) ([]*url.URL, error) {
|
||||
cc := []*url.URL{}
|
||||
ccProp := i.GetActivityStreamsCc()
|
||||
for i := ccProp.Begin(); i != ccProp.End(); i = i.Next() {
|
||||
if i.IsIRI() {
|
||||
if i.GetIRI() != nil {
|
||||
cc = append(cc, i.GetIRI())
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(cc) == 0 {
|
||||
return nil, errors.New("found no cc entries")
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func extractAttributedTo(i withAttributedTo) (*url.URL, error) {
|
||||
attributedToProp := i.GetActivityStreamsAttributedTo()
|
||||
for aIter := attributedToProp.Begin(); aIter != attributedToProp.End(); aIter = aIter.Next() {
|
||||
if aIter.IsIRI() {
|
||||
if aIter.GetIRI() != nil {
|
||||
return aIter.GetIRI(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("couldn't find iri for attributed to")
|
||||
}
|
||||
|
||||
func extractPublished(i withPublished) (time.Time, error) {
|
||||
publishedProp := i.GetActivityStreamsPublished()
|
||||
if publishedProp == nil {
|
||||
return time.Time{}, errors.New("published prop was nil")
|
||||
}
|
||||
|
||||
if !publishedProp.IsXMLSchemaDateTime() {
|
||||
return time.Time{}, errors.New("published prop was not date time")
|
||||
}
|
||||
|
||||
t := publishedProp.Get()
|
||||
if t.IsZero() {
|
||||
return time.Time{}, errors.New("published time was zero")
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// extractIconURL extracts a URL to a supported image file from something like:
|
||||
// "icon": {
|
||||
// "mediaType": "image/jpeg",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import "github.com/go-fed/activity/streams/vocab"
|
|||
type Accountable interface {
|
||||
withJSONLDId
|
||||
withGetTypeName
|
||||
|
||||
withPreferredUsername
|
||||
withIcon
|
||||
withDisplayName
|
||||
|
|
@ -40,6 +41,27 @@ type Accountable interface {
|
|||
withFeatured
|
||||
}
|
||||
|
||||
// Statusable represents the minimum activitypub interface for representing a 'status'.
|
||||
// This interface is fulfilled by: Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile
|
||||
type Statusable interface {
|
||||
withJSONLDId
|
||||
withGetTypeName
|
||||
|
||||
withSummary
|
||||
withInReplyTo
|
||||
withPublished
|
||||
withURL
|
||||
withAttributedTo
|
||||
withTo
|
||||
withCC
|
||||
withSensitive
|
||||
withConversation
|
||||
withContent
|
||||
withAttachment
|
||||
withTag
|
||||
withReplies
|
||||
}
|
||||
|
||||
type withJSONLDId interface {
|
||||
GetJSONLDId() vocab.JSONLDIdProperty
|
||||
}
|
||||
|
|
@ -99,3 +121,47 @@ type withFollowers interface {
|
|||
type withFeatured interface {
|
||||
GetTootFeatured() vocab.TootFeaturedProperty
|
||||
}
|
||||
|
||||
type withAttributedTo interface {
|
||||
GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
|
||||
}
|
||||
|
||||
type withAttachment interface {
|
||||
GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty
|
||||
}
|
||||
|
||||
type withTo interface {
|
||||
GetActivityStreamsTo() vocab.ActivityStreamsToProperty
|
||||
}
|
||||
|
||||
type withInReplyTo interface {
|
||||
GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
|
||||
}
|
||||
|
||||
type withCC interface {
|
||||
GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
|
||||
}
|
||||
|
||||
type withSensitive interface {
|
||||
// TODO
|
||||
}
|
||||
|
||||
type withConversation interface {
|
||||
// TODO
|
||||
}
|
||||
|
||||
type withContent interface {
|
||||
GetActivityStreamsContent() vocab.ActivityStreamsContentProperty
|
||||
}
|
||||
|
||||
type withPublished interface {
|
||||
GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
|
||||
}
|
||||
|
||||
type withTag interface {
|
||||
GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
|
||||
}
|
||||
|
||||
type withReplies interface {
|
||||
GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty
|
||||
}
|
||||
|
|
@ -157,3 +157,49 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable) (*gtsmode
|
|||
|
||||
return acct, nil
|
||||
}
|
||||
|
||||
func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) {
|
||||
status := >smodel.Status{}
|
||||
|
||||
uriProp := statusable.GetJSONLDId()
|
||||
if uriProp == nil || !uriProp.IsIRI() {
|
||||
return nil, errors.New("no id property found, or id was not an iri")
|
||||
}
|
||||
status.URI = uriProp.GetIRI().String()
|
||||
|
||||
cw, err := extractSummary(statusable)
|
||||
if err == nil && cw != "" {
|
||||
status.ContentWarning = cw
|
||||
}
|
||||
|
||||
inReplyToURI, err := extractInReplyToURI(statusable)
|
||||
if err == nil {
|
||||
inReplyToStatus := >smodel.Status{}
|
||||
if err := c.db.GetWhere("uri", inReplyToURI.String(), inReplyToStatus); err == nil {
|
||||
status.InReplyToID = inReplyToStatus.ID
|
||||
}
|
||||
}
|
||||
|
||||
published, err := extractPublished(statusable)
|
||||
if err == nil {
|
||||
status.CreatedAt = published
|
||||
}
|
||||
|
||||
statusURL, err := extractURL(statusable)
|
||||
if err == nil {
|
||||
status.URL = statusURL.String()
|
||||
}
|
||||
|
||||
attributedTo, err := extractAttributedTo(statusable)
|
||||
if err != nil {
|
||||
return nil, errors.New("attributedTo was empty")
|
||||
}
|
||||
|
||||
statusOwner := >smodel.Status{}
|
||||
if err := c.db.GetWhere("uri", attributedTo.String(), statusOwner); err != nil {
|
||||
return nil, fmt.Errorf("cannot attribute %s to an account we know: %s", attributedTo.String(), err)
|
||||
}
|
||||
status.AccountID = statusOwner.ID
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ type TypeConverter interface {
|
|||
|
||||
// ASPersonToAccount converts a remote account/person/application representation into a gts model account
|
||||
ASRepresentationToAccount(accountable Accountable) (*gtsmodel.Account, error)
|
||||
// ASStatus converts a remote activitystreams 'status' representation into a gts model status.
|
||||
ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error)
|
||||
|
||||
/*
|
||||
INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL
|
||||
|
|
|
|||
|
|
@ -200,15 +200,15 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
|
|||
// icon
|
||||
// Used as profile avatar.
|
||||
if a.AvatarMediaAttachmentID != "" {
|
||||
iconProperty := streams.NewActivityStreamsIconProperty()
|
||||
|
||||
iconImage := streams.NewActivityStreamsImage()
|
||||
|
||||
avatar := >smodel.MediaAttachment{}
|
||||
if err := c.db.GetByID(a.AvatarMediaAttachmentID, avatar); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iconProperty := streams.NewActivityStreamsIconProperty()
|
||||
|
||||
iconImage := streams.NewActivityStreamsImage()
|
||||
|
||||
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
||||
mediaType.Set(avatar.File.ContentType)
|
||||
iconImage.SetActivityStreamsMediaType(mediaType)
|
||||
|
|
@ -228,15 +228,15 @@ func (c *converter) AccountToAS(a *gtsmodel.Account) (vocab.ActivityStreamsPerso
|
|||
// image
|
||||
// Used as profile header.
|
||||
if a.HeaderMediaAttachmentID != "" {
|
||||
headerProperty := streams.NewActivityStreamsImageProperty()
|
||||
|
||||
headerImage := streams.NewActivityStreamsImage()
|
||||
|
||||
header := >smodel.MediaAttachment{}
|
||||
if err := c.db.GetByID(a.HeaderMediaAttachmentID, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
headerProperty := streams.NewActivityStreamsImageProperty()
|
||||
|
||||
headerImage := streams.NewActivityStreamsImage()
|
||||
|
||||
mediaType := streams.NewActivityStreamsMediaTypeProperty()
|
||||
mediaType.Set(header.File.ContentType)
|
||||
headerImage.SetActivityStreamsMediaType(mediaType)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue