Added user settings form and server allow flags for auto follow and reply collection updates.

This commit is contained in:
Nick Gerakines 2020-03-15 16:43:59 -04:00
parent 3d75b3b9b0
commit c387363b6e
15 changed files with 199 additions and 42 deletions

View File

@ -24,6 +24,7 @@ type Agent struct {
DataStorage storage.Storage
HTTPClient common.HTTPClient
AssetStorageConfig config.AssetStorageConfig
FedConfig config.FedConfig
}
func (a Agent) HandleImage(ctx context.Context, location string) (storage.ImageAsset, error) {

32
config/fed.go Normal file
View File

@ -0,0 +1,32 @@
package config
import (
"github.com/urfave/cli/v2"
)
type FedConfig struct {
AllowReplyCollectionUpdates bool
AllowAutoAcceptFollowers bool
}
var AllowReplyCollectionUpdatesFlag = cli.BoolFlag{
Name: "allow-reply-collection-updates",
Usage: "Allow users to turn on object reply collection updates.",
EnvVars: []string{"ALLOW_REPLY_COLLECTION_UPDATES"},
Value: false,
}
var AllowAutoAcceptFollowersFlag = cli.BoolFlag{
Name: "allow-auto-accept-followers",
Usage: "Allow users to turn on automatically accepting follow requests.",
EnvVars: []string{"ALLOW_AUTO_ACCEPT_FOLLOWERS"},
Value: true,
}
func NewFedConfig(cliCtx *cli.Context) (FedConfig, error) {
cfg := FedConfig{
AllowReplyCollectionUpdates: cliCtx.Bool("allow-reply-collection-updates"),
AllowAutoAcceptFollowers: cliCtx.Bool("allow-auto-accept-followers"),
}
return cfg, nil
}

View File

@ -21,6 +21,7 @@ type Crawler struct {
MaxDepth int
AssetStorage asset.Storage
AssetStorageConfig config.AssetStorageConfig
FedConfig config.FedConfig
}
type Signer interface {

View File

@ -21,9 +21,10 @@ type assetDownload struct {
assetStorage asset.Storage
httpClient common.HTTPClient
assetStorageConfig config.AssetStorageConfig
fedConfig config.FedConfig
}
func NewAssetDownloadJob(logger *zap.Logger, queue common.StringQueue, storage storage.Storage, assetStorage asset.Storage, config config.AssetStorageConfig) Job {
func NewAssetDownloadJob(logger *zap.Logger, queue common.StringQueue, storage storage.Storage, assetStorage asset.Storage, config config.AssetStorageConfig, fedConfig config.FedConfig) Job {
return &assetDownload{
logger: logger,
queue: queue,
@ -31,6 +32,7 @@ func NewAssetDownloadJob(logger *zap.Logger, queue common.StringQueue, storage s
assetStorage: assetStorage,
httpClient: common.DefaultHTTPClient(),
assetStorageConfig: config,
fedConfig: fedConfig,
}
}
@ -76,6 +78,7 @@ func (job *assetDownload) work() error {
DataStorage: job.storage,
HTTPClient: job.httpClient,
AssetStorageConfig: job.assetStorageConfig,
FedConfig: job.fedConfig,
}
_, err = a.HandleImage(job.ctx, work)
if err != nil {

View File

@ -25,9 +25,10 @@ type crawl struct {
httpClient common.HTTPClient
assetStorage asset.Storage
assetStorageConfig config.AssetStorageConfig
fedConfig config.FedConfig
}
func NewCrawlWorker(logger *zap.Logger, queue common.StringQueue, storage storage.Storage, assetStorage asset.Storage, config config.AssetStorageConfig) Job {
func NewCrawlWorker(logger *zap.Logger, queue common.StringQueue, storage storage.Storage, assetStorage asset.Storage, config config.AssetStorageConfig, fedConfig config.FedConfig) Job {
return &crawl{
logger: logger,
queue: queue,
@ -35,6 +36,7 @@ func NewCrawlWorker(logger *zap.Logger, queue common.StringQueue, storage storag
assetStorage: assetStorage,
httpClient: common.DefaultHTTPClient(),
assetStorageConfig: config,
fedConfig: fedConfig,
}
}
@ -96,6 +98,7 @@ func (job *crawl) work() error {
MaxDepth: fed.CrawlerDefaultMaxCount,
AssetStorage: job.assetStorage,
AssetStorageConfig: job.assetStorageConfig,
FedConfig: job.fedConfig,
}
if _, _, err = crawler.Start(user, parts[1]); err != nil {
job.logger.Warn("error crawling", zap.Error(err), zap.String("location", parts[1]))

View File

@ -7,6 +7,7 @@ import (
"go.uber.org/zap"
"github.com/ngerakines/tavern/common"
"github.com/ngerakines/tavern/config"
"github.com/ngerakines/tavern/fed"
"github.com/ngerakines/tavern/storage"
)
@ -18,14 +19,16 @@ type webfinger struct {
queue common.StringQueue
storage storage.Storage
httpClient common.HTTPClient
fedConfig config.FedConfig
}
func NewWebFingerWorker(logger *zap.Logger, queue common.StringQueue, storage storage.Storage) Job {
func NewWebFingerWorker(logger *zap.Logger, queue common.StringQueue, storage storage.Storage, fedConfig config.FedConfig) Job {
return &webfinger{
logger: logger,
queue: queue,
storage: storage,
httpClient: common.DefaultHTTPClient(),
fedConfig: fedConfig,
}
}

View File

@ -3,7 +3,6 @@ package storage
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/gofrs/uuid"
@ -140,8 +139,6 @@ func (s pgStorage) keyedCount(ctx context.Context, query string, args ...interfa
}
func (s pgStorage) keysToUUID(ctx context.Context, query string, args ...interface{}) (map[string]uuid.UUID, error) {
fmt.Println(query)
fmt.Println(args...)
results := make(map[string]uuid.UUID)
rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {

View File

@ -24,25 +24,28 @@ type UserStorage interface {
GetUserByName(ctx context.Context, name string) (*User, error)
GetUserBySession(ctx context.Context, session sessions.Session) (*User, error)
UpdateUserLastAuth(ctx context.Context, userID uuid.UUID) error
UpdateUserAutoAcceptFollowers(ctx context.Context, userID uuid.UUID, value bool) error
UpdateUserReplyCollectionUpdates(ctx context.Context, userID uuid.UUID, value bool) error
}
type User struct {
ID uuid.UUID
Email string
Password []byte
CreatedAt time.Time
UpdatedAt time.Time
LastAuthAt time.Time
Location string
MuteEmail bool
Locale string
PrivateKey string
PublicKey string
Name string
DisplayName string
About string
AcceptFollowers bool
ActorID uuid.UUID
ID uuid.UUID
Email string
Password []byte
CreatedAt time.Time
UpdatedAt time.Time
LastAuthAt time.Time
Location string
MuteEmail bool
Locale string
PrivateKey string
PublicKey string
Name string
DisplayName string
About string
AcceptFollowers bool
ActorID uuid.UUID
ReplyCollectionUpdates bool
}
func (u *User) GetPrivateKey() (*rsa.PrivateKey, error) {
@ -79,6 +82,7 @@ var userFields = []string{
"about",
"accept_followers",
"actor_id",
"reply_collection_updates",
}
func (s pgStorage) GetUserBySession(ctx context.Context, session sessions.Session) (*User, error) {
@ -118,7 +122,8 @@ func (s pgStorage) GetUserWithQuery(ctx context.Context, query string, args ...i
&user.DisplayName,
&user.About,
&user.AcceptFollowers,
&user.ActorID)
&user.ActorID,
&user.ReplyCollectionUpdates)
if err != nil {
if err == sql.ErrNoRows {
return nil, errors.NewUserNotFoundError(err)
@ -160,3 +165,15 @@ func (s pgStorage) UpdateUserLastAuth(ctx context.Context, userID uuid.UUID) err
_, err := s.db.ExecContext(ctx, "UPDATE users SET last_auth_at = $2, updated_at = $2 WHERE id = $1", userID, now)
return errors.WrapUpdateUserFailedError(err)
}
func (s pgStorage) UpdateUserAutoAcceptFollowers(ctx context.Context, userID uuid.UUID, value bool) error {
now := s.now()
_, err := s.db.ExecContext(ctx, "UPDATE users SET accept_followers = $3, updated_at = $2 WHERE id = $1", userID, now, value)
return errors.WrapUpdateUserFailedError(err)
}
func (s pgStorage) UpdateUserReplyCollectionUpdates(ctx context.Context, userID uuid.UUID, value bool) error {
now := s.now()
_, err := s.db.ExecContext(ctx, "UPDATE users SET reply_collection_updates = $3, updated_at = $2 WHERE id = $1", userID, now, value)
return errors.WrapUpdateUserFailedError(err)
}

View File

@ -1,11 +1,68 @@
{{define "head"}}{{end}}
{{define "footer_script"}}{{end}}
{{define "content"}}
{{ $t := .Trans }}
{{ template "dashboard_menu" "configure" }}
<div class="row pt-3">
<div class="col">
<h1>Configure</h1>
<h1 class="pb-1">Settings</h1>
<form method="POST" action="{{ url "configure_user" }}">
<div class="form-group">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="checked"
id="userReplyCollectionUpdates" name="reply_collection_updates"
{{ if .user_config.reply_collection_updates }}checked="checked"{{ end }}
{{ if eq .fed_config.reply_collection_updates false }}disabled{{ end }}
>
<label class="form-check-label" for="userReplyCollectionUpdates">
Enable Reply Collection Updates
</label>
<small class="form-text text-muted">
When this feature is enabled, an <code>Update</code> activity will be sent to followers when
a reply is accepted and added to the object replies collection of the note. This has the
potential to generate a lot of activity.
</small>
{{ if eq .fed_config.reply_collection_updates false }}
<small class="form-text text-danger">
This has been disabled at the server level. Please contact the instance admin to allow
this feature to be enabled.
</small>
{{ end }}
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="checked"
id="userAutoAcceptFollowers" name="auto_accept_followers"
{{ if .user_config.auto_accept_followers }}checked="checked"{{ end }}
{{ if eq .fed_config.auto_accept_followers false }} disabled{{ end }}>
<label class="form-check-label" for="userAutoAcceptFollowers">
Auto Accept Followers
</label>
<small class="form-text text-muted">
When this feature is enabled, all <code>Follow</code> requests sent to the user will be
immediately accepted.
</small>
{{ if eq .fed_config.auto_accept_followers false }}
<small class="form-text text-danger">
This has been disabled at the server level. Please contact the instance admin to allow
this feature to be enabled.
</small>
{{ end }}
</div>
</div>
<input type="submit" class="btn btn-dark" name="submit" value="Submit"/>
</form>
</div>
</div>
<div class="row pt-3">
<div class="col">
<h1>Utilities</h1>
<p class="lead">
Several utilities are provided to allow manually trigger crawl jobs for actors and objects.
</p>
<ul>
<li>
<a href="{{ url "utilities" }}">Crawl Actor</a>
<a href="{{ url "utilities" }}">Crawl Object</a>
</li>
</ul>
</div>
</div>
{{end}}

View File

@ -42,7 +42,7 @@
<a class="nav-link" href="{{ url "profile" }}">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url "settings" }}">Settings</a>
<a class="nav-link" href="{{ url "configure" }}">Configure</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url "signout" }}">Sign-out</a>

View File

@ -59,6 +59,9 @@ var Command = cli.Command{
&config.AssetStorageRemoteAllowFlag,
&config.AssetStorageRemoteDenyFlag,
&config.AssetStorageRemoteMaxFlag,
&config.AllowReplyCollectionUpdatesFlag,
&config.AllowAutoAcceptFollowersFlag,
},
Action: serverCommandAction,
}
@ -89,6 +92,11 @@ func serverCommandAction(cliCtx *cli.Context) error {
}
svgerConfig := config.NewSVGerConfig(cliCtx)
fedConfig, err := config.NewFedConfig(cliCtx)
if err != nil {
return err
}
if sentryConfig.Enabled {
err = sentry.Init(sentry.ClientOptions{
Dsn: sentryConfig.Key,
@ -205,6 +213,7 @@ func serverCommandAction(cliCtx *cli.Context) error {
logger: logger,
domain: domain,
sentryConfig: sentryConfig,
fedConfig: fedConfig,
webFingerQueue: webFingerQueue,
crawlQueue: crawlQueue,
assetQueue: assetQueue,
@ -266,6 +275,7 @@ func serverCommandAction(cliCtx *cli.Context) error {
authenticated.POST("/dashboard/notes/announce/note", h.announceNote)
authenticated.GET("/configure", h.configure)
authenticated.POST("/configure/user", h.saveUserSettings)
authenticated.GET("/notifications", h.notifications)
@ -305,13 +315,13 @@ func serverCommandAction(cliCtx *cli.Context) error {
}
})
webFingerJob := job.NewWebFingerWorker(logger, webFingerQueue, s)
webFingerJob := job.NewWebFingerWorker(logger, webFingerQueue, s, fedConfig)
job.RunWorker(&group, logger, webFingerJob, parentCtx)
crawlJob := job.NewCrawlWorker(logger, crawlQueue, s, assetStorage, assetStorageConfig)
crawlJob := job.NewCrawlWorker(logger, crawlQueue, s, assetStorage, assetStorageConfig, fedConfig)
job.RunWorker(&group, logger, crawlJob, parentCtx)
assetJob := job.NewAssetDownloadJob(logger, assetQueue, s, assetStorage, assetStorageConfig)
assetJob := job.NewAssetDownloadJob(logger, assetQueue, s, assetStorage, assetStorageConfig, fedConfig)
job.RunWorker(&group, logger, assetJob, parentCtx)
quit := make(chan os.Signal)

View File

@ -29,6 +29,7 @@ type handler struct {
svgConverter SVGConverter
assetStorage asset.Storage
assetQueue common.StringQueue
fedConfig config.FedConfig
}
func (h handler) hardFail(ctx *gin.Context, err error, fields ...zap.Field) {

View File

@ -251,18 +251,6 @@ func (h handler) createNote(c *gin.Context) {
note["replies"] = replies
/*
"replies": {
"id": "https://tavern.town/objects/799b9617-dace-4510-9b95-7b84f747008a/replies",
"type": "OrderedCollection",
"totalItems": 0,
"first": "https://tavern.town/objects/799b9617-dace-4510-9b95-7b84f747008a/replies?page=1",
"published": "2020-03-12T00:00:00Z",
"updated": "2020-03-12T00:00:00Z"
}
*/
form, err := c.MultipartForm()
if err == nil {

View File

@ -5,8 +5,10 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"github.com/ngerakines/tavern/errors"
"github.com/ngerakines/tavern/storage"
)
func (h handler) configure(c *gin.Context) {
@ -38,6 +40,17 @@ func (h handler) configure(c *gin.Context) {
}
data["user"] = user
data["authenticated"] = true
data["fed_config"] = map[string]bool{
"reply_collection_updates": h.fedConfig.AllowReplyCollectionUpdates,
"auto_accept_followers": h.fedConfig.AllowAutoAcceptFollowers,
}
data["user_config"] = map[string]bool{
"reply_collection_updates": user.ReplyCollectionUpdates,
"auto_accept_followers": user.AcceptFollowers,
}
if err = session.Save(); err != nil {
h.hardFail(c, err)
@ -46,3 +59,32 @@ func (h handler) configure(c *gin.Context) {
c.HTML(http.StatusOK, "configure", data)
}
func (h handler) saveUserSettings(c *gin.Context) {
session := sessions.Default(c)
ctx := c.Request.Context()
user, err := h.storage.GetUserBySession(ctx, session)
if err != nil {
h.hardFail(c, err)
return
}
h.logger.Info("saving user settings",
zap.String("auto_accept_followers", c.PostForm("auto_accept_followers")),
zap.String("reply_collection_updates", c.PostForm("reply_collection_updates")),
)
err = storage.TransactionalStorage(ctx, h.storage, func(storage storage.Storage) error {
txErr := storage.UpdateUserAutoAcceptFollowers(ctx, user.ID, c.PostForm("auto_accept_followers") == "checked")
if txErr != nil {
return txErr
}
return storage.UpdateUserReplyCollectionUpdates(ctx, user.ID, c.PostForm("reply_collection_updates") == "checked")
})
if err != nil {
h.hardFail(c, err)
return
}
c.Redirect(http.StatusFound, h.url("configure"))
}

View File

@ -130,6 +130,8 @@ func tmplUrlGen(siteBase string) func(parts ...interface{}) string {
case "configure":
return fmt.Sprintf("%s/configure", siteBase)
case "configure_user":
return fmt.Sprintf("%s/configure/user", siteBase)
}
}
}