Skip to content

Commit

Permalink
Merge pull request #150 from SkynetLabs/ivo/limits_in_bytes
Browse files Browse the repository at this point in the history
/user/limits in bits or bytes
  • Loading branch information
ro-tex authored Mar 17, 2022
2 parents 800ba57 + 6c05966 commit 9533fae
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 26 deletions.
50 changes: 32 additions & 18 deletions api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/mail"
"net/url"
"strconv"
"strings"
"time"

"github.com/SkynetLabs/skynet-accounts/build"
Expand Down Expand Up @@ -79,11 +80,13 @@ type (
EmailConfirmed bool `json:"emailConfirmed"`
}
// UserLimitsGET is response of GET /user/limits
// The returned speeds might be in bits or bytes per second, depending on
// the client's request.
UserLimitsGET struct {
TierID int `json:"tierID"`
TierName string `json:"tierName"`
UploadBandwidth int `json:"upload"` // bytes per second
DownloadBandwidth int `json:"download"` // bytes per second
UploadBandwidth int `json:"upload"` // bits or bytes per second
DownloadBandwidth int `json:"download"` // bits or bytes per second
MaxUploadSize int64 `json:"maxUploadSize"` // the max size of a single upload in bytes
MaxNumberUploads int `json:"-"`
RegistryDelay int `json:"registry"` // ms delay
Expand Down Expand Up @@ -402,22 +405,26 @@ func (api *API) userGET(u *database.User, w http.ResponseWriter, _ *http.Request
// NOTE: This handler needs to use the noAuth middleware in order to be able to
// optimise its calls to the DB and the use of caching.
func (api *API) userLimitsGET(_ *database.User, w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
// inBytes is a flag indicating that the caller wants all bandwidth limits
// to be presented in bytes per second. The default behaviour is to present
// them in bits per second.
inBytes := strings.EqualFold(req.FormValue("unit"), "byte")
// First check for an API key.
ak, err := apiKeyFromRequest(req)
respAnon := userLimitsGetFromTier(database.TierAnonymous)
respAnon := userLimitsGetFromTier(database.TierAnonymous, inBytes)
if err == nil {
u, err := api.staticDB.UserByAPIKey(req.Context(), ak)
if err != nil {
api.staticLogger.Traceln("Error while fetching user by API key:", err)
api.WriteJSON(w, respAnon)
return
}
resp := userLimitsGetFromTier(u.Tier)
resp := userLimitsGetFromTier(u.Tier, inBytes)
// If the quota is exceeded we should keep the user's tier but report
// anonymous-level speeds.
if u.QuotaExceeded {
// Report the speeds for tier anonymous.
resp = userLimitsGetFromTier(database.TierAnonymous)
resp = userLimitsGetFromTier(database.TierAnonymous, inBytes)
// But keep reporting the user's actual tier and it's name.
resp.TierID = u.Tier
resp.TierName = database.UserLimits[u.Tier].TierName
Expand Down Expand Up @@ -455,12 +462,12 @@ func (api *API) userLimitsGET(_ *database.User, w http.ResponseWriter, req *http
build.Critical("Failed to fetch user from UserTierCache right after setting it.")
}
}
resp := userLimitsGetFromTier(tier)
resp := userLimitsGetFromTier(tier, inBytes)
// If the quota is exceeded we should keep the user's tier but report
// anonymous-level speeds.
if qe {
// Report anonymous speeds.
resp = userLimitsGetFromTier(database.TierAnonymous)
resp = userLimitsGetFromTier(database.TierAnonymous, inBytes)
// Keep reporting the user's actual tier and tier name.
resp.TierID = tier
resp.TierName = database.UserLimits[tier].TierName
Expand Down Expand Up @@ -1196,17 +1203,24 @@ func parseRequestBodyJSON(body io.ReadCloser, maxBodySize int64, objRef interfac
}

// userLimitsGetFromTier is a helper that lets us succinctly translate
// from the database DTO to the API DTO.
func userLimitsGetFromTier(tier int) *UserLimitsGET {
// from the database DTO to the API DTO. The `inBytes` parameter determines
// whether the returned speeds will be in Bps or bps.
func userLimitsGetFromTier(tier int, inBytes bool) *UserLimitsGET {
t := database.UserLimits[tier]
return &UserLimitsGET{
TierID: tier,
TierName: t.TierName,
UploadBandwidth: t.UploadBandwidth,
DownloadBandwidth: t.DownloadBandwidth,
MaxUploadSize: t.MaxUploadSize,
MaxNumberUploads: t.MaxNumberUploads,
RegistryDelay: t.RegistryDelay,
Storage: t.Storage,
ul := UserLimitsGET{
TierID: tier,
TierName: t.TierName,
MaxUploadSize: t.MaxUploadSize,
MaxNumberUploads: t.MaxNumberUploads,
RegistryDelay: t.RegistryDelay,
Storage: t.Storage,
}
if inBytes {
ul.UploadBandwidth = t.UploadBandwidth
ul.DownloadBandwidth = t.DownloadBandwidth
} else {
ul.UploadBandwidth = t.UploadBandwidth * 8
ul.DownloadBandwidth = t.DownloadBandwidth * 8
}
return &ul
}
6 changes: 3 additions & 3 deletions test/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func TestUserTierCache(t *testing.T) {
}
at.SetCookie(test.ExtractCookie(r))
// Get the user's limit.
ul, _, err := at.UserLimits()
ul, _, err := at.UserLimits("byte")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -236,7 +236,7 @@ func TestUserTierCache(t *testing.T) {
err = build.Retry(10, 200*time.Millisecond, func() error {
// We expect to get tier with name and id matching TierPremium20 but with
// speeds matching TierAnonymous.
ul, _, err = at.UserLimits()
ul, _, err = at.UserLimits("byte")
if err != nil {
t.Fatal(err)
}
Expand All @@ -262,7 +262,7 @@ func TestUserTierCache(t *testing.T) {
}
err = build.Retry(10, 200*time.Millisecond, func() error {
// We expect to get TierPremium20.
ul, _, err = at.UserLimits()
ul, _, err = at.UserLimits("byte")
if err != nil {
return errors.AddContext(err, "failed to call /user/limits")
}
Expand Down
36 changes: 33 additions & 3 deletions test/api/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ func testUserLimits(t *testing.T, at *test.AccountsTester) {
defer at.ClearCredentials()

// Call /user/limits with a cookie. Expect FreeTier response.
tl, _, err := at.UserLimits()
tl, _, err := at.UserLimits("byte")
if err != nil {
t.Fatal(err)
}
Expand All @@ -457,7 +457,7 @@ func testUserLimits(t *testing.T, at *test.AccountsTester) {

// Call /user/limits without a cookie. Expect FreeAnonymous response.
at.ClearCredentials()
tl, _, err = at.UserLimits()
tl, _, err = at.UserLimits("byte")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -506,7 +506,7 @@ func testUserLimits(t *testing.T, at *test.AccountsTester) {
err = build.Retry(10, 200*time.Millisecond, func() error {
// Check the user's limits. We expect the tier to be Free but the limits to
// match Anonymous.
tl, _, err = at.UserLimits()
tl, _, err = at.UserLimits("byte")
if err != nil {
return errors.AddContext(err, "failed to call /user/limits")
}
Expand All @@ -524,6 +524,36 @@ func testUserLimits(t *testing.T, at *test.AccountsTester) {
if err != nil {
t.Fatal(err)
}

// Test the `unit` parameter. The only valid value is `byte`, anything else
// is ignored and the results are returned in bits per second.
tl, _, err = at.UserLimits("")
if err != nil {
t.Fatal(err)
}
// Request it with an invalid value. Expect it to be ignored.
tlBits, _, err := at.UserLimits("not-a-byte")
if err != nil {
t.Fatal(err)
}
if tlBits.UploadBandwidth != tl.UploadBandwidth || tlBits.DownloadBandwidth != tl.DownloadBandwidth {
t.Fatalf("Expected these to be equal. %+v, %+v", tl, tlBits)
}
tlBytes, _, err := at.UserLimits("byte")
if err != nil {
t.Fatal(err)
}
if tlBytes.UploadBandwidth*8 != tl.UploadBandwidth || tlBytes.DownloadBandwidth*8 != tl.DownloadBandwidth {
t.Fatalf("Invalid values in bytes. Values in bps: %+v, values in Bps: %+v", tl, tlBytes)
}
// Ensure we're not case-sensitive.
tlBytes2, _, err := at.UserLimits("ByTe")
if err != nil {
t.Fatal(err)
}
if tlBytes2.UploadBandwidth != tlBytes.UploadBandwidth || tlBytes2.DownloadBandwidth != tlBytes.DownloadBandwidth {
t.Fatalf("Got different values for different capitalizations of 'byte'.\nValues for 'byte': %+v, values for 'ByTe': %+v", tlBytes, tlBytes2)
}
}

// testUserUploadsDELETE tests the DELETE /user/uploads/:skylink endpoint.
Expand Down
9 changes: 7 additions & 2 deletions test/tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func ExtractCookie(r *http.Response) *http.Cookie {
func NewAccountsTester(dbName string) (*AccountsTester, error) {
ctx := context.Background()
logger := logrus.New()
logger.Out = ioutil.Discard

// Initialise the environment.
jwt.PortalName = testPortalAddr
Expand Down Expand Up @@ -312,8 +313,12 @@ func (at *AccountsTester) TrackRegistryWrite() (int, error) {
}

// UserLimits performs a `GET /user/limits` request.
func (at *AccountsTester) UserLimits() (api.UserLimitsGET, int, error) {
r, b, err := at.request(http.MethodGet, "/user/limits", nil, nil)
func (at *AccountsTester) UserLimits(unit string) (api.UserLimitsGET, int, error) {
queryParams := url.Values{}
if unit != "" {
queryParams.Set("unit", unit)
}
r, b, err := at.request(http.MethodGet, "/user/limits", queryParams, nil)
if err != nil {
return api.UserLimitsGET{}, r.StatusCode, err
}
Expand Down

0 comments on commit 9533fae

Please sign in to comment.