Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beds 152/archived dashboards #692

Merged
merged 6 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 80 additions & 30 deletions backend/pkg/api/data_access/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)

type UserRepository interface {
Expand Down Expand Up @@ -547,53 +548,102 @@ func (d *DataAccessService) GetFreeTierPerks(ctx context.Context) (*t.PremiumPer
}

func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64) (*t.UserDashboardsData, error) {
// TODO @DATA-ACCESS Adjust to api changes: return archival related fields
result := &t.UserDashboardsData{}

dbReturn := []struct {
Id uint64 `db:"id"`
Name string `db:"name"`
PublicId sql.NullString `db:"public_id"`
PublicName sql.NullString `db:"public_name"`
SharedGroups sql.NullBool `db:"shared_groups"`
}{}
wg := errgroup.Group{}

// Get the validator dashboards including the public ones
err := d.alloyReader.SelectContext(ctx, &dbReturn, `
validatorDashboardMap := make(map[uint64]*t.ValidatorDashboard, 0)
wg.Go(func() error {
dbReturn := []struct {
Id uint64 `db:"id"`
Name string `db:"name"`
IsArchived sql.NullString `db:"is_archived"`
PublicId sql.NullString `db:"public_id"`
PublicName sql.NullString `db:"public_name"`
SharedGroups sql.NullBool `db:"shared_groups"`
}{}

err := d.alloyReader.SelectContext(ctx, &dbReturn, `
SELECT
uvd.id,
uvd.name,
uvd.is_archived,
uvds.public_id,
uvds.name AS public_name,
uvds.shared_groups
FROM users_val_dashboards uvd
LEFT JOIN users_val_dashboards_sharing uvds ON uvd.id = uvds.dashboard_id
WHERE uvd.user_id = $1
`, userId)
if err != nil {
return nil, err
}
if err != nil {
return err
}

// Fill the result
validatorDashboardMap := make(map[uint64]*t.ValidatorDashboard, 0)
for _, row := range dbReturn {
if _, ok := validatorDashboardMap[row.Id]; !ok {
validatorDashboardMap[row.Id] = &t.ValidatorDashboard{
Id: row.Id,
Name: row.Name,
PublicIds: []t.VDBPublicId{},
for _, row := range dbReturn {
if _, ok := validatorDashboardMap[row.Id]; !ok {
validatorDashboardMap[row.Id] = &t.ValidatorDashboard{
Id: row.Id,
Name: row.Name,
PublicIds: []t.VDBPublicId{},
IsArchived: row.IsArchived.Valid,
ArchivedReason: row.IsArchived.String,
}
}
if row.PublicId.Valid {
publicId := t.VDBPublicId{}
publicId.PublicId = row.PublicId.String
publicId.Name = row.PublicName.String
publicId.ShareSettings.ShareGroups = row.SharedGroups.Bool

validatorDashboardMap[row.Id].PublicIds = append(validatorDashboardMap[row.Id].PublicIds, publicId)
}
}
if row.PublicId.Valid {
result := t.VDBPublicId{}
result.PublicId = row.PublicId.String
result.Name = row.PublicName.String
result.ShareSettings.ShareGroups = row.SharedGroups.Bool

validatorDashboardMap[row.Id].PublicIds = append(validatorDashboardMap[row.Id].PublicIds, result)
return nil
})

type DashboardCount struct {
Id uint64 `db:"id"`
GroupCount uint64 `db:"group_count"`
ValidatorCount uint64 `db:"validator_count"`
}

validatorDashboardCountMap := make(map[uint64]DashboardCount, 0)
wg.Go(func() error {
dbReturn := []DashboardCount{}

err := d.alloyReader.SelectContext(ctx, &dbReturn, `
SELECT
uvd.id,
COUNT(DISTINCT(uvdg.id)) AS group_count,
COUNT(DISTINCT(uvdv.validator_index)) AS validator_count
FROM users_val_dashboards uvd
LEFT JOIN users_val_dashboards_groups uvdg ON uvd.id = uvdg.dashboard_id
LEFT JOIN users_val_dashboards_validators uvdv ON uvd.id = uvdv.dashboard_id
WHERE uvd.user_id = $1
GROUP BY uvd.id
`, userId)
if err != nil {
return err
}

for _, row := range dbReturn {
validatorDashboardCountMap[row.Id] = row
}

return nil
})

err := wg.Wait()
if err != nil {
return nil, fmt.Errorf("error retrieving user dashboards data: %v", err)
}

// Fill the result
for _, validatorDashboard := range validatorDashboardMap {
validatorDashboard.GroupCount = validatorDashboardCountMap[validatorDashboard.Id].GroupCount
validatorDashboard.ValidatorCount = validatorDashboardCountMap[validatorDashboard.Id].ValidatorCount

result.ValidatorDashboards = append(result.ValidatorDashboards, *validatorDashboard)
}

Expand All @@ -614,11 +664,11 @@ func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64

// return number of active / archived dashboards
func (d *DataAccessService) GetUserValidatorDashboardCount(ctx context.Context, userId uint64, active bool) (uint64, error) {
// @DATA-ACCESS return number of dashboards depending on archival status (see comment above)
var count uint64
err := d.alloyReader.GetContext(ctx, &count, `
SELECT COUNT(*) FROM users_val_dashboards
WHERE user_id = $1
`, userId)
WHERE user_id = $1 AND (($2 AND is_archived IS NULL) OR (NOT $2 AND is_archived IS NOT NULL))
`, userId, active)

return count, err
}
112 changes: 108 additions & 4 deletions backend/pkg/api/data_access/vdb_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sort"
"strconv"
"strings"
"sync"

"github.com/doug-martin/goqu/v9"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -59,8 +60,96 @@ func (d *DataAccessService) GetValidatorDashboardInfoByPublicId(ctx context.Cont
}

func (d *DataAccessService) GetValidatorDashboard(ctx context.Context, dashboardId t.VDBId) (*t.ValidatorDashboard, error) {
// TODO @DATA-ACCESS
return d.dummy.GetValidatorDashboard(ctx, dashboardId)
result := &t.ValidatorDashboard{}

wg := errgroup.Group{}
mutex := &sync.RWMutex{}

wg.Go(func() error {
dbReturn := []struct {
Name string `db:"name"`
IsArchived sql.NullString `db:"is_archived"`
PublicId sql.NullString `db:"public_id"`
PublicName sql.NullString `db:"public_name"`
SharedGroups sql.NullBool `db:"shared_groups"`
}{}

err := d.alloyReader.SelectContext(ctx, &dbReturn, `
SELECT
uvd.name,
uvd.is_archived,
uvds.public_id,
uvds.name AS public_name,
uvds.shared_groups
FROM users_val_dashboards uvd
LEFT JOIN users_val_dashboards_sharing uvds ON uvd.id = uvds.dashboard_id
WHERE uvd.id = $1
`, dashboardId.Id)
if err != nil {
return err
}

if len(dbReturn) == 0 {
return fmt.Errorf("error dashboard with id %v not found", dashboardId)
}

mutex.Lock()
result.Id = uint64(dashboardId.Id)
result.Name = dbReturn[0].Name
result.IsArchived = dbReturn[0].IsArchived.Valid
result.ArchivedReason = dbReturn[0].IsArchived.String

for _, row := range dbReturn {
if row.PublicId.Valid {
publicId := t.VDBPublicId{}
publicId.PublicId = row.PublicId.String
publicId.Name = row.PublicName.String
publicId.ShareSettings.ShareGroups = row.SharedGroups.Bool

result.PublicIds = append(result.PublicIds, publicId)
}
}
mutex.Unlock()

return nil
})

wg.Go(func() error {
dbReturn := struct {
GroupCount uint64 `db:"group_count"`
ValidatorCount uint64 `db:"validator_count"`
}{}

err := d.alloyReader.GetContext(ctx, &dbReturn, `
WITH dashboards_groups AS
(SELECT COUNT(uvdg.id) AS group_count FROM users_val_dashboards_groups uvdg WHERE uvdg.dashboard_id = $1),
dashboards_validators AS
(SELECT COUNT(uvdv.validator_index) AS validator_count FROM users_val_dashboards_validators uvdv WHERE uvdv.dashboard_id = $1)
SELECT
dashboards_groups.group_count,
dashboards_validators.validator_count
FROM
dashboards_groups,
dashboards_validators
`, dashboardId.Id)
if err != nil {
return err
}

mutex.Lock()
result.GroupCount = dbReturn.GroupCount
result.ValidatorCount = dbReturn.ValidatorCount
mutex.Unlock()

return nil
})

err := wg.Wait()
if err != nil {
return nil, fmt.Errorf("error retrieving user dashboards data: %v", err)
}

return result, nil
}

func (d *DataAccessService) GetValidatorDashboardName(ctx context.Context, dashboardId t.VDBIdPrimary) (string, error) {
Expand Down Expand Up @@ -188,8 +277,23 @@ func (d *DataAccessService) RemoveValidatorDashboard(ctx context.Context, dashbo
}

func (d *DataAccessService) UpdateValidatorDashboardArchiving(ctx context.Context, dashboardId t.VDBIdPrimary, archived bool) (*t.VDBPostArchivingReturnData, error) {
// TODO @DATA-ACCESS
return d.dummy.UpdateValidatorDashboardArchiving(ctx, dashboardId, archived)
result := &t.VDBPostArchivingReturnData{}

var archivedReason *string
if archived {
reason := enums.VDBArchivedReasons.User.ToString()
archivedReason = &reason
}

err := d.alloyWriter.GetContext(ctx, result, `
UPDATE users_val_dashboards SET is_archived = $1 WHERE id = $2
RETURNING id, is_archived IS NOT NULL AS is_archived
`, archivedReason, dashboardId)
if err != nil {
return nil, err
}

return result, nil
}

func (d *DataAccessService) UpdateValidatorDashboardName(ctx context.Context, dashboardId t.VDBIdPrimary, name string) (*t.VDBPostReturnData, error) {
Expand Down
45 changes: 45 additions & 0 deletions backend/pkg/api/enums/validator_dashboard_enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,51 @@ var VDBManageValidatorsColumns = struct {
VDBManageValidatorsWithdrawalCredential,
}

// ----------------
// Validator Dashboard Archived Reasons

type VDBArchivedReason int

var _ Enum = VDBArchivedReason(0)

const (
VDBArchivedUser VDBArchivedReason = iota
VDBArchivedDashboards
VDBArchivedGroups
VDBArchivedValidators
)

func (r VDBArchivedReason) Int() int {
return int(r)
}

func (r VDBArchivedReason) ToString() string {
switch r {
case VDBArchivedUser:
return "user"
case VDBArchivedDashboards:
return "dashboard_limit"
case VDBArchivedGroups:
return "group_limit"
case VDBArchivedValidators:
return "validator_limit"
default:
return ""
}
}

var VDBArchivedReasons = struct {
User VDBArchivedReason
Dashboards VDBArchivedReason
Groups VDBArchivedReason
Validators VDBArchivedReason
}{
VDBArchivedUser,
VDBArchivedDashboards,
VDBArchivedGroups,
VDBArchivedValidators,
}

// ----------------
// Validator Reward Chart Efficiency Filter

Expand Down
48 changes: 26 additions & 22 deletions backend/pkg/api/handlers/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ func (h *HandlerService) InternalPutValidatorDashboardArchiving(w http.ResponseW
returnOk(w, types.ApiDataResponse[types.VDBPostArchivingReturnData]{
Data: types.VDBPostArchivingReturnData{Id: uint64(dashboardId), IsArchived: req.IsArchived},
})
return
}

userId, err := GetUserIdByContext(r)
Expand All @@ -471,28 +472,31 @@ func (h *HandlerService) InternalPutValidatorDashboardArchiving(w http.ResponseW
handleErr(w, err)
return
}
if req.IsArchived {
if dashboardCount >= maxArchivedDashboardsCount {
returnConflict(w, errors.New("maximum number of archived validator dashboards reached"))
return
}
} else {
userInfo, err := h.dai.GetUserInfo(r.Context(), userId)
if err != nil {
handleErr(w, err)
return
}
if dashboardCount >= userInfo.PremiumPerks.ValidatorDasboards && !isUserAdmin(userInfo) {
returnConflict(w, errors.New("maximum number of active validator dashboards reached"))
return
}
if dashboardInfo.GroupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard && !isUserAdmin(userInfo) {
returnConflict(w, errors.New("maximum number of groups in dashboards reached"))
return
}
if dashboardInfo.ValidatorCount >= userInfo.PremiumPerks.ValidatorsPerDashboard && !isUserAdmin(userInfo) {
returnConflict(w, errors.New("maximum number of validators in dashboards reached"))
return

userInfo, err := h.dai.GetUserInfo(r.Context(), userId)
if err != nil {
handleErr(w, err)
return
}
if !isUserAdmin(userInfo) {
if req.IsArchived {
if dashboardCount >= maxArchivedDashboardsCount {
returnConflict(w, errors.New("maximum number of archived validator dashboards reached"))
return
}
} else {
if dashboardCount >= userInfo.PremiumPerks.ValidatorDasboards {
returnConflict(w, errors.New("maximum number of active validator dashboards reached"))
return
}
if dashboardInfo.GroupCount >= userInfo.PremiumPerks.ValidatorGroupsPerDashboard {
returnConflict(w, errors.New("maximum number of groups in dashboards reached"))
return
}
if dashboardInfo.ValidatorCount >= userInfo.PremiumPerks.ValidatorsPerDashboard {
returnConflict(w, errors.New("maximum number of validators in dashboards reached"))
return
}
}
}

Expand Down
Loading
Loading