mirror of https://gitlab.com/ngerakines/tavern.git
Added user settings form and server allow flags for auto follow and reply collection updates.
This commit is contained in:
parent
3d75b3b9b0
commit
c387363b6e
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -21,6 +21,7 @@ type Crawler struct {
|
|||
MaxDepth int
|
||||
AssetStorage asset.Storage
|
||||
AssetStorageConfig config.AssetStorageConfig
|
||||
FedConfig config.FedConfig
|
||||
}
|
||||
|
||||
type Signer interface {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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}}
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue