TopicsLimit

This commit is contained in:
binwiederhier 2023-01-01 20:42:33 -05:00
parent 2267d27c9b
commit e650f813c5
7 changed files with 43 additions and 14 deletions

View File

@ -39,7 +39,7 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
return nil return nil
} }
func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *visitor) error {
stats, err := v.Info() stats, err := v.Info()
if err != nil { if err != nil {
return err return err
@ -50,6 +50,8 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
MessagesRemaining: stats.MessagesRemaining, MessagesRemaining: stats.MessagesRemaining,
Emails: stats.Emails, Emails: stats.Emails,
EmailsRemaining: stats.EmailsRemaining, EmailsRemaining: stats.EmailsRemaining,
Topics: stats.Topics,
TopicsRemaining: stats.TopicsRemaining,
AttachmentTotalSize: stats.AttachmentTotalSize, AttachmentTotalSize: stats.AttachmentTotalSize,
AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining, AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
}, },
@ -57,6 +59,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
Basis: stats.Basis, Basis: stats.Basis,
Messages: stats.MessagesLimit, Messages: stats.MessagesLimit,
Emails: stats.EmailsLimit, Emails: stats.EmailsLimit,
Topics: stats.TopicsLimit,
AttachmentTotalSize: stats.AttachmentTotalSizeLimit, AttachmentTotalSize: stats.AttachmentTotalSizeLimit,
AttachmentFileSize: stats.AttachmentFileSizeLimit, AttachmentFileSize: stats.AttachmentFileSizeLimit,
}, },
@ -119,7 +122,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
return nil return nil
} }
func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountDelete(w http.ResponseWriter, _ *http.Request, v *visitor) error {
if err := s.userManager.RemoveUser(v.user.Name); err != nil { if err := s.userManager.RemoveUser(v.user.Name); err != nil {
return err return err
} }
@ -141,7 +144,7 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ
return nil return nil
} }
func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, _ *http.Request, v *visitor) error {
// TODO rate limit // TODO rate limit
token, err := s.userManager.CreateToken(v.user) token, err := s.userManager.CreateToken(v.user)
if err != nil { if err != nil {
@ -159,7 +162,7 @@ func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, r *http.Request,
return nil return nil
} }
func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, _ *http.Request, v *visitor) error {
// TODO rate limit // TODO rate limit
if v.user == nil { if v.user == nil {
return errHTTPUnauthorized return errHTTPUnauthorized
@ -182,7 +185,7 @@ func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, r *http.Request
return nil return nil
} }
func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request, v *visitor) error { func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, _ *http.Request, v *visitor) error {
// TODO rate limit // TODO rate limit
if v.user.Token == "" { if v.user.Token == "" {
return errHTTPBadRequestNoTokenProvided return errHTTPBadRequestNoTokenProvided

View File

@ -243,6 +243,7 @@ type apiAccountLimits struct {
Basis string `json:"basis"` // "ip", "role" or "plan" Basis string `json:"basis"` // "ip", "role" or "plan"
Messages int64 `json:"messages"` Messages int64 `json:"messages"`
Emails int64 `json:"emails"` Emails int64 `json:"emails"`
Topics int64 `json:"topics"`
AttachmentTotalSize int64 `json:"attachment_total_size"` AttachmentTotalSize int64 `json:"attachment_total_size"`
AttachmentFileSize int64 `json:"attachment_file_size"` AttachmentFileSize int64 `json:"attachment_file_size"`
} }
@ -252,6 +253,8 @@ type apiAccountStats struct {
MessagesRemaining int64 `json:"messages_remaining"` MessagesRemaining int64 `json:"messages_remaining"`
Emails int64 `json:"emails"` Emails int64 `json:"emails"`
EmailsRemaining int64 `json:"emails_remaining"` EmailsRemaining int64 `json:"emails_remaining"`
Topics int64 `json:"topics"`
TopicsRemaining int64 `json:"topics_remaining"`
AttachmentTotalSize int64 `json:"attachment_total_size"` AttachmentTotalSize int64 `json:"attachment_total_size"`
AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"` AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
} }

View File

@ -48,6 +48,9 @@ type visitorInfo struct {
Emails int64 Emails int64
EmailsLimit int64 EmailsLimit int64
EmailsRemaining int64 EmailsRemaining int64
Topics int64
TopicsLimit int64
TopicsRemaining int64
AttachmentTotalSize int64 AttachmentTotalSize int64
AttachmentTotalSizeLimit int64 AttachmentTotalSizeLimit int64
AttachmentTotalSizeRemaining int64 AttachmentTotalSizeRemaining int64
@ -173,20 +176,19 @@ func (v *visitor) Info() (*visitorInfo, error) {
info := &visitorInfo{} info := &visitorInfo{}
if v.user != nil && v.user.Role == user.RoleAdmin { if v.user != nil && v.user.Role == user.RoleAdmin {
info.Basis = "role" info.Basis = "role"
info.MessagesLimit = 0 // All limits are zero!
info.EmailsLimit = 0
info.AttachmentTotalSizeLimit = 0
info.AttachmentFileSizeLimit = 0
} else if v.user != nil && v.user.Plan != nil { } else if v.user != nil && v.user.Plan != nil {
info.Basis = "plan" info.Basis = "plan"
info.MessagesLimit = v.user.Plan.MessagesLimit info.MessagesLimit = v.user.Plan.MessagesLimit
info.EmailsLimit = v.user.Plan.EmailsLimit info.EmailsLimit = v.user.Plan.EmailsLimit
info.TopicsLimit = v.user.Plan.TopicsLimit
info.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit info.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
info.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit info.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
} else { } else {
info.Basis = "ip" info.Basis = "ip"
info.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish) info.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
info.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish) info.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
info.TopicsLimit = 0 // FIXME
info.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit info.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
info.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit info.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
} }
@ -200,10 +202,20 @@ func (v *visitor) Info() (*visitorInfo, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var topics int64
if v.user != nil {
for _, grant := range v.user.Grants {
if grant.Owner {
topics++
}
}
}
info.Messages = messages info.Messages = messages
info.MessagesRemaining = zeroIfNegative(info.MessagesLimit - info.Messages) info.MessagesRemaining = zeroIfNegative(info.MessagesLimit - info.Messages)
info.Emails = emails info.Emails = emails
info.EmailsRemaining = zeroIfNegative(info.EmailsLimit - info.Emails) info.EmailsRemaining = zeroIfNegative(info.EmailsLimit - info.Emails)
info.Topics = topics
info.TopicsRemaining = zeroIfNegative(info.TopicsLimit - info.Topics)
info.AttachmentTotalSize = attachmentsBytesUsed info.AttachmentTotalSize = attachmentsBytesUsed
info.AttachmentTotalSizeRemaining = zeroIfNegative(info.AttachmentTotalSizeLimit - info.AttachmentTotalSize) info.AttachmentTotalSizeRemaining = zeroIfNegative(info.AttachmentTotalSizeLimit - info.AttachmentTotalSize)
return info, nil return info, nil

View File

@ -35,6 +35,7 @@ const (
code TEXT NOT NULL, code TEXT NOT NULL,
messages_limit INT NOT NULL, messages_limit INT NOT NULL,
emails_limit INT NOT NULL, emails_limit INT NOT NULL,
topics_limit INT NOT NULL,
attachment_file_size_limit INT NOT NULL, attachment_file_size_limit INT NOT NULL,
attachment_total_size_limit INT NOT NULL, attachment_total_size_limit INT NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)
@ -75,13 +76,13 @@ const (
` `
createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;` createTablesQueries = `BEGIN; ` + createTablesQueriesNoTx + ` COMMIT;`
selectUserByNameQuery = ` selectUserByNameQuery = `
SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.topics_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
FROM user u FROM user u
LEFT JOIN plan p on p.id = u.plan_id LEFT JOIN plan p on p.id = u.plan_id
WHERE user = ? WHERE user = ?
` `
selectUserByTokenQuery = ` selectUserByTokenQuery = `
SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.attachment_file_size_limit, p.attachment_total_size_limit SELECT u.user, u.pass, u.role, u.messages, u.emails, u.settings, p.code, p.messages_limit, p.emails_limit, p.topics_limit, p.attachment_file_size_limit, p.attachment_total_size_limit
FROM user u FROM user u
JOIN user_token t on u.id = t.user_id JOIN user_token t on u.id = t.user_id
LEFT JOIN plan p on p.id = u.plan_id LEFT JOIN plan p on p.id = u.plan_id
@ -469,11 +470,11 @@ func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
var username, hash, role string var username, hash, role string
var settings, planCode sql.NullString var settings, planCode sql.NullString
var messages, emails int64 var messages, emails int64
var messagesLimit, emailsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit sql.NullInt64 var messagesLimit, emailsLimit, topicsLimit, attachmentFileSizeLimit, attachmentTotalSizeLimit sql.NullInt64
if !rows.Next() { if !rows.Next() {
return nil, ErrNotFound return nil, ErrNotFound
} }
if err := rows.Scan(&username, &hash, &role, &messages, &emails, &settings, &planCode, &messagesLimit, &emailsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit); err != nil { if err := rows.Scan(&username, &hash, &role, &messages, &emails, &settings, &planCode, &messagesLimit, &emailsLimit, &topicsLimit, &attachmentFileSizeLimit, &attachmentTotalSizeLimit); err != nil {
return nil, err return nil, err
} else if err := rows.Err(); err != nil { } else if err := rows.Err(); err != nil {
return nil, err return nil, err
@ -504,6 +505,7 @@ func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
Upgradable: true, // FIXME Upgradable: true, // FIXME
MessagesLimit: messagesLimit.Int64, MessagesLimit: messagesLimit.Int64,
EmailsLimit: emailsLimit.Int64, EmailsLimit: emailsLimit.Int64,
TopicsLimit: topicsLimit.Int64,
AttachmentFileSizeLimit: attachmentFileSizeLimit.Int64, AttachmentFileSizeLimit: attachmentFileSizeLimit.Int64,
AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64, AttachmentTotalSizeLimit: attachmentTotalSizeLimit.Int64,
} }

View File

@ -60,6 +60,7 @@ type Plan struct {
Upgradable bool `json:"upgradable"` Upgradable bool `json:"upgradable"`
MessagesLimit int64 `json:"messages_limit"` MessagesLimit int64 `json:"messages_limit"`
EmailsLimit int64 `json:"emails_limit"` EmailsLimit int64 `json:"emails_limit"`
TopicsLimit int64 `json:"topics_limit"`
AttachmentFileSizeLimit int64 `json:"attachment_file_size_limit"` AttachmentFileSizeLimit int64 `json:"attachment_file_size_limit"`
AttachmentTotalSizeLimit int64 `json:"attachment_total_size_limit"` AttachmentTotalSizeLimit int64 `json:"attachment_total_size_limit"`
} }

View File

@ -183,6 +183,7 @@
"account_usage_plan_code_business_plus": "Business Plus", "account_usage_plan_code_business_plus": "Business Plus",
"account_usage_messages_title": "Published messages", "account_usage_messages_title": "Published messages",
"account_usage_emails_title": "Emails sent", "account_usage_emails_title": "Emails sent",
"account_usage_topics_title": "Topics reserved",
"account_usage_attachment_storage_title": "Attachment storage", "account_usage_attachment_storage_title": "Attachment storage",
"account_usage_attachment_storage_subtitle": "{{filesize}} per file", "account_usage_attachment_storage_subtitle": "{{filesize}} per file",
"account_usage_basis_ip_description": "Usage stats and limits for this account are based on your IP address, so they may be shared with other users.", "account_usage_basis_ip_description": "Usage stats and limits for this account are based on your IP address, so they may be shared with other users.",

View File

@ -168,7 +168,7 @@ const Stats = () => {
return <></>; return <></>;
} }
const planCode = account.plan.code ?? "none"; const planCode = account.plan.code ?? "none";
const normalize = (value, max) => (value / max * 100); const normalize = (value, max) => Math.min(value / max * 100, 100);
return ( return (
<Card sx={{p: 3}} aria-label={t("account_usage_title")}> <Card sx={{p: 3}} aria-label={t("account_usage_title")}>
<Typography variant="h5" sx={{marginBottom: 2}}> <Typography variant="h5" sx={{marginBottom: 2}}>
@ -182,6 +182,13 @@ const Stats = () => {
: t(`account_usage_plan_code_${planCode}`)} : t(`account_usage_plan_code_${planCode}`)}
</div> </div>
</Pref> </Pref>
<Pref title={t("account_usage_topics_title")}>
<div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.topics}</Typography>
<Typography variant="body2" sx={{float: "right"}}>{account.limits.topics > 0 ? t("account_usage_of_limit", { limit: account.limits.topics }) : t("account_usage_unlimited")}</Typography>
</div>
<LinearProgress variant="determinate" value={account.limits.topics > 0 ? normalize(account.stats.topics, account.limits.topics) : 100} />
</Pref>
<Pref title={t("account_usage_messages_title")}> <Pref title={t("account_usage_messages_title")}>
<div> <div>
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography> <Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>