Forráskód Böngészése

Restructure limits

binwiederhier 3 éve
szülő
commit
84785b7a60
7 módosított fájl, 105 hozzáadás és 74 törlés
  1. 1 1
      auth/auth.go
  2. 2 2
      auth/auth_sqlite.go
  3. 2 2
      server/server.go
  4. 19 36
      server/server_account.go
  5. 14 11
      server/types.go
  6. 58 11
      server/visitor.go
  7. 9 11
      web/src/components/Account.js

+ 1 - 1
auth/auth.go

@@ -84,7 +84,7 @@ const (
 type Plan struct {
 	Code                     string `json:"name"`
 	Upgradable               bool   `json:"upgradable"`
-	MessageLimit             int64  `json:"messages_limit"`
+	MessagesLimit            int64  `json:"messages_limit"`
 	EmailsLimit              int64  `json:"emails_limit"`
 	AttachmentFileSizeLimit  int64  `json:"attachment_file_size_limit"`
 	AttachmentTotalSizeLimit int64  `json:"attachment_total_size_limit"`

+ 2 - 2
auth/auth_sqlite.go

@@ -110,7 +110,7 @@ const (
 	selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
 )
 
-// SQLiteAuthManager is an implementation of Manager and Manager. It stores users and access control list
+// SQLiteAuthManager is an implementation of Manager. It stores users and access control list
 // in a SQLite database.
 type SQLiteAuthManager struct {
 	db           *sql.DB
@@ -355,7 +355,7 @@ func (a *SQLiteAuthManager) readUser(rows *sql.Rows) (*User, error) {
 		user.Plan = &Plan{
 			Code:                     planCode.String,
 			Upgradable:               true, // FIXME
-			MessageLimit:             messagesLimit.Int64,
+			MessagesLimit:            messagesLimit.Int64,
 			EmailsLimit:              emailsLimit.Int64,
 			AttachmentFileSizeLimit:  attachmentFileSizeLimit.Int64,
 			AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64,

+ 2 - 2
server/server.go

@@ -773,7 +773,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
 	contentLengthStr := r.Header.Get("Content-Length")
 	if contentLengthStr != "" { // Early "do-not-trust" check, hard limit see below
 		contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
-		if err == nil && (contentLength > visitorStats.VisitorAttachmentBytesRemaining || contentLength > s.config.AttachmentFileSizeLimit) {
+		if err == nil && (contentLength > visitorStats.AttachmentTotalSizeRemaining || contentLength > s.config.AttachmentFileSizeLimit) {
 			return errHTTPEntityTooLargeAttachmentTooLarge
 		}
 	}
@@ -791,7 +791,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
 	if m.Message == "" {
 		m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name)
 	}
-	m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(visitorStats.VisitorAttachmentBytesRemaining))
+	m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(visitorStats.AttachmentTotalSizeRemaining))
 	if err == util.ErrLimitReached {
 		return errHTTPEntityTooLargeAttachmentTooLarge
 	} else if err != nil {

+ 19 - 36
server/server_account.go

@@ -40,7 +40,21 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
 		return err
 	}
 	response := &apiAccountSettingsResponse{
-		Usage: &apiAccountStats{},
+		Stats: &apiAccountStats{
+			Messages:                     stats.Messages,
+			MessagesRemaining:            stats.MessagesRemaining,
+			Emails:                       stats.Emails,
+			EmailsRemaining:              stats.EmailsRemaining,
+			AttachmentTotalSize:          stats.AttachmentTotalSize,
+			AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
+		},
+		Limits: &apiAccountLimits{
+			Basis:               stats.Basis,
+			Messages:            stats.MessagesLimit,
+			Emails:              stats.EmailsLimit,
+			AttachmentTotalSize: stats.AttachmentTotalSizeLimit,
+			AttachmentFileSize:  stats.AttachmentFileSizeLimit,
+		},
 	}
 	if v.user != nil {
 		response.Username = v.user.Name
@@ -57,62 +71,31 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
 			}
 		}
 		if v.user.Plan != nil {
-			response.Usage.Basis = "account"
-			response.Plan = &apiAccountSettingsPlan{
+			response.Plan = &apiAccountPlan{
 				Code:       v.user.Plan.Code,
 				Upgradable: v.user.Plan.Upgradable,
 			}
-			response.Limits = &apiAccountLimits{
-				MessagesLimit:            v.user.Plan.MessageLimit,
-				EmailsLimit:              v.user.Plan.EmailsLimit,
-				AttachmentFileSizeLimit:  v.user.Plan.AttachmentFileSizeLimit,
-				AttachmentTotalSizeLimit: v.user.Plan.AttachmentTotalSizeLimit,
-			}
 		} else {
 			if v.user.Role == auth.RoleAdmin {
-				response.Usage.Basis = "account"
-				response.Plan = &apiAccountSettingsPlan{
+				response.Plan = &apiAccountPlan{
 					Code:       string(auth.PlanUnlimited),
 					Upgradable: false,
 				}
-				response.Limits = &apiAccountLimits{
-					MessagesLimit:            0,
-					EmailsLimit:              0,
-					AttachmentFileSizeLimit:  0,
-					AttachmentTotalSizeLimit: 0,
-				}
 			} else {
-				response.Usage.Basis = "ip"
-				response.Plan = &apiAccountSettingsPlan{
+				response.Plan = &apiAccountPlan{
 					Code:       string(auth.PlanDefault),
 					Upgradable: true,
 				}
-				response.Limits = &apiAccountLimits{
-					MessagesLimit:            int64(s.config.VisitorRequestLimitBurst),
-					EmailsLimit:              int64(s.config.VisitorEmailLimitBurst),
-					AttachmentFileSizeLimit:  s.config.AttachmentFileSizeLimit,
-					AttachmentTotalSizeLimit: s.config.VisitorAttachmentTotalSizeLimit,
-				}
 			}
 		}
 	} else {
 		response.Username = auth.Everyone
 		response.Role = string(auth.RoleAnonymous)
-		response.Usage.Basis = "ip"
-		response.Plan = &apiAccountSettingsPlan{
+		response.Plan = &apiAccountPlan{
 			Code:       string(auth.PlanNone),
 			Upgradable: true,
 		}
-		response.Limits = &apiAccountLimits{
-			MessagesLimit:            int64(s.config.VisitorRequestLimitBurst),
-			EmailsLimit:              int64(s.config.VisitorEmailLimitBurst),
-			AttachmentFileSizeLimit:  s.config.AttachmentFileSizeLimit,
-			AttachmentTotalSizeLimit: s.config.VisitorAttachmentTotalSizeLimit,
-		}
 	}
-	response.Usage.Messages = stats.Messages
-	response.Usage.Emails = stats.Emails
-	response.Usage.AttachmentsSize = stats.AttachmentBytes
 	if err := json.NewEncoder(w).Encode(response); err != nil {
 		return err
 	}

+ 14 - 11
server/types.go

@@ -224,23 +224,26 @@ type apiAccountTokenResponse struct {
 	Token string `json:"token"`
 }
 
-type apiAccountSettingsPlan struct {
+type apiAccountPlan struct {
 	Code       string `json:"code"`
 	Upgradable bool   `json:"upgradable"`
 }
 
 type apiAccountLimits struct {
-	MessagesLimit            int64 `json:"messages"`
-	EmailsLimit              int64 `json:"emails"`
-	AttachmentFileSizeLimit  int64 `json:"attachment_file_size"`
-	AttachmentTotalSizeLimit int64 `json:"attachment_total_size"`
+	Basis               string `json:"basis"` // "ip", "role" or "plan"
+	Messages            int64  `json:"messages"`
+	Emails              int64  `json:"emails"`
+	AttachmentTotalSize int64  `json:"attachment_total_size"`
+	AttachmentFileSize  int64  `json:"attachment_file_size"`
 }
 
 type apiAccountStats struct {
-	Basis           string `json:"basis"` // "ip" or "account"
-	Messages        int64  `json:"messages"`
-	Emails          int64  `json:"emails"`
-	AttachmentsSize int64  `json:"attachments_size"`
+	Messages                     int64 `json:"messages"`
+	MessagesRemaining            int64 `json:"messages_remaining"`
+	Emails                       int64 `json:"emails"`
+	EmailsRemaining              int64 `json:"emails_remaining"`
+	AttachmentTotalSize          int64 `json:"attachment_total_size"`
+	AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
 }
 
 type apiAccountSettingsResponse struct {
@@ -249,7 +252,7 @@ type apiAccountSettingsResponse struct {
 	Language      string                      `json:"language,omitempty"`
 	Notification  *auth.UserNotificationPrefs `json:"notification,omitempty"`
 	Subscriptions []*auth.UserSubscription    `json:"subscriptions,omitempty"`
-	Plan          *apiAccountSettingsPlan     `json:"plan,omitempty"`
+	Plan          *apiAccountPlan             `json:"plan,omitempty"`
 	Limits        *apiAccountLimits           `json:"limits,omitempty"`
-	Usage         *apiAccountStats            `json:"usage,omitempty"`
+	Stats         *apiAccountStats            `json:"stats,omitempty"`
 }

+ 58 - 11
server/visitor.go

@@ -40,17 +40,27 @@ type visitor struct {
 }
 
 type visitorStats struct {
-	Messages        int64
-	Emails          int64
-	AttachmentBytes int64
+	Basis                        string // "ip", "role" or "plan"
+	Messages                     int64
+	MessagesLimit                int64
+	MessagesRemaining            int64
+	Emails                       int64
+	EmailsLimit                  int64
+	EmailsRemaining              int64
+	AttachmentTotalSize          int64
+	AttachmentTotalSizeLimit     int64
+	AttachmentTotalSizeRemaining int64
+	AttachmentFileSizeLimit      int64
 }
 
 func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *auth.User) *visitor {
-	var requestLimiter *rate.Limiter
+	var requestLimiter, emailsLimiter *rate.Limiter
 	if user != nil && user.Plan != nil {
-		requestLimiter = rate.NewLimiter(rate.Limit(user.Plan.MessageLimit)*rate.Every(24*time.Hour), conf.VisitorRequestLimitBurst)
+		requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.MessagesLimit), conf.VisitorRequestLimitBurst)
+		emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.EmailsLimit), conf.VisitorEmailLimitBurst)
 	} else {
 		requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
+		emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
 	}
 	return &visitor{
 		config:              conf,
@@ -60,7 +70,7 @@ func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *a
 		messages:            0, // TODO
 		emails:              0, // TODO
 		requestLimiter:      requestLimiter,
-		emailsLimiter:       rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst),
+		emailsLimiter:       emailsLimiter,
 		subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
 		bandwidthLimiter:    util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
 		firebase:            time.Unix(0, 0),
@@ -147,9 +157,46 @@ func (v *visitor) Stats() (*visitorStats, error) {
 	}
 	v.mu.Lock()
 	defer v.mu.Unlock()
-	return &visitorStats{
-		Messages:        v.messages,
-		Emails:          v.emails,
-		AttachmentBytes: attachmentsBytesUsed,
-	}, nil
+	stats := &visitorStats{}
+	if v.user != nil && v.user.Role == auth.RoleAdmin {
+		stats.Basis = "role"
+		stats.MessagesLimit = 0
+		stats.EmailsLimit = 0
+		stats.AttachmentTotalSizeLimit = 0
+		stats.AttachmentFileSizeLimit = 0
+	} else if v.user != nil && v.user.Plan != nil {
+		stats.Basis = "plan"
+		stats.MessagesLimit = v.user.Plan.MessagesLimit
+		stats.EmailsLimit = v.user.Plan.EmailsLimit
+		stats.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
+		stats.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
+	} else {
+		stats.Basis = "ip"
+		stats.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
+		stats.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
+		stats.AttachmentTotalSizeLimit = v.config.AttachmentTotalSizeLimit
+		stats.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
+	}
+	stats.Messages = v.messages
+	stats.MessagesRemaining = zeroIfNegative(stats.MessagesLimit - stats.MessagesLimit)
+	stats.Emails = v.emails
+	stats.EmailsRemaining = zeroIfNegative(stats.EmailsLimit - stats.EmailsRemaining)
+	stats.AttachmentTotalSize = attachmentsBytesUsed
+	stats.AttachmentTotalSizeRemaining = zeroIfNegative(stats.AttachmentTotalSizeLimit - stats.AttachmentTotalSize)
+	return stats, nil
+}
+
+func zeroIfNegative(value int64) int64 {
+	if value < 0 {
+		return 0
+	}
+	return value
+}
+
+func replenishDurationToDailyLimit(duration time.Duration) int64 {
+	return int64(24 * time.Hour / duration)
+}
+
+func dailyLimitToRate(limit int64) rate.Limit {
+	return rate.Limit(limit) * rate.Every(24*time.Hour)
 }

+ 9 - 11
web/src/components/Account.js

@@ -60,8 +60,6 @@ const Stats = () => {
         return <></>; // TODO loading
     }
     const accountType = account.plan.code ?? "none";
-    const limits = account.limits;
-    const usage = account.usage;
     const normalize = (value, max) => (value / max * 100);
     return (
         <Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}>
@@ -78,24 +76,24 @@ const Stats = () => {
                 </Pref>
                 <Pref labelId={"messages"} title={t("Published messages")}>
                     <div>
-                        <Typography variant="body2" sx={{float: "left"}}>{usage.messages}</Typography>
-                        <Typography variant="body2" sx={{float: "right"}}>{limits.messages > 0 ? t("of {{limit}}", { limit: limits.messages }) : t("Unlimited")}</Typography>
+                        <Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>
+                        <Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("of {{limit}}", { limit: account.limits.messages }) : t("Unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={limits.messages > 0 ? normalize(usage.messages, limits.messages) : 100} />
+                    <LinearProgress variant="determinate" value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100} />
                 </Pref>
                 <Pref labelId={"emails"} title={t("Emails sent")}>
                     <div>
-                        <Typography variant="body2" sx={{float: "left"}}>{usage.emails}</Typography>
-                        <Typography variant="body2" sx={{float: "right"}}>{limits.emails > 0 ? t("of {{limit}}", { limit: limits.emails }) : t("Unlimited")}</Typography>
+                        <Typography variant="body2" sx={{float: "left"}}>{account.stats.emails}</Typography>
+                        <Typography variant="body2" sx={{float: "right"}}>{account.limits.emails > 0 ? t("of {{limit}}", { limit: account.limits.emails }) : t("Unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={limits.emails > 0 ? normalize(usage.emails, limits.emails) : 100} />
+                    <LinearProgress variant="determinate" value={account.limits.emails > 0 ? normalize(account.stats.emails, account.limits.emails) : 100} />
                 </Pref>
                 <Pref labelId={"attachments"} title={t("Attachment storage")}>
                     <div>
-                        <Typography variant="body2" sx={{float: "left"}}>{formatBytes(usage.attachments_size)}</Typography>
-                        <Typography variant="body2" sx={{float: "right"}}>{limits.attachment_total_size > 0 ? t("of {{limit}}", { limit: formatBytes(limits.attachment_total_size) }) : t("Unlimited")}</Typography>
+                        <Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
+                        <Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("of {{limit}}", { limit: formatBytes(account.limits.attachment_total_size) }) : t("Unlimited")}</Typography>
                     </div>
-                    <LinearProgress variant="determinate" value={limits.attachment_total_size > 0 ? normalize(usage.attachments_size, limits.attachment_total_size) : 100} />
+                    <LinearProgress variant="determinate" value={account.limits.attachment_total_size > 0 ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100} />
                 </Pref>
             </PrefGroup>
         </Card>