Restructure limits
This commit is contained in:
parent
6598ce2fe4
commit
84785b7a60
7 changed files with 105 additions and 74 deletions
|
@ -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"`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
MessagesRemaining int64 `json:"messages_remaining"`
|
||||
Emails int64 `json:"emails"`
|
||||
AttachmentsSize int64 `json:"attachments_size"`
|
||||
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"`
|
||||
}
|
||||
|
|
|
@ -40,17 +40,27 @@ type visitor struct {
|
|||
}
|
||||
|
||||
type visitorStats struct {
|
||||
Basis string // "ip", "role" or "plan"
|
||||
Messages int64
|
||||
MessagesLimit int64
|
||||
MessagesRemaining int64
|
||||
Emails int64
|
||||
AttachmentBytes 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)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue