ntfy/cmd/tier.go

290 lines
9.6 KiB
Go

//go:build !noserver
package cmd
import (
"errors"
"fmt"
"github.com/urfave/cli/v2"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"time"
)
func init() {
commands = append(commands, cmdTier)
}
const (
defaultMessageLimit = 5000
defaultMessageExpiryDuration = 12 * time.Hour
defaultEmailLimit = 20
defaultReservationLimit = 3
defaultAttachmentFileSizeLimit = "15M"
defaultAttachmentTotalSizeLimit = "100M"
defaultAttachmentExpiryDuration = 6 * time.Hour
defaultAttachmentBandwidthLimit = "1G"
)
var (
flagsTier = append([]cli.Flag{}, flagsUser...)
)
var cmdTier = &cli.Command{
Name: "tier",
Usage: "Manage/show tiers",
UsageText: "ntfy tier [list|add|remove] ...",
Flags: flagsTier,
Before: initConfigFileInputSourceFunc("config", flagsUser, initLogFunc),
Category: categoryServer,
Subcommands: []*cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "Adds a new tier",
UsageText: "ntfy tier add [OPTIONS] CODE",
Action: execTierAdd,
Flags: []cli.Flag{
&cli.StringFlag{Name: "name", Usage: "tier name"},
&cli.Int64Flag{Name: "message-limit", Value: defaultMessageLimit, Usage: "daily message limit"},
&cli.DurationFlag{Name: "message-expiry-duration", Value: defaultMessageExpiryDuration, Usage: "duration after which messages are deleted"},
&cli.Int64Flag{Name: "email-limit", Value: defaultEmailLimit, Usage: "daily email limit"},
&cli.Int64Flag{Name: "reservation-limit", Value: defaultReservationLimit, Usage: "topic reservation limit"},
&cli.StringFlag{Name: "attachment-file-size-limit", Value: defaultAttachmentFileSizeLimit, Usage: "per-attachment file size limit"},
&cli.StringFlag{Name: "attachment-total-size-limit", Value: defaultAttachmentTotalSizeLimit, Usage: "total size limit of attachments for the user"},
&cli.DurationFlag{Name: "attachment-expiry-duration", Value: defaultAttachmentExpiryDuration, Usage: "duration after which attachments are deleted"},
&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
`,
},
{
Name: "change",
Aliases: []string{"ch"},
Usage: "Change a tier",
UsageText: "ntfy tier change [OPTIONS] CODE",
Action: execTierChange,
Flags: []cli.Flag{
&cli.StringFlag{Name: "name", Usage: "tier name"},
&cli.Int64Flag{Name: "message-limit", Usage: "daily message limit"},
&cli.DurationFlag{Name: "message-expiry-duration", Usage: "duration after which messages are deleted"},
&cli.Int64Flag{Name: "email-limit", Usage: "daily email limit"},
&cli.Int64Flag{Name: "reservation-limit", Usage: "topic reservation limit"},
&cli.StringFlag{Name: "attachment-file-size-limit", Usage: "per-attachment file size limit"},
&cli.StringFlag{Name: "attachment-total-size-limit", Usage: "total size limit of attachments for the user"},
&cli.DurationFlag{Name: "attachment-expiry-duration", Usage: "duration after which attachments are deleted"},
&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
`,
},
{
Name: "remove",
Aliases: []string{"del", "rm"},
Usage: "Removes a tier",
UsageText: "ntfy tier remove CODE",
Action: execTierDel,
Description: `
FIXME
`,
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "Shows a list of tiers",
Action: execTierList,
Description: `
FIXME
`,
},
},
Description: `Manage tier 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.
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
`,
}
func execTierAdd(c *cli.Context) error {
code := c.Args().Get(0)
if code == "" {
return errors.New("tier code expected, type 'ntfy tier add --help' for help")
} else if !user.AllowedTier(code) {
return errors.New("tier code must consist only of numbers and letters")
}
manager, err := createUserManager(c)
if err != nil {
return err
}
if tier, _ := manager.Tier(code); tier != nil {
return fmt.Errorf("tier %s already exists", code)
}
name := c.String("name")
if name == "" {
name = code
}
attachmentFileSizeLimit, err := util.ParseSize(c.String("attachment-file-size-limit"))
if err != nil {
return err
}
attachmentTotalSizeLimit, err := util.ParseSize(c.String("attachment-total-size-limit"))
if err != nil {
return err
}
attachmentBandwidthLimit, err := util.ParseSize(c.String("attachment-bandwidth-limit"))
if err != nil {
return err
}
tier := &user.Tier{
ID: "", // Generated
Code: code,
Name: name,
MessageLimit: c.Int64("message-limit"),
MessageExpiryDuration: c.Duration("message-expiry-duration"),
EmailLimit: c.Int64("email-limit"),
ReservationLimit: c.Int64("reservation-limit"),
AttachmentFileSizeLimit: attachmentFileSizeLimit,
AttachmentTotalSizeLimit: attachmentTotalSizeLimit,
AttachmentExpiryDuration: c.Duration("attachment-expiry-duration"),
AttachmentBandwidthLimit: attachmentBandwidthLimit,
StripePriceID: c.String("stripe-price-id"),
}
if err := manager.AddTier(tier); err != nil {
return err
}
tier, err = manager.Tier(code)
if err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "tier added\n\n")
printTier(c, tier)
return nil
}
func execTierChange(c *cli.Context) error {
code := c.Args().Get(0)
if code == "" {
return errors.New("tier code expected, type 'ntfy tier change --help' for help")
} else if !user.AllowedTier(code) {
return errors.New("tier code must consist only of numbers and letters")
}
manager, err := createUserManager(c)
if err != nil {
return err
}
tier, err := manager.Tier(code)
if err == user.ErrTierNotFound {
return fmt.Errorf("tier %s does not exist", code)
} else if err != nil {
return err
}
if c.IsSet("name") {
tier.Name = c.String("name")
}
if c.IsSet("message-limit") {
tier.MessageLimit = c.Int64("message-limit")
}
if c.IsSet("message-expiry-duration") {
tier.MessageExpiryDuration = c.Duration("message-expiry-duration")
}
if c.IsSet("email-limit") {
tier.EmailLimit = c.Int64("email-limit")
}
if c.IsSet("reservation-limit") {
tier.ReservationLimit = c.Int64("reservation-limit")
}
if c.IsSet("attachment-file-size-limit") {
tier.AttachmentFileSizeLimit, err = util.ParseSize(c.String("attachment-file-size-limit"))
if err != nil {
return err
}
}
if c.IsSet("attachment-total-size-limit") {
tier.AttachmentTotalSizeLimit, err = util.ParseSize(c.String("attachment-total-size-limit"))
if err != nil {
return err
}
}
if c.IsSet("attachment-expiry-duration") {
tier.AttachmentExpiryDuration = c.Duration("attachment-expiry-duration")
}
if c.IsSet("attachment-bandwidth-limit") {
tier.AttachmentBandwidthLimit, err = util.ParseSize(c.String("attachment-bandwidth-limit"))
if err != nil {
return err
}
}
if c.IsSet("stripe-price-id") {
tier.StripePriceID = c.String("stripe-price-id")
}
if err := manager.UpdateTier(tier); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "tier updated\n\n")
printTier(c, tier)
return nil
}
func execTierDel(c *cli.Context) error {
code := c.Args().Get(0)
if code == "" {
return errors.New("tier code expected, type 'ntfy tier del --help' for help")
}
manager, err := createUserManager(c)
if err != nil {
return err
}
if _, err := manager.Tier(code); err == user.ErrTierNotFound {
return fmt.Errorf("tier %s does not exist", code)
}
if err := manager.RemoveTier(code); err != nil {
return err
}
fmt.Fprintf(c.App.ErrWriter, "tier %s removed\n", code)
return nil
}
func execTierList(c *cli.Context) error {
manager, err := createUserManager(c)
if err != nil {
return err
}
tiers, err := manager.Tiers()
if err != nil {
return err
}
for _, tier := range tiers {
printTier(c, tier)
}
return nil
}
func printTier(c *cli.Context, tier *user.Tier) {
stripePriceID := tier.StripePriceID
if stripePriceID == "" {
stripePriceID = "(none)"
}
fmt.Fprintf(c.App.ErrWriter, "tier %s (id: %s)\n", tier.Code, tier.ID)
fmt.Fprintf(c.App.ErrWriter, "- Name: %s\n", tier.Name)
fmt.Fprintf(c.App.ErrWriter, "- Message limit: %d\n", tier.MessageLimit)
fmt.Fprintf(c.App.ErrWriter, "- Message expiry duration: %s (%d seconds)\n", tier.MessageExpiryDuration.String(), int64(tier.MessageExpiryDuration.Seconds()))
fmt.Fprintf(c.App.ErrWriter, "- Email limit: %d\n", tier.EmailLimit)
fmt.Fprintf(c.App.ErrWriter, "- Reservation limit: %d\n", tier.ReservationLimit)
fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSize(tier.AttachmentFileSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSize(tier.AttachmentTotalSizeLimit))
fmt.Fprintf(c.App.ErrWriter, "- Attachment expiry duration: %s (%d seconds)\n", tier.AttachmentExpiryDuration.String(), int64(tier.AttachmentExpiryDuration.Seconds()))
fmt.Fprintf(c.App.ErrWriter, "- Attachment daily bandwidth limit: %s\n", util.FormatSize(tier.AttachmentBandwidthLimit))
fmt.Fprintf(c.App.ErrWriter, "- Stripe price: %s\n", stripePriceID)
}