Przeglądaj źródła

Plan stuff WIPWIPWIP

binwiederhier 3 lat temu
rodzic
commit
ac56fa36ba

+ 8 - 0
auth/auth.go

@@ -64,6 +64,7 @@ type User struct {
 	Role   Role
 	Grants []Grant
 	Prefs  *UserPrefs
+	Plan   *UserPlan
 }
 
 type UserPrefs struct {
@@ -72,6 +73,13 @@ type UserPrefs struct {
 	Subscriptions []*UserSubscription    `json:"subscriptions,omitempty"`
 }
 
+type UserPlan struct {
+	Name                 string `json:"name"`
+	MessagesLimit        int    `json:"messages_limit"`
+	EmailsLimit          int    `json:"emails_limit"`
+	AttachmentBytesLimit int64  `json:"attachment_bytes_limit"`
+}
+
 type UserSubscription struct {
 	ID      string `json:"id"`
 	BaseURL string `json:"base_url"`

+ 25 - 12
auth/auth_sqlite.go

@@ -23,8 +23,10 @@ const (
 		BEGIN;
 		CREATE TABLE IF NOT EXISTS plan (
 			id INT NOT NULL,		
-			name TEXT NOT NULL,	
-			limit_messages INT,
+			name TEXT NOT NULL,
+			messages_limit INT NOT NULL,
+			emails_limit INT NOT NULL,
+			attachment_bytes_limit INT NOT NULL,
 			PRIMARY KEY (id)
 		);
 		CREATE TABLE IF NOT EXISTS user (
@@ -55,20 +57,21 @@ const (
 			id INT PRIMARY KEY,
 			version INT NOT NULL
 		);
-		INSERT INTO plan (id, name) VALUES (1, 'Admin') ON CONFLICT (id) DO NOTHING;
 		INSERT INTO user (id, user, pass, role) VALUES (1, '*', '', 'anonymous') ON CONFLICT (id) DO NOTHING;
 		COMMIT;
 	`
 	selectUserByNameQuery = `
-		SELECT user, pass, role, settings 
-		FROM user 
-		WHERE user = ?
+		SELECT u.user, u.pass, u.role, u.settings, p.name, p.messages_limit, p.emails_limit, p.attachment_bytes_limit
+		FROM user u
+		LEFT JOIN plan p on p.id = u.plan_id
+		WHERE user = ?		
 	`
 	selectUserByTokenQuery = `
-		SELECT user, pass, role, settings 
-		FROM user
-		JOIN user_token on user.id = user_token.user_id
-		WHERE token = ?
+		SELECT u.user, u.pass, u.role, u.settings, p.name, p.messages_limit, p.emails_limit, p.attachment_bytes_limit
+		FROM user u
+		JOIN user_token t on u.id = t.user_id
+		LEFT JOIN plan p on p.id = u.plan_id
+		WHERE t.token = ?
 	`
 	selectTopicPermsQuery = `
 		SELECT read, write 
@@ -321,11 +324,13 @@ func (a *SQLiteAuthManager) userByToken(token string) (*User, error) {
 func (a *SQLiteAuthManager) readUser(rows *sql.Rows) (*User, error) {
 	defer rows.Close()
 	var username, hash, role string
-	var prefs sql.NullString
+	var prefs, planName sql.NullString
+	var messagesLimit, emailsLimit sql.NullInt32
+	var attachmentBytesLimit sql.NullInt64
 	if !rows.Next() {
 		return nil, ErrNotFound
 	}
-	if err := rows.Scan(&username, &hash, &role, &prefs); err != nil {
+	if err := rows.Scan(&username, &hash, &role, &prefs, &planName, &messagesLimit, &emailsLimit, &attachmentBytesLimit); err != nil {
 		return nil, err
 	} else if err := rows.Err(); err != nil {
 		return nil, err
@@ -346,6 +351,14 @@ func (a *SQLiteAuthManager) readUser(rows *sql.Rows) (*User, error) {
 			return nil, err
 		}
 	}
+	if planName.Valid {
+		user.Plan = &UserPlan{
+			Name:                 planName.String,
+			MessagesLimit:        int(messagesLimit.Int32),
+			EmailsLimit:          int(emailsLimit.Int32),
+			AttachmentBytesLimit: attachmentBytesLimit.Int64,
+		}
+	}
 	return user, nil
 }
 

+ 2 - 2
server/server.go

@@ -333,6 +333,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
 		return s.handleUserStats(w, r, v)
 	} else if r.Method == http.MethodPost && r.URL.Path == accountPath {
 		return s.handleAccountCreate(w, r, v)
+	} else if r.Method == http.MethodGet && r.URL.Path == accountPath {
+		return s.handleAccountGet(w, r, v)
 	} else if r.Method == http.MethodDelete && r.URL.Path == accountPath {
 		return s.handleAccountDelete(w, r, v)
 	} else if r.Method == http.MethodPost && r.URL.Path == accountPasswordPath {
@@ -341,8 +343,6 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
 		return s.handleAccountTokenGet(w, r, v)
 	} else if r.Method == http.MethodDelete && r.URL.Path == accountTokenPath {
 		return s.handleAccountTokenDelete(w, r, v)
-	} else if r.Method == http.MethodGet && r.URL.Path == accountSettingsPath {
-		return s.handleAccountSettingsGet(w, r, v)
 	} else if r.Method == http.MethodPost && r.URL.Path == accountSettingsPath {
 		return s.handleAccountSettingsChange(w, r, v)
 	} else if r.Method == http.MethodPost && r.URL.Path == accountSubscriptionPath {

+ 46 - 30
server/server_account.go

@@ -32,6 +32,52 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
 	return nil
 }
 
+func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
+	stats, err := v.Stats()
+	if err != nil {
+		return err
+	}
+	response := &apiAccountSettingsResponse{
+		Usage: &apiAccountUsageLimits{
+			Basis: "ip",
+		},
+	}
+	if v.user != nil {
+		response.Username = v.user.Name
+		response.Role = string(v.user.Role)
+		if v.user.Prefs != nil {
+			if v.user.Prefs.Language != "" {
+				response.Language = v.user.Prefs.Language
+			}
+			if v.user.Prefs.Notification != nil {
+				response.Notification = v.user.Prefs.Notification
+			}
+			if v.user.Prefs.Subscriptions != nil {
+				response.Subscriptions = v.user.Prefs.Subscriptions
+			}
+		}
+		if v.user.Plan != nil {
+			response.Usage.Basis = "account"
+			response.Plan = &apiAccountSettingsPlan{
+				Name:                  v.user.Plan.Name,
+				MessagesLimit:         v.user.Plan.MessagesLimit,
+				EmailsLimit:           v.user.Plan.EmailsLimit,
+				AttachmentsBytesLimit: v.user.Plan.AttachmentBytesLimit,
+			}
+		}
+	} else {
+		response.Username = auth.Everyone
+		response.Role = string(auth.RoleAnonymous)
+	}
+	response.Usage.AttachmentsBytes = stats.VisitorAttachmentBytesUsed
+	if err := json.NewEncoder(w).Encode(response); err != nil {
+		return err
+	}
+	return nil
+}
+
 func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	if v.user == nil {
 		return errHTTPUnauthorized
@@ -99,36 +145,6 @@ func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request
 	return nil
 }
 
-func (s *Server) handleAccountSettingsGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
-	w.Header().Set("Content-Type", "application/json")
-	w.Header().Set("Access-Control-Allow-Origin", "*") // FIXME remove this
-	response := &apiAccountSettingsResponse{}
-	if v.user != nil {
-		response.Username = v.user.Name
-		response.Role = string(v.user.Role)
-		if v.user.Prefs != nil {
-			if v.user.Prefs.Language != "" {
-				response.Language = v.user.Prefs.Language
-			}
-			if v.user.Prefs.Notification != nil {
-				response.Notification = v.user.Prefs.Notification
-			}
-			if v.user.Prefs.Subscriptions != nil {
-				response.Subscriptions = v.user.Prefs.Subscriptions
-			}
-		}
-	} else {
-		response = &apiAccountSettingsResponse{
-			Username: auth.Everyone,
-			Role:     string(auth.RoleAnonymous),
-		}
-	}
-	if err := json.NewEncoder(w).Encode(response); err != nil {
-		return err
-	}
-	return nil
-}
-
 func (s *Server) handleAccountSettingsChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
 	if v.user == nil {
 		return errors.New("no user")

+ 12 - 2
server/types.go

@@ -225,8 +225,17 @@ type apiAccountTokenResponse struct {
 }
 
 type apiAccountSettingsPlan struct {
-	Id   int    `json:"id"`
-	Name string `json:"name"`
+	Name                  string `json:"name"`
+	MessagesLimit         int    `json:"messages_limit"`
+	EmailsLimit           int    `json:"emails_limit"`
+	AttachmentsBytesLimit int64  `json:"attachments_bytes_limit"`
+}
+
+type apiAccountUsageLimits struct {
+	Basis            string `json:"basis"` // "ip" or "account"
+	Messages         int    `json:"messages"`
+	Emails           int    `json:"emails"`
+	AttachmentsBytes int64  `json:"attachments_bytes"`
 }
 
 type apiAccountSettingsResponse struct {
@@ -236,4 +245,5 @@ type apiAccountSettingsResponse struct {
 	Language      string                      `json:"language,omitempty"`
 	Notification  *auth.UserNotificationPrefs `json:"notification,omitempty"`
 	Subscriptions []*auth.UserSubscription    `json:"subscriptions,omitempty"`
+	Usage         *apiAccountUsageLimits      `json:"usage,omitempty"`
 }

+ 14 - 14
web/src/app/Api.js

@@ -175,6 +175,20 @@ class Api {
         }
     }
 
+    async getAccount(baseUrl, token) {
+        const url = accountUrl(baseUrl);
+        console.log(`[Api] Fetching user account ${url}`);
+        const response = await fetch(url, {
+            headers: maybeWithBearerAuth({}, token)
+        });
+        if (response.status !== 200) {
+            throw new Error(`Unexpected server response ${response.status}`);
+        }
+        const account = await response.json();
+        console.log(`[Api] Account`, account);
+        return account;
+    }
+
     async deleteAccount(baseUrl, token) {
         const url = accountUrl(baseUrl);
         console.log(`[Api] Deleting user account ${url}`);
@@ -202,20 +216,6 @@ class Api {
         }
     }
 
-    async getAccountSettings(baseUrl, token) {
-        const url = accountSettingsUrl(baseUrl);
-        console.log(`[Api] Fetching user account ${url}`);
-        const response = await fetch(url, {
-            headers: maybeWithBearerAuth({}, token)
-        });
-        if (response.status !== 200) {
-            throw new Error(`Unexpected server response ${response.status}`);
-        }
-        const account = await response.json();
-        console.log(`[Api] Account`, account);
-        return account;
-    }
-
     async updateAccountSettings(baseUrl, token, payload) {
         const url = accountSettingsUrl(baseUrl);
         const body = JSON.stringify(payload);

+ 3 - 1
web/src/components/Account.js

@@ -52,6 +52,8 @@ const Basics = () => {
 const Stats = () => {
     const { t } = useTranslation();
     const { account } = useOutletContext();
+    const admin = account?.role === "admin"
+    const accountType = account?.plan?.name ?? "Free";
     return (
         <Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}>
             <Typography variant="h5" sx={{marginBottom: 2}}>
@@ -62,7 +64,7 @@ const Stats = () => {
                     <div>
                         {account?.role === "admin"
                             ? <>Unlimited <Tooltip title={"You are Admin"}><span style={{cursor: "default"}}>👑</span></Tooltip></>
-                            : "Free"}
+                            : accountType}
                     </div>
                 </Pref>
                 <Pref labelId={"dailyMessages"} title={t("Daily messages")}>

+ 1 - 1
web/src/components/App.js

@@ -96,7 +96,7 @@ const Layout = () => {
 
     useEffect(() => {
         (async () => {
-            const acc = await api.getAccountSettings("http://localhost:2586", session.token());
+            const acc = await api.getAccount("http://localhost:2586", session.token());
             if (acc) {
                 setAccount(acc);
                 if (acc.language) {