From b1887c6de39b6bce9084359cb8dac3e8d106ada4 Mon Sep 17 00:00:00 2001 From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:41:28 +0200 Subject: [PATCH 1/5] (BEDS-425) implement endpoint for mobile app bundles --- backend/cmd/typescript_converter/main.go | 2 +- backend/pkg/api/data_access/app.go | 14 ++++++++++ backend/pkg/api/data_access/dummy.go | 8 ++++++ backend/pkg/api/enums/enums.go | 35 ++++++++++++++++++++++++ backend/pkg/api/handlers/internal.go | 34 +++++++++++++++++++++++ backend/pkg/api/router.go | 1 + backend/pkg/api/types/data_access.go | 11 ++++++++ backend/pkg/api/types/mobile.go | 8 ++++++ frontend/types/api/mobile.ts | 12 ++++++++ 9 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 backend/pkg/api/types/mobile.go create mode 100644 frontend/types/api/mobile.ts diff --git a/backend/cmd/typescript_converter/main.go b/backend/cmd/typescript_converter/main.go index 2a3ce86f5..360e57188 100644 --- a/backend/cmd/typescript_converter/main.go +++ b/backend/cmd/typescript_converter/main.go @@ -21,7 +21,7 @@ const ( ) // Files that should not be converted to TypeScript -var ignoredFiles = []string{"data_access", "search_types"} +var ignoredFiles = []string{"data_access", "search_types", "archiver"} var typeMappings = map[string]string{ "decimal.Decimal": "string /* decimal.Decimal */", diff --git a/backend/pkg/api/data_access/app.go b/backend/pkg/api/data_access/app.go index e3c29d621..774f89550 100644 --- a/backend/pkg/api/data_access/app.go +++ b/backend/pkg/api/data_access/app.go @@ -1,10 +1,12 @@ package dataaccess import ( + "context" "database/sql" "fmt" "time" + "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gobitfly/beaconchain/pkg/commons/utils" "github.com/gobitfly/beaconchain/pkg/userservice" @@ -19,6 +21,8 @@ type AppRepository interface { AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error GetAppSubscriptionCount(userID uint64) (uint64, error) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails t.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error + GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) + IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error } // GetUserIdByRefreshToken basically used to confirm the claimed user id with the refresh token. Returns the userId if successful @@ -105,3 +109,13 @@ func (d *DataAccessService) AddMobilePurchase(tx *sql.Tx, userID uint64, payment return err } + +func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { + // @TODO data access + return d.dummy.GetLatestBundleForNativeVersion(ctx, version, environment) +} + +func (d *DataAccessService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error { + // @TODO data access + return d.dummy.IncrementBundleDeliveryCount(ctx, bundleVerison) +} diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index 6197a72b2..37c202a5a 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -641,3 +641,11 @@ func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) types.Healt r, _ := getDummyData[types.HealthzData]() return r } + +func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { + return getDummyStruct[t.MobileAppBundleStats]() +} + +func (d *DummyService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error { + return nil +} diff --git a/backend/pkg/api/enums/enums.go b/backend/pkg/api/enums/enums.go index a0bd6997d..f8a8b48cf 100644 --- a/backend/pkg/api/enums/enums.go +++ b/backend/pkg/api/enums/enums.go @@ -268,3 +268,38 @@ func (c ChartAggregation) Duration(secondsPerEpoch uint64) time.Duration { return 0 } } + +// ---------------- +// Environment + +type Environment int + +var _ EnumFactory[Environment] = Environment(0) + +const ( + EnvironmentProduction Environment = iota + EnvironmentStaging +) + +func (e Environment) Int() int { + return int(e) +} + +func (Environment) NewFromString(s string) Environment { + switch s { + case "production", "prod", "": + return EnvironmentProduction + case "staging": + return EnvironmentStaging + default: + return Environment(-1) + } +} + +var Environments = struct { + Production Environment + Staging Environment +}{ + EnvironmentProduction, + EnvironmentStaging, +} diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index 1c4157290..90b143b9b 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -461,6 +461,40 @@ func (h *HandlerService) InternalGetValidatorDashboardRocketPoolMinipools(w http h.PublicGetValidatorDashboardRocketPoolMinipools(w, r) } +// -------------------------------------- +// Mobile + +func (h *HandlerService) InternalGetMobileLatestBundle(w http.ResponseWriter, r *http.Request) { + var v validationError + q := r.URL.Query() + force := v.checkBool(q.Get("force"), "force") + bundleVersion := v.checkUint(q.Get("bundle_version"), "bundle_version") + nativeVersion := v.checkUint(q.Get("native_version"), "native_version") + environment := checkEnum[enums.Environment](&v, q.Get("environment"), "environment") + if v.hasErrors() { + handleErr(w, r, v) + return + } + stats, err := h.dai.GetLatestBundleForNativeVersion(r.Context(), nativeVersion, environment) + if err != nil { + handleErr(w, r, err) + return + } + var data types.MobileBundleData + data.HasNativeUpdateAvailable = stats.MaxNativeVersion > nativeVersion + if !force && bundleVersion <= stats.BundleVersion && stats.TargetCount > 0 && stats.DeliveryCount >= stats.TargetCount { + returnOk(w, r, data) + return + } + data.BundleUrl = stats.BundleUrl + returnOk(w, r, data) + + err = h.dai.IncrementBundleDeliveryCount(r.Context(), bundleVersion) + if err != nil { + logApiError(r, err, 0) + } +} + // -------------------------------------- // Notifications diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go index 5603e4856..248356377 100644 --- a/backend/pkg/api/router.go +++ b/backend/pkg/api/router.go @@ -93,6 +93,7 @@ func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Ro {http.MethodGet, "/mobile/authorize", nil, hs.InternalPostMobileAuthorize}, {http.MethodPost, "/mobile/equivalent-exchange", nil, hs.InternalPostMobileEquivalentExchange}, {http.MethodPost, "/mobile/purchase", nil, hs.InternalHandleMobilePurchase}, + {http.MethodGet, "/mobile/latest-bundle", nil, hs.InternalGetMobileLatestBundle}, {http.MethodPost, "/logout", nil, hs.InternalPostLogout}, diff --git a/backend/pkg/api/types/data_access.go b/backend/pkg/api/types/data_access.go index fb1257f62..015e5ea02 100644 --- a/backend/pkg/api/types/data_access.go +++ b/backend/pkg/api/types/data_access.go @@ -227,3 +227,14 @@ type HealthzData struct { DeploymentType string `json:"deployment_type"` Reports map[string][]HealthzResult `json:"status_reports"` } + +// ------------------------- +// Mobile structs + +type MobileAppBundleStats struct { + BundleVersion uint64 + BundleUrl string + TargetCount uint64 // coalesce to 0 if column is null + DeliveryCount uint64 + MaxNativeVersion uint64 // the max native version of the whole table for the given environment +} diff --git a/backend/pkg/api/types/mobile.go b/backend/pkg/api/types/mobile.go new file mode 100644 index 000000000..7f84a999d --- /dev/null +++ b/backend/pkg/api/types/mobile.go @@ -0,0 +1,8 @@ +package types + +type MobileBundleData struct { + BundleUrl string `json:"bundle_url,omitempty"` + HasNativeUpdateAvailable bool `json:"has_native_update_available"` +} + +type GetMobileLatestBundleResponse ApiDataResponse[MobileBundleData] diff --git a/frontend/types/api/mobile.ts b/frontend/types/api/mobile.ts new file mode 100644 index 000000000..d6b234a18 --- /dev/null +++ b/frontend/types/api/mobile.ts @@ -0,0 +1,12 @@ +// Code generated by tygo. DO NOT EDIT. +/* eslint-disable */ +import type { ApiDataResponse } from './common' + +////////// +// source: mobile.go + +export interface MobileBundleData { + bundle_url?: string; + has_native_update_available: boolean; +} +export type GetMobileLatestBundleResponse = ApiDataResponse; From 782cf362a079f76ed28b831fe140d421ecbe7f7e Mon Sep 17 00:00:00 2001 From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:52:47 +0200 Subject: [PATCH 2/5] (BEDS-425) move increment to own endpoint --- backend/pkg/api/handlers/internal.go | 22 ++++++++++++++++------ backend/pkg/api/router.go | 1 + backend/pkg/api/types/data_access.go | 10 +++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index 90b143b9b..af77d379d 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -482,17 +482,27 @@ func (h *HandlerService) InternalGetMobileLatestBundle(w http.ResponseWriter, r } var data types.MobileBundleData data.HasNativeUpdateAvailable = stats.MaxNativeVersion > nativeVersion - if !force && bundleVersion <= stats.BundleVersion && stats.TargetCount > 0 && stats.DeliveryCount >= stats.TargetCount { - returnOk(w, r, data) - return + // if given bundle version is smaller than the latest and delivery count is less than target count, return the latest bundle + if force || (bundleVersion < stats.LatestBundleVersion && (stats.TargetCount == 0 || stats.DeliveryCount < stats.TargetCount)) { + data.BundleUrl = stats.BundleUrl } - data.BundleUrl = stats.BundleUrl returnOk(w, r, data) +} - err = h.dai.IncrementBundleDeliveryCount(r.Context(), bundleVersion) +func (h *HandlerService) InternalPostMobileBundleDeliveries(w http.ResponseWriter, r *http.Request) { + var v validationError + vars := mux.Vars(r) + bundleVersion := v.checkUint(vars["bundle_version"], "bundle_version") + if v.hasErrors() { + handleErr(w, r, v) + return + } + err := h.dai.IncrementBundleDeliveryCount(r.Context(), bundleVersion) if err != nil { - logApiError(r, err, 0) + handleErr(w, r, err) + return } + returnNoContent(w, r) } // -------------------------------------- diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go index 248356377..0b3d6a819 100644 --- a/backend/pkg/api/router.go +++ b/backend/pkg/api/router.go @@ -94,6 +94,7 @@ func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Ro {http.MethodPost, "/mobile/equivalent-exchange", nil, hs.InternalPostMobileEquivalentExchange}, {http.MethodPost, "/mobile/purchase", nil, hs.InternalHandleMobilePurchase}, {http.MethodGet, "/mobile/latest-bundle", nil, hs.InternalGetMobileLatestBundle}, + {http.MethodPost, "/mobile/bundles/{bundle_version}/deliveries", nil, hs.InternalPostMobileBundleDeliveries}, {http.MethodPost, "/logout", nil, hs.InternalPostLogout}, diff --git a/backend/pkg/api/types/data_access.go b/backend/pkg/api/types/data_access.go index 015e5ea02..49d88b926 100644 --- a/backend/pkg/api/types/data_access.go +++ b/backend/pkg/api/types/data_access.go @@ -232,9 +232,9 @@ type HealthzData struct { // Mobile structs type MobileAppBundleStats struct { - BundleVersion uint64 - BundleUrl string - TargetCount uint64 // coalesce to 0 if column is null - DeliveryCount uint64 - MaxNativeVersion uint64 // the max native version of the whole table for the given environment + LatestBundleVersion uint64 + BundleUrl string + TargetCount uint64 // coalesce to 0 if column is null + DeliveryCount uint64 + MaxNativeVersion uint64 // the max native version of the whole table for the given environment } From 2171c7bccbb3e27e6077dc05b22faeb37765c098 Mon Sep 17 00:00:00 2001 From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com> Date: Mon, 9 Sep 2024 12:58:30 +0200 Subject: [PATCH 3/5] (BEDS-425) GetLatestBundleForNativeVersion: rename param --- backend/pkg/api/data_access/app.go | 6 +++--- backend/pkg/api/data_access/dummy.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/pkg/api/data_access/app.go b/backend/pkg/api/data_access/app.go index 774f89550..ee80d2f5a 100644 --- a/backend/pkg/api/data_access/app.go +++ b/backend/pkg/api/data_access/app.go @@ -21,7 +21,7 @@ type AppRepository interface { AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error GetAppSubscriptionCount(userID uint64) (uint64, error) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails t.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error - GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) + GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error } @@ -110,9 +110,9 @@ func (d *DataAccessService) AddMobilePurchase(tx *sql.Tx, userID uint64, payment return err } -func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { +func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { // @TODO data access - return d.dummy.GetLatestBundleForNativeVersion(ctx, version, environment) + return d.dummy.GetLatestBundleForNativeVersion(ctx, nativeVersion, environment) } func (d *DataAccessService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error { diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index 37c202a5a..eed807fcd 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -642,7 +642,7 @@ func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) types.Healt return r } -func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, version uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { +func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { return getDummyStruct[t.MobileAppBundleStats]() } From c398c9a3fe9ad5cdd470a75105f8b6ad0c76cdf7 Mon Sep 17 00:00:00 2001 From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:23:12 +0200 Subject: [PATCH 4/5] (BEDS-425) return proper response --- backend/pkg/api/handlers/internal.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index af77d379d..f4cf3f6b2 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -486,7 +486,10 @@ func (h *HandlerService) InternalGetMobileLatestBundle(w http.ResponseWriter, r if force || (bundleVersion < stats.LatestBundleVersion && (stats.TargetCount == 0 || stats.DeliveryCount < stats.TargetCount)) { data.BundleUrl = stats.BundleUrl } - returnOk(w, r, data) + response := types.GetMobileLatestBundleResponse{ + Data: data, + } + returnOk(w, r, response) } func (h *HandlerService) InternalPostMobileBundleDeliveries(w http.ResponseWriter, r *http.Request) { From e0594f4b1fa130809d7b2ff8b456c69baeb7dabc Mon Sep 17 00:00:00 2001 From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:24:49 +0200 Subject: [PATCH 5/5] (BEDS-425) remove environment query param --- backend/pkg/api/data_access/app.go | 7 +++--- backend/pkg/api/data_access/dummy.go | 7 +++--- backend/pkg/api/enums/enums.go | 35 ---------------------------- backend/pkg/api/handlers/internal.go | 3 +-- 4 files changed, 7 insertions(+), 45 deletions(-) diff --git a/backend/pkg/api/data_access/app.go b/backend/pkg/api/data_access/app.go index ee80d2f5a..812549b1a 100644 --- a/backend/pkg/api/data_access/app.go +++ b/backend/pkg/api/data_access/app.go @@ -6,7 +6,6 @@ import ( "fmt" "time" - "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gobitfly/beaconchain/pkg/commons/utils" "github.com/gobitfly/beaconchain/pkg/userservice" @@ -21,7 +20,7 @@ type AppRepository interface { AddMobileNotificationToken(userID uint64, deviceID, notifyToken string) error GetAppSubscriptionCount(userID uint64) (uint64, error) AddMobilePurchase(tx *sql.Tx, userID uint64, paymentDetails t.MobileSubscription, verifyResponse *userservice.VerifyResponse, extSubscriptionId string) error - GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) + GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64) (*t.MobileAppBundleStats, error) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error } @@ -110,9 +109,9 @@ func (d *DataAccessService) AddMobilePurchase(tx *sql.Tx, userID uint64, payment return err } -func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { +func (d *DataAccessService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64) (*t.MobileAppBundleStats, error) { // @TODO data access - return d.dummy.GetLatestBundleForNativeVersion(ctx, nativeVersion, environment) + return d.dummy.GetLatestBundleForNativeVersion(ctx, nativeVersion) } func (d *DataAccessService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error { diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index eed807fcd..36247ea76 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -11,7 +11,6 @@ import ( "github.com/go-faker/faker/v4" "github.com/go-faker/faker/v4/pkg/options" "github.com/gobitfly/beaconchain/pkg/api/enums" - "github.com/gobitfly/beaconchain/pkg/api/types" t "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gobitfly/beaconchain/pkg/userservice" "github.com/shopspring/decimal" @@ -637,12 +636,12 @@ func (d *DummyService) GetRocketPoolOverview(ctx context.Context) (*t.RocketPool return getDummyStruct[t.RocketPoolData]() } -func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) types.HealthzData { - r, _ := getDummyData[types.HealthzData]() +func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) t.HealthzData { + r, _ := getDummyData[t.HealthzData]() return r } -func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64, environment enums.Environment) (*t.MobileAppBundleStats, error) { +func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, nativeVersion uint64) (*t.MobileAppBundleStats, error) { return getDummyStruct[t.MobileAppBundleStats]() } diff --git a/backend/pkg/api/enums/enums.go b/backend/pkg/api/enums/enums.go index f8a8b48cf..a0bd6997d 100644 --- a/backend/pkg/api/enums/enums.go +++ b/backend/pkg/api/enums/enums.go @@ -268,38 +268,3 @@ func (c ChartAggregation) Duration(secondsPerEpoch uint64) time.Duration { return 0 } } - -// ---------------- -// Environment - -type Environment int - -var _ EnumFactory[Environment] = Environment(0) - -const ( - EnvironmentProduction Environment = iota - EnvironmentStaging -) - -func (e Environment) Int() int { - return int(e) -} - -func (Environment) NewFromString(s string) Environment { - switch s { - case "production", "prod", "": - return EnvironmentProduction - case "staging": - return EnvironmentStaging - default: - return Environment(-1) - } -} - -var Environments = struct { - Production Environment - Staging Environment -}{ - EnvironmentProduction, - EnvironmentStaging, -} diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go index f4cf3f6b2..d111ab7ca 100644 --- a/backend/pkg/api/handlers/internal.go +++ b/backend/pkg/api/handlers/internal.go @@ -470,12 +470,11 @@ func (h *HandlerService) InternalGetMobileLatestBundle(w http.ResponseWriter, r force := v.checkBool(q.Get("force"), "force") bundleVersion := v.checkUint(q.Get("bundle_version"), "bundle_version") nativeVersion := v.checkUint(q.Get("native_version"), "native_version") - environment := checkEnum[enums.Environment](&v, q.Get("environment"), "environment") if v.hasErrors() { handleErr(w, r, v) return } - stats, err := h.dai.GetLatestBundleForNativeVersion(r.Context(), nativeVersion, environment) + stats, err := h.dai.GetLatestBundleForNativeVersion(r.Context(), nativeVersion) if err != nil { handleErr(w, r, err) return