*fiddles*

This commit is contained in:
tsmethurst 2021-03-31 18:54:37 +02:00
parent 11b18f986c
commit 48ab34f71a
12 changed files with 221 additions and 74 deletions

View File

@ -20,7 +20,9 @@ package account
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/apimodule"
"github.com/superseriousbusiness/gotosocial/internal/config"
@ -60,8 +62,17 @@ 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, verifyPath, m.accountVerifyGETHandler)
r.AttachHandler(http.MethodPatch, updateCredentialsPath, m.accountUpdateCredentialsPATCHHandler)
r.AttachHandler(http.MethodGet, basePathWithID, m.accountGETHandler)
r.AttachHandler(http.MethodGet, basePathWithID, m.muxHandler)
return nil
}
func (m *accountModule) muxHandler(c *gin.Context) {
ru := c.Request.RequestURI
if strings.HasPrefix(ru, verifyPath) {
m.accountVerifyGETHandler(c)
} else if strings.HasPrefix(ru, updateCredentialsPath) {
m.accountUpdateCredentialsPATCHHandler(c)
} else {
m.accountGETHandler(c)
}
}

View File

@ -127,13 +127,42 @@ func (m *accountModule) accountUpdateCredentialsPATCHHandler(c *gin.Context) {
if form.Locked != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &model.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"": err.Error()})
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
if form.Source != nil {
// TODO: parse source nicely and update
if form.Source.Language != nil {
if err := util.ValidateLanguage(*form.Source.Language); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} else {
if err := m.db.UpdateOneByID(authed.Account.ID, "language", *form.Source.Language, &model.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
}
if form.Source.Sensitive != nil {
if err := m.db.UpdateOneByID(authed.Account.ID, "locked", *form.Locked, &model.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
if form.Source.Privacy != nil {
if err := util.ValidatePrivacy(*form.Source.Privacy); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} else {
if err := m.db.UpdateOneByID(authed.Account.ID, "privacy", *form.Source.Privacy, &model.Account{}); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
}
}
if form.FieldsAttributes != nil {

View File

@ -23,7 +23,6 @@ import (
"context"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
@ -291,9 +290,9 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
defer result.Body.Close()
// TODO: implement proper checks here
//
b, err := ioutil.ReadAll(result.Body)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), `{"error":"not authorized"}`, string(b))
// b, err := ioutil.ReadAll(result.Body)
// assert.NoError(suite.T(), err)
// assert.Equal(suite.T(), `{"error":"not authorized"}`, string(b))
}
func TestAccountUpdateTestSuite(t *testing.T) {

View File

@ -40,22 +40,14 @@ type Config struct {
// FromFile returns a new config from a file, or an error if something goes amiss.
func FromFile(path string) (*Config, error) {
c, err := loadFromFile(path)
if err != nil {
return nil, fmt.Errorf("error creating config: %s", err)
}
return c, nil
}
// Default returns a new config with default values.
// Not yet implemented.
func Default() *Config {
// TODO: find a way of doing this without code repetition, because having to
// repeat all values here and elsewhere is annoying and gonna be prone to mistakes.
return &Config{
DBConfig: &DBConfig{},
TemplateConfig: &TemplateConfig{},
if path != "" {
c, err := loadFromFile(path)
if err != nil {
return nil, fmt.Errorf("error creating config: %s", err)
}
return c, nil
}
return Empty(), nil
}
// Empty just returns an empty config

View File

@ -507,7 +507,39 @@ func (ps *postgresService) GetAvatarForAccountID(avatar *model.MediaAttachment,
// https://docs.joinmastodon.org/methods/accounts/. Note that it's *sensitive* because it's only meant to be exposed to the user
// that the account actually belongs to.
func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotypes.Account, error) {
// we can build this sensitive account easily by first getting the public account....
mastoAccount, err := ps.AccountToMastoPublic(a)
if err != nil {
return nil, err
}
// then adding the Source object to it...
// check pending follow requests aimed at this account
fr := []model.FollowRequest{}
if err := ps.GetFollowRequestsForAccountID(a.ID, &fr); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting follow requests: %s", err)
}
}
var frc int
if fr != nil {
frc = len(fr)
}
mastoAccount.Source = &mastotypes.Source{
Privacy: a.Privacy,
Sensitive: a.Sensitive,
Language: a.Language,
Note: a.Note,
Fields: mastoAccount.Fields,
FollowRequestsCount: frc,
}
return mastoAccount, nil
}
func (ps *postgresService) AccountToMastoPublic(a *model.Account) (*mastotypes.Account, error) {
// count followers
followers := []model.Follow{}
if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil {
@ -588,52 +620,34 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
fields = append(fields, mField)
}
// check pending follow requests aimed at this account
fr := []model.FollowRequest{}
if err := ps.GetFollowRequestsForAccountID(a.ID, &fr); err != nil {
if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting follow requests: %s", err)
}
}
var frc int
if fr != nil {
frc = len(fr)
}
// derive source from fields and other info
source := &mastotypes.Source{
Privacy: a.Privacy,
Sensitive: a.Sensitive,
Language: a.Language,
Note: a.Note,
Fields: fields,
FollowRequestsCount: frc,
var acct string
if a.Domain != "" {
// this is a remote user
acct = fmt.Sprintf("%s@%s", a.Username, a.Domain)
} else {
// this is a local user
acct = a.Username
}
return &mastotypes.Account{
ID: a.ID,
Username: a.Username,
Acct: a.Username, // equivalent to username for local users only, which sensitive always is
Acct: acct,
DisplayName: a.DisplayName,
Locked: a.Locked,
Bot: a.Bot,
CreatedAt: a.CreatedAt.Format(time.RFC3339),
Note: a.Note,
URL: a.URL,
Avatar: aviURL, // TODO: build this url properly using host and protocol from config
AvatarStatic: aviURLStatic, // TODO: build this url properly using host and protocol from config
Header: headerURL, // TODO: build this url properly using host and protocol from config
HeaderStatic: headerURLStatic, // TODO: build this url properly using host and protocol from config
Avatar: aviURL,
AvatarStatic: aviURLStatic,
Header: headerURL,
HeaderStatic: headerURLStatic,
FollowersCount: followersCount,
FollowingCount: followingCount,
StatusesCount: statusesCount,
LastStatusAt: lastStatusAt,
Source: source,
Emojis: nil, // TODO: implement this
Fields: fields,
}, nil
}
func (ps *postgresService) AccountToMastoPublic(account *model.Account) (*mastotypes.Account, error) {
return nil, nil
}

View File

@ -27,8 +27,18 @@ import (
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/action"
"github.com/superseriousbusiness/gotosocial/internal/apimodule"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/app"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/auth"
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/media"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/router"
"github.com/superseriousbusiness/gotosocial/internal/storage"
)
// Run creates and starts a gotosocial server
@ -38,9 +48,45 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
return fmt.Errorf("error creating dbservice: %s", err)
}
// if err := dbService.CreateSchema(ctx); err != nil {
// return fmt.Errorf("error creating dbschema: %s", err)
// }
router, err := router.New(c, log)
if err != nil {
return fmt.Errorf("error creating router: %s", err)
}
storageBackend, err := storage.NewInMem(c, log)
if err != nil {
return fmt.Errorf("error creating storage backend: %s", err)
}
// build backend handlers
mediaHandler := media.New(c, dbService, storageBackend, log)
oauthServer := oauth.New(dbService, log)
// build client api modules
authModule := auth.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, log)
appsModule := app.New(oauthServer, dbService, log)
apiModules := []apimodule.ClientAPIModule{
authModule, // this one has to go first so the other modules use its middleware
accountModule,
appsModule,
}
for _, m := range apiModules {
if err := m.Route(router); err != nil {
return fmt.Errorf("routing error: %s", err)
}
}
gts, err := New(dbService, &cache.MockCache{}, router, federation.New(dbService), c)
if err != nil {
return fmt.Errorf("error creating gotosocial service: %s", err)
}
if err := gts.Start(ctx); err != nil {
return fmt.Errorf("error starting gotosocial service: %s", err)
}
// catch shutdown signals from the operating system
sigs := make(chan os.Signal, 1)
@ -49,8 +95,8 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
log.Infof("received signal %s, shutting down", sig)
// close down all running services in order
if err := dbService.Stop(ctx); err != nil {
return fmt.Errorf("error closing dbservice: %s", err)
if err := gts.Stop(ctx); err != nil {
return fmt.Errorf("error closing gotosocial service: %s", err)
}
log.Info("done! exiting...")

View File

@ -32,6 +32,7 @@ import (
// The logic of stopping and starting the entire server is contained here.
type Gotosocial interface {
Start(context.Context) error
Stop(context.Context) error
}
// New returns a new gotosocial server, initialized with the given configuration.
@ -56,10 +57,19 @@ type gotosocial struct {
config *config.Config
}
// Start starts up the gotosocial server. It is a blocking call, so only call it when
// you're absolutely sure you want to start up the server. If something goes wrong
// while starting the server, then an error will be returned. You can treat this function a
// lot like you would treat http.ListenAndServe()
// Start starts up the gotosocial server. If something goes wrong
// while starting the server, then an error will be returned.
func (gts *gotosocial) Start(ctx context.Context) error {
gts.apiRouter.Start()
return nil
}
func (gts *gotosocial) Stop(ctx context.Context) error {
if err := gts.apiRouter.Stop(ctx); err != nil {
return err
}
if err := gts.db.Stop(ctx); err != nil {
return err
}
return nil
}

View File

@ -59,8 +59,6 @@ func (r *router) Start() {
r.logger.Fatalf("listen: %s", err)
}
}()
// c := &gin.Context{}
// c.Get()
}
// Stop shuts down the router nicely

31
internal/storage/inmem.go Normal file
View File

@ -0,0 +1,31 @@
package storage
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
func NewInMem(c *config.Config, log *logrus.Logger) (Storage, error) {
return &inMemStorage{
stored: make(map[string][]byte),
}, nil
}
type inMemStorage struct {
stored map[string][]byte
}
func (s *inMemStorage) StoreFileAt(path string, data []byte) error {
s.stored[path] = data
return nil
}
func (s *inMemStorage) RetrieveFileFrom(path string) ([]byte, error) {
d, ok := s.stored[path]
if !ok {
return nil, fmt.Errorf("no data found at path %s", path)
}
return d, nil
}

21
internal/storage/local.go Normal file
View File

@ -0,0 +1,21 @@
package storage
import (
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
func NewLocal(c *config.Config, log *logrus.Logger) (Storage, error) {
return &localStorage{}, nil
}
type localStorage struct {
}
func (s *localStorage) StoreFileAt(path string, data []byte) error {
return nil
}
func (s *localStorage) RetrieveFileFrom(path string) ([]byte, error) {
return nil, nil
}

View File

@ -18,16 +18,7 @@
package storage
import "time"
type Storage interface {
StoreFileAt(path string, data []byte) error
RetrieveFileFrom(path string) ([]byte, error)
}
type FileInfo struct {
Data []byte
StorePath string
CreatedAt time.Time
UpdatedAt time.Time
}

View File

@ -137,3 +137,8 @@ func ValidateNote(note string) error {
// TODO: add some validation logic here -- length, characters, etc
return nil
}
func ValidatePrivacy(privacy string) error {
// TODO: add some validation logic here -- length, characters, etc
return nil
}