binwiederhier 3 лет назад
Родитель
Сommit
a32e8abc12
8 измененных файлов с 140 добавлено и 40 удалено
  1. 12 5
      cmd/publish_test.go
  2. 58 15
      cmd/tier.go
  3. 46 0
      cmd/tier_test.go
  4. 6 6
      cmd/user.go
  5. 8 6
      log/event.go
  6. 4 4
      log/log_test.go
  7. 3 1
      server/log.go
  8. 3 3
      server/server.go

+ 12 - 5
cmd/publish_test.go

@@ -8,20 +8,27 @@ import (
 	"os"
 	"os/exec"
 	"strconv"
+	"strings"
 	"testing"
 	"time"
 )
 
 func TestCLI_Publish_Subscribe_Poll_Real_Server(t *testing.T) {
 	testMessage := util.RandomString(10)
-
 	app, _, _, _ := newTestApp()
 	require.Nil(t, app.Run([]string{"ntfy", "publish", "ntfytest", "ntfy unit test " + testMessage}))
-	time.Sleep(3 * time.Second) // Since #502, ntfy.sh writes messages to the cache asynchronously, after a timeout of ~1.5s
 
-	app2, _, stdout, _ := newTestApp()
-	require.Nil(t, app2.Run([]string{"ntfy", "subscribe", "--poll", "ntfytest"}))
-	require.Contains(t, stdout.String(), testMessage)
+	_, err := util.Retry(func() (*int, error) {
+		app2, _, stdout, _ := newTestApp()
+		if err := app2.Run([]string{"ntfy", "subscribe", "--poll", "ntfytest"}); err != nil {
+			return nil, err
+		}
+		if !strings.Contains(stdout.String(), testMessage) {
+			return nil, fmt.Errorf("test message %s not found in topic", testMessage)
+		}
+		return util.Int(1), nil
+	}, time.Second, 2*time.Second, 5*time.Second) // Since #502, ntfy.sh writes messages to the cache asynchronously, after a timeout of ~1.5s
+	require.Nil(t, err)
 }
 
 func TestCLI_Publish_Subscribe_Poll(t *testing.T) {

+ 58 - 15
cmd/tier.go

@@ -56,8 +56,27 @@ var cmdTier = &cli.Command{
 				&cli.StringFlag{Name: "attachment-bandwidth-limit", Value: defaultAttachmentBandwidthLimit, Usage: "daily bandwidth limit for attachment uploads/downloads"},
 				&cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
 			},
-			Description: `
-FIXME
+			Description: `Add a new tier to the ntfy user database.
+
+Tiers can be used to grant users higher limits based, such as daily message limits, attachment size, or
+make it possible for users to reserve topics.
+
+This is a server-only command. It directly reads from the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined.
+
+Examples:
+  ntfy tier add pro                     # Add tier with code "pro", using the defaults
+  ntfy tier add \                       # Add a tier with custom limits
+    --name="Pro" \
+    --message-limit=10000 \
+    --message-expiry-duration=24h \
+    --email-limit=50 \
+    --reservation-limit=10 \
+    --attachment-file-size-limit=100M \
+    --attachment-total-size-limit=1G \
+    --attachment-expiry-duration=12h \
+    --attachment-bandwidth-limit=5G \
+    pro
 `,
 		},
 		{
@@ -78,8 +97,20 @@ FIXME
 				&cli.StringFlag{Name: "attachment-bandwidth-limit", Usage: "daily bandwidth limit for attachment uploads/downloads"},
 				&cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
 			},
-			Description: `
-FIXME
+			Description: `Updates a tier to change the limits.
+
+After updating a tier, you may have to restart the ntfy server to apply them 
+to all visitors. 
+
+This is a server-only command. It directly reads from the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined.
+
+Examples:
+  ntfy tier change --name="Pro" pro        # Update the name of an existing tier
+  ntfy tier change \                       # Update multiple limits and fields
+    --message-expiry-duration=24h \
+    --stripe-price-id=price_1234 \
+    pro
 `,
 		},
 		{
@@ -88,8 +119,16 @@ FIXME
 			Usage:     "Removes a tier",
 			UsageText: "ntfy tier remove CODE",
 			Action:    execTierDel,
-			Description: `
-FIXME
+			Description: `Remove a tier from the ntfy user database.
+
+You cannot remove a tier if there are users associated with a tier. Use "ntfy user change-tier"
+to remove or switch their tier first.
+
+This is a server-only command. It directly reads from the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined.
+
+Example:
+  ntfy tier del pro
 `,
 		},
 		{
@@ -97,22 +136,26 @@ FIXME
 			Aliases: []string{"l"},
 			Usage:   "Shows a list of tiers",
 			Action:  execTierList,
-			Description: `
-FIXME
+			Description: `Shows a list of all configured tiers.
+
+This is a server-only command. It directly reads from the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined.
 `,
 		},
 	},
-	Description: `Manage tier of the ntfy server.
+	Description: `Manage tiers of the ntfy server.
 
-The command allows you to add/remove/change tier in the ntfy user database. Tiers are used
-to grant users higher limits based on their tier.
+The command allows you to add/remove/change tiers in the ntfy user database. Tiers are used
+to grant users higher limits, such as daily message limits, attachment size, or make it 
+possible for users to reserve topics.
 
 This is a server-only command. It directly manages the user.db as defined in the server config
-file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
-to the related command 'ntfy access'.
-
-FIXME
+file server.yml. The command only works if 'auth-file' is properly defined.
 
+Examples:
+  ntfy tier add pro                     # Add tier with code "pro", using the defaults
+  ntfy tier change --name="Pro" pro     # Update the name of an existing tier
+  ntfy tier del pro                     # Delete an existing tier
 `,
 }
 

+ 46 - 0
cmd/tier_test.go

@@ -0,0 +1,46 @@
+package cmd
+
+import (
+	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli/v2"
+	"heckel.io/ntfy/server"
+	"heckel.io/ntfy/test"
+	"testing"
+)
+
+func TestCLI_Tier_AddListChangeDelete(t *testing.T) {
+	s, conf, port := newTestServerWithAuth(t)
+	defer test.StopServer(t, s, port)
+
+	app, _, _, stderr := newTestApp()
+	require.Nil(t, runTierCommand(app, conf, "add", "--name", "Pro", "--message-limit", "1234", "pro"))
+	require.Contains(t, stderr.String(), "tier added\n\ntier pro (id: ti_")
+
+	err := runTierCommand(app, conf, "add", "pro")
+	require.NotNil(t, err)
+	require.Equal(t, "tier pro already exists", err.Error())
+
+	app, _, _, stderr = newTestApp()
+	require.Nil(t, runTierCommand(app, conf, "list"))
+	require.Contains(t, stderr.String(), "tier pro (id: ti_")
+	require.Contains(t, stderr.String(), "- Name: Pro")
+	require.Contains(t, stderr.String(), "- Message limit: 1234")
+
+	app, _, _, stderr = newTestApp()
+	require.Nil(t, runTierCommand(app, conf, "change", "--message-limit", "999", "pro"))
+	require.Contains(t, stderr.String(), "- Message limit: 999")
+
+	app, _, _, stderr = newTestApp()
+	require.Nil(t, runTierCommand(app, conf, "remove", "pro"))
+	require.Contains(t, stderr.String(), "tier pro removed")
+}
+
+func runTierCommand(app *cli.App, conf *server.Config, args ...string) error {
+	userArgs := []string{
+		"ntfy",
+		"tier",
+		"--auth-file=" + conf.AuthFile,
+		"--auth-default-access=" + conf.AuthDefault.String(),
+	}
+	return app.Run(append(userArgs, args...))
+}

+ 6 - 6
cmd/user.go

@@ -139,22 +139,22 @@ Example:
 			Action:  execUserList,
 			Description: `Shows a list of all configured users, including the everyone ('*') user.
 
-This is a server-only command. It directly reads from the user.db as defined in the server config
-file server.yml. The command only works if 'auth-file' is properly defined. 
-
 This command is an alias to calling 'ntfy access' (display access control list).
+
+This is a server-only command. It directly reads from the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined.
 `,
 		},
 	},
 	Description: `Manage users of the ntfy server.
 
+The command allows you to add/remove/change users in the ntfy user database, as well as change 
+passwords or roles.
+
 This is a server-only command. It directly manages the user.db as defined in the server config
 file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
 to the related command 'ntfy access'.
 
-The command allows you to add/remove/change users in the ntfy user database, as well as change 
-passwords or roles.
-
 Examples:
   ntfy user list                               # Shows list of users (alias: 'ntfy access')                      
   ntfy user add phil                           # Add regular user phil  

+ 8 - 6
log/event.go

@@ -11,13 +11,14 @@ import (
 )
 
 const (
-	tagField   = "tag"
-	errorField = "error"
+	tagField        = "tag"
+	errorField      = "error"
+	timestampFormat = "2006-01-02T15:04:05.999Z07:00"
 )
 
 // Event represents a single log event
 type Event struct {
-	Timestamp int64  `json:"time"`
+	Timestamp string `json:"time"`
 	Level     Level  `json:"level"`
 	Message   string `json:"message"`
 	fields    Context
@@ -25,8 +26,9 @@ type Event struct {
 
 // newEvent creates a new log event
 func newEvent() *Event {
+	now := time.Now()
 	return &Event{
-		Timestamp: time.Now().UnixMilli(),
+		Timestamp: now.Format(timestampFormat),
 		fields:    make(Context),
 	}
 }
@@ -70,8 +72,8 @@ func (e *Event) Tag(tag string) *Event {
 }
 
 // Time sets the time field
-func (e *Event) Time(time time.Time) *Event {
-	e.Timestamp = time.UnixMilli()
+func (e *Event) Time(t time.Time) *Event {
+	e.Timestamp = t.Format(timestampFormat)
 	return e
 }
 

+ 4 - 4
log/log_test.go

@@ -35,7 +35,7 @@ func TestLog_TagContextFieldFields(t *testing.T) {
 		Tag("mytag").
 		Field("field2", 123).
 		Field("field1", "value1").
-		Time(time.Unix(123, 0)).
+		Time(time.Unix(123, 999000000).UTC()).
 		Info("hi there %s", "phil")
 	log.
 		Tag("not-stripe").
@@ -48,11 +48,11 @@ func TestLog_TagContextFieldFields(t *testing.T) {
 		}).
 		Tag("stripe").
 		Err(err).
-		Time(time.Unix(456, 0)).
+		Time(time.Unix(456, 123000000).UTC()).
 		Debug("Subscription status %s", "active")
 
-	expected := `{"time":123000,"level":"INFO","message":"hi there phil","field1":"value1","field2":123,"tag":"mytag"}
-{"time":456000,"level":"DEBUG","message":"Subscription status active","error":"some error","error_code":123,"stripe_customer_id":"acct_123","stripe_subscription_id":"sub_123","tag":"stripe","user_id":"u_abc","visitor_ip":"1.2.3.4"}
+	expected := `{"time":"1970-01-01T00:02:03.999Z","level":"INFO","message":"hi there phil","field1":"value1","field2":123,"tag":"mytag"}
+{"time":"1970-01-01T00:07:36.123Z","level":"DEBUG","message":"Subscription status active","error":"some error","error_code":123,"stripe_customer_id":"acct_123","stripe_subscription_id":"sub_123","tag":"stripe","user_id":"u_abc","visitor_ip":"1.2.3.4"}
 `
 	require.Equal(t, expected, out.String())
 }

+ 3 - 1
server/log.go

@@ -24,7 +24,9 @@ func logv(v *visitor) *log.Event {
 
 // logr creates a new log event with HTTP request and visitor fields
 func logvr(v *visitor, r *http.Request) *log.Event {
-	return logv(v).Fields(httpContext(r))
+	return logv(v).
+		Fields(httpContext(r)).
+		Fields(requestLimiterFields(v.RequestLimiter()))
 }
 
 // logvrm creates a new log event with HTTP request, visitor fields and message fields

+ 3 - 3
server/server.go

@@ -37,7 +37,7 @@ import (
 - HIGH Rate limiting: Sensitive endpoints (account/login/change-password/...)
 - HIGH Account limit creation triggers when account is taken!
 - HIGH Docs
-- HIGH CLI "ntfy tier [add|list|delete]"
+- HIGH make request limit independent of message limit again
 - HIGH Self-review
 - MEDIUM: Test for expiring messages after reservation removal
 - MEDIUM: Test new token endpoints & never-expiring token
@@ -235,8 +235,8 @@ func (s *Server) Run() error {
 	}
 	log.Info("Listening on%s, ntfy %s, log level is %s", listenStr, s.config.Version, log.CurrentLevel().String())
 	if log.IsFile() {
-		fmt.Fprintf(os.Stderr, "Listening on%s, ntfy %s, log file is %s\n", listenStr, s.config.Version, log.File())
-		fmt.Fprintln(os.Stderr, "No more output is expected.")
+		fmt.Fprintf(os.Stderr, "Listening on%s, ntfy %s\n", listenStr, s.config.Version)
+		fmt.Fprintf(os.Stderr, "Logs are written to %s\n", log.File())
 	}
 	mux := http.NewServeMux()
 	mux.HandleFunc("/", s.handle)