tier.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. //go:build !noserver
  2. package cmd
  3. import (
  4. "errors"
  5. "fmt"
  6. "github.com/urfave/cli/v2"
  7. "heckel.io/ntfy/user"
  8. "heckel.io/ntfy/util"
  9. "time"
  10. )
  11. func init() {
  12. commands = append(commands, cmdTier)
  13. }
  14. const (
  15. defaultMessageLimit = 5000
  16. defaultMessageExpiryDuration = 12 * time.Hour
  17. defaultEmailLimit = 20
  18. defaultReservationLimit = 3
  19. defaultAttachmentFileSizeLimit = "15M"
  20. defaultAttachmentTotalSizeLimit = "100M"
  21. defaultAttachmentExpiryDuration = 6 * time.Hour
  22. defaultAttachmentBandwidthLimit = "1G"
  23. )
  24. var (
  25. flagsTier = append([]cli.Flag{}, flagsUser...)
  26. )
  27. var cmdTier = &cli.Command{
  28. Name: "tier",
  29. Usage: "Manage/show tiers",
  30. UsageText: "ntfy tier [list|add|remove] ...",
  31. Flags: flagsTier,
  32. Before: initConfigFileInputSourceFunc("config", flagsUser, initLogFunc),
  33. Category: categoryServer,
  34. Subcommands: []*cli.Command{
  35. {
  36. Name: "add",
  37. Aliases: []string{"a"},
  38. Usage: "Adds a new tier",
  39. UsageText: "ntfy tier add [OPTIONS] CODE",
  40. Action: execTierAdd,
  41. Flags: []cli.Flag{
  42. &cli.StringFlag{Name: "name", Usage: "tier name"},
  43. &cli.Int64Flag{Name: "message-limit", Value: defaultMessageLimit, Usage: "daily message limit"},
  44. &cli.DurationFlag{Name: "message-expiry-duration", Value: defaultMessageExpiryDuration, Usage: "duration after which messages are deleted"},
  45. &cli.Int64Flag{Name: "email-limit", Value: defaultEmailLimit, Usage: "daily email limit"},
  46. &cli.Int64Flag{Name: "reservation-limit", Value: defaultReservationLimit, Usage: "topic reservation limit"},
  47. &cli.StringFlag{Name: "attachment-file-size-limit", Value: defaultAttachmentFileSizeLimit, Usage: "per-attachment file size limit"},
  48. &cli.StringFlag{Name: "attachment-total-size-limit", Value: defaultAttachmentTotalSizeLimit, Usage: "total size limit of attachments for the user"},
  49. &cli.DurationFlag{Name: "attachment-expiry-duration", Value: defaultAttachmentExpiryDuration, Usage: "duration after which attachments are deleted"},
  50. &cli.StringFlag{Name: "attachment-bandwidth-limit", Value: defaultAttachmentBandwidthLimit, Usage: "daily bandwidth limit for attachment uploads/downloads"},
  51. &cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
  52. },
  53. Description: `
  54. FIXME
  55. `,
  56. },
  57. {
  58. Name: "change",
  59. Aliases: []string{"ch"},
  60. Usage: "Change a tier",
  61. UsageText: "ntfy tier change [OPTIONS] CODE",
  62. Action: execTierChange,
  63. Flags: []cli.Flag{
  64. &cli.StringFlag{Name: "name", Usage: "tier name"},
  65. &cli.Int64Flag{Name: "message-limit", Usage: "daily message limit"},
  66. &cli.DurationFlag{Name: "message-expiry-duration", Usage: "duration after which messages are deleted"},
  67. &cli.Int64Flag{Name: "email-limit", Usage: "daily email limit"},
  68. &cli.Int64Flag{Name: "reservation-limit", Usage: "topic reservation limit"},
  69. &cli.StringFlag{Name: "attachment-file-size-limit", Usage: "per-attachment file size limit"},
  70. &cli.StringFlag{Name: "attachment-total-size-limit", Usage: "total size limit of attachments for the user"},
  71. &cli.DurationFlag{Name: "attachment-expiry-duration", Usage: "duration after which attachments are deleted"},
  72. &cli.StringFlag{Name: "attachment-bandwidth-limit", Usage: "daily bandwidth limit for attachment uploads/downloads"},
  73. &cli.StringFlag{Name: "stripe-price-id", Usage: "Stripe price ID for paid tiers (e.g. price_12345)"},
  74. },
  75. Description: `
  76. FIXME
  77. `,
  78. },
  79. {
  80. Name: "remove",
  81. Aliases: []string{"del", "rm"},
  82. Usage: "Removes a tier",
  83. UsageText: "ntfy tier remove CODE",
  84. Action: execTierDel,
  85. Description: `
  86. FIXME
  87. `,
  88. },
  89. {
  90. Name: "list",
  91. Aliases: []string{"l"},
  92. Usage: "Shows a list of tiers",
  93. Action: execTierList,
  94. Description: `
  95. FIXME
  96. `,
  97. },
  98. },
  99. Description: `Manage tier of the ntfy server.
  100. The command allows you to add/remove/change tier in the ntfy user database. Tiers are used
  101. to grant users higher limits based on their tier.
  102. This is a server-only command. It directly manages the user.db as defined in the server config
  103. file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
  104. to the related command 'ntfy access'.
  105. FIXME
  106. `,
  107. }
  108. func execTierAdd(c *cli.Context) error {
  109. code := c.Args().Get(0)
  110. if code == "" {
  111. return errors.New("tier code expected, type 'ntfy tier add --help' for help")
  112. } else if !user.AllowedTier(code) {
  113. return errors.New("tier code must consist only of numbers and letters")
  114. }
  115. manager, err := createUserManager(c)
  116. if err != nil {
  117. return err
  118. }
  119. if tier, _ := manager.Tier(code); tier != nil {
  120. return fmt.Errorf("tier %s already exists", code)
  121. }
  122. name := c.String("name")
  123. if name == "" {
  124. name = code
  125. }
  126. attachmentFileSizeLimit, err := util.ParseSize(c.String("attachment-file-size-limit"))
  127. if err != nil {
  128. return err
  129. }
  130. attachmentTotalSizeLimit, err := util.ParseSize(c.String("attachment-total-size-limit"))
  131. if err != nil {
  132. return err
  133. }
  134. attachmentBandwidthLimit, err := util.ParseSize(c.String("attachment-bandwidth-limit"))
  135. if err != nil {
  136. return err
  137. }
  138. tier := &user.Tier{
  139. ID: "", // Generated
  140. Code: code,
  141. Name: name,
  142. MessageLimit: c.Int64("message-limit"),
  143. MessageExpiryDuration: c.Duration("message-expiry-duration"),
  144. EmailLimit: c.Int64("email-limit"),
  145. ReservationLimit: c.Int64("reservation-limit"),
  146. AttachmentFileSizeLimit: attachmentFileSizeLimit,
  147. AttachmentTotalSizeLimit: attachmentTotalSizeLimit,
  148. AttachmentExpiryDuration: c.Duration("attachment-expiry-duration"),
  149. AttachmentBandwidthLimit: attachmentBandwidthLimit,
  150. StripePriceID: c.String("stripe-price-id"),
  151. }
  152. if err := manager.AddTier(tier); err != nil {
  153. return err
  154. }
  155. tier, err = manager.Tier(code)
  156. if err != nil {
  157. return err
  158. }
  159. fmt.Fprintf(c.App.ErrWriter, "tier added\n\n")
  160. printTier(c, tier)
  161. return nil
  162. }
  163. func execTierChange(c *cli.Context) error {
  164. code := c.Args().Get(0)
  165. if code == "" {
  166. return errors.New("tier code expected, type 'ntfy tier change --help' for help")
  167. } else if !user.AllowedTier(code) {
  168. return errors.New("tier code must consist only of numbers and letters")
  169. }
  170. manager, err := createUserManager(c)
  171. if err != nil {
  172. return err
  173. }
  174. tier, err := manager.Tier(code)
  175. if err == user.ErrTierNotFound {
  176. return fmt.Errorf("tier %s does not exist", code)
  177. } else if err != nil {
  178. return err
  179. }
  180. if c.IsSet("name") {
  181. tier.Name = c.String("name")
  182. }
  183. if c.IsSet("message-limit") {
  184. tier.MessageLimit = c.Int64("message-limit")
  185. }
  186. if c.IsSet("message-expiry-duration") {
  187. tier.MessageExpiryDuration = c.Duration("message-expiry-duration")
  188. }
  189. if c.IsSet("email-limit") {
  190. tier.EmailLimit = c.Int64("email-limit")
  191. }
  192. if c.IsSet("reservation-limit") {
  193. tier.ReservationLimit = c.Int64("reservation-limit")
  194. }
  195. if c.IsSet("attachment-file-size-limit") {
  196. tier.AttachmentFileSizeLimit, err = util.ParseSize(c.String("attachment-file-size-limit"))
  197. if err != nil {
  198. return err
  199. }
  200. }
  201. if c.IsSet("attachment-total-size-limit") {
  202. tier.AttachmentTotalSizeLimit, err = util.ParseSize(c.String("attachment-total-size-limit"))
  203. if err != nil {
  204. return err
  205. }
  206. }
  207. if c.IsSet("attachment-expiry-duration") {
  208. tier.AttachmentExpiryDuration = c.Duration("attachment-expiry-duration")
  209. }
  210. if c.IsSet("attachment-bandwidth-limit") {
  211. tier.AttachmentBandwidthLimit, err = util.ParseSize(c.String("attachment-bandwidth-limit"))
  212. if err != nil {
  213. return err
  214. }
  215. }
  216. if c.IsSet("stripe-price-id") {
  217. tier.StripePriceID = c.String("stripe-price-id")
  218. }
  219. if err := manager.UpdateTier(tier); err != nil {
  220. return err
  221. }
  222. fmt.Fprintf(c.App.ErrWriter, "tier updated\n\n")
  223. printTier(c, tier)
  224. return nil
  225. }
  226. func execTierDel(c *cli.Context) error {
  227. code := c.Args().Get(0)
  228. if code == "" {
  229. return errors.New("tier code expected, type 'ntfy tier del --help' for help")
  230. }
  231. manager, err := createUserManager(c)
  232. if err != nil {
  233. return err
  234. }
  235. if _, err := manager.Tier(code); err == user.ErrTierNotFound {
  236. return fmt.Errorf("tier %s does not exist", code)
  237. }
  238. if err := manager.RemoveTier(code); err != nil {
  239. return err
  240. }
  241. fmt.Fprintf(c.App.ErrWriter, "tier %s removed\n", code)
  242. return nil
  243. }
  244. func execTierList(c *cli.Context) error {
  245. manager, err := createUserManager(c)
  246. if err != nil {
  247. return err
  248. }
  249. tiers, err := manager.Tiers()
  250. if err != nil {
  251. return err
  252. }
  253. for _, tier := range tiers {
  254. printTier(c, tier)
  255. }
  256. return nil
  257. }
  258. func printTier(c *cli.Context, tier *user.Tier) {
  259. stripePriceID := tier.StripePriceID
  260. if stripePriceID == "" {
  261. stripePriceID = "(none)"
  262. }
  263. fmt.Fprintf(c.App.ErrWriter, "tier %s (id: %s)\n", tier.Code, tier.ID)
  264. fmt.Fprintf(c.App.ErrWriter, "- Name: %s\n", tier.Name)
  265. fmt.Fprintf(c.App.ErrWriter, "- Message limit: %d\n", tier.MessageLimit)
  266. fmt.Fprintf(c.App.ErrWriter, "- Message expiry duration: %s (%d seconds)\n", tier.MessageExpiryDuration.String(), int64(tier.MessageExpiryDuration.Seconds()))
  267. fmt.Fprintf(c.App.ErrWriter, "- Email limit: %d\n", tier.EmailLimit)
  268. fmt.Fprintf(c.App.ErrWriter, "- Reservation limit: %d\n", tier.ReservationLimit)
  269. fmt.Fprintf(c.App.ErrWriter, "- Attachment file size limit: %s\n", util.FormatSize(tier.AttachmentFileSizeLimit))
  270. fmt.Fprintf(c.App.ErrWriter, "- Attachment total size limit: %s\n", util.FormatSize(tier.AttachmentTotalSizeLimit))
  271. fmt.Fprintf(c.App.ErrWriter, "- Attachment expiry duration: %s (%d seconds)\n", tier.AttachmentExpiryDuration.String(), int64(tier.AttachmentExpiryDuration.Seconds()))
  272. fmt.Fprintf(c.App.ErrWriter, "- Attachment daily bandwidth limit: %s\n", util.FormatSize(tier.AttachmentBandwidthLimit))
  273. fmt.Fprintf(c.App.ErrWriter, "- Stripe price: %s\n", stripePriceID)
  274. }