Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c37d0ef
bugfix cas-168 update does not overwrite nil fields (#93)
0xmovses Jul 4, 2022
e0d282d
feat(frontend): update create community flow (#62)
germanurrus Jul 5, 2022
b56c31b
CAS-139: Add EarlyVote scoring (#84)
jbluks Jul 5, 2022
42090b3
added nft strategy to DB, created new migration (#106)
0xmovses Jul 6, 2022
5a976da
CAS-152 - use strategies form community on proposal creation (#73)
germanurrus Jul 7, 2022
3017d2e
CAS-108 - validate contract address during community creation (#83)
germanurrus Jul 7, 2022
e33e8a8
CAS-167 - list community strategies on community page (#90)
germanurrus Jul 7, 2022
f1fcb66
CAS-169 - update fields validation on community creation (#94)
germanurrus Jul 7, 2022
6d5a79b
Merge pull request #109 from DapperCollectives/main
dbslone Jul 7, 2022
6aecca9
CAS-170 - validation on Proposal & Voting step of community creation …
germanurrus Jul 7, 2022
b6e6afb
Merge pull request #110 from DapperCollectives/main
dbslone Jul 8, 2022
148dbbb
CAS-182 - fix validation for flow addresses (#111)
germanurrus Jul 8, 2022
bbb48d1
Merge pull request #112 from DapperCollectives/main
dbslone Jul 8, 2022
b8a74e8
CAS-183 - Remove reference to domain names in community creation flow…
germanurrus Jul 8, 2022
8c3cac6
CAS-185 - fix modal default (#114)
germanurrus Jul 8, 2022
30cd3fe
cancel build frontend - cancel build backend - cancel migrations
Jul 8, 2022
2f2ceb4
force update develop
Jul 8, 2022
8c43255
CAS-154 - add My Communities section to homepage (#97)
germanurrus Jul 8, 2022
1a501cd
Adding flow.json
Jul 8, 2022
5aef211
update flow community with strategies and socials (#119)
dbslone Jul 8, 2022
4ceca5d
Create community needs to match pagination payload (#120)
dbslone Jul 9, 2022
cf64097
CAS-174 - update my communities section after joining a community (#108)
germanurrus Jul 9, 2022
aff0729
Merge pull request #121 from DapperCollectives/main
dbslone Jul 9, 2022
d043ff1
CAS-206 - fix fetch user communities to accept count param (#122)
germanurrus Jul 11, 2022
b758624
CAS-205 - fix loading category when pressing back during create commu…
germanurrus Jul 11, 2022
375d108
CAS-140: Add Streak Bonus (#98)
jbluks Jul 11, 2022
3fd1175
add useCallback wrapper (#127)
germanurrus Jul 11, 2022
faed59f
CAS-140: Fix Migration File names (#128)
jbluks Jul 11, 2022
939b4ba
CAS-192 - fix avatars missing from community header area (#126)
germanurrus Jul 11, 2022
d05e119
CAS-188 - show join button when user is not connected (#125)
germanurrus Jul 11, 2022
547e2b2
Merge pull request #129 from DapperCollectives/main
dbslone Jul 11, 2022
521cc93
Update update-prod.yaml
fersan1985 Jul 12, 2022
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
4 changes: 2 additions & 2 deletions .github/workflows/update-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ jobs:
id: docker-from-release
run: |
export TAG=$(curl -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ env.github_token }}" https://api.github.com/repos/brudfyi/flow-voting-tool-argo/releases/latest | jq -r '.tag_name')
cd backend && docker build . -t bruddev/cast-frontend:$TAG && docker push bruddev/cast-frontend:$TAG
cd frontend && docker build . -t bruddev/cast-frontend:$TAG && docker push bruddev/cast-frontend:$TAG

## ARGO FROM HERE

Expand Down Expand Up @@ -156,4 +156,4 @@ jobs:
cd ./argo/voting-tool-stage-prod/frontend/environments/prod && kustomize edit set image bruddev/cast-frontend:$TAG
git config --global user.email "brunkins@brudfyi.com"
git config --global user.name "Brunkins"
git commit -am "Added new frontend value $TAG from GHA $TIME" && git push
git commit -am "Added new frontend value $TAG from GHA $TIME" && git push
2 changes: 1 addition & 1 deletion .github/workflows/update-uat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
# Trigger the workflow on push or pull request merge
push:
branches:
- uat
- uat #We need to test this environment.

env:
TAG: ${{ github.sha }}
Expand Down
1 change: 1 addition & 0 deletions backend/Dockerfile.kube
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ COPY ./go.mod ./
ADD ./main ./main
RUN go mod tidy
RUN go mod download
COPY flow.json ./

FROM vt-init as vt-build
WORKDIR /flow-voting-tool
Expand Down
120 changes: 107 additions & 13 deletions backend/main/leaderboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,124 @@ import (
"github.com/DapperCollectives/CAST/backend/main/test_utils"
"github.com/stretchr/testify/assert"
)

func TestGetCommunityLeaderboard(t *testing.T) {
clearTable("communities")
clearTable("community_users")
clearTable("user_achievements")
clearTable("proposals")
clearTable("votes")

communityId := otu.AddCommunities(1)[0]

expectedUsers := 2
expectedProposals := 2

// users get single vote for each proposal they voted on
expectedScore := expectedProposals

otu.GenerateVotes(communityId, expectedProposals, expectedUsers)

// Remove all achievements to test base case for scoring
clearTable("user_achievements")

response := otu.GetCommunityLeaderboardAPI(communityId)
checkResponseCode(t, http.StatusOK, response.Code)

var p test_utils.PaginatedResponseWithLeaderboardUser
json.Unmarshal(response.Body.Bytes(), &p)

receivedUser1Score := p.Data[0].Score
receivedUser2Score := p.Data[0].Score

assert.Equal(t, expectedUsers, len(p.Data))
assert.Equal(t, expectedScore, receivedUser1Score)
assert.Equal(t, expectedScore, receivedUser2Score)
}

func TestGetCommunityLeaderboardWithEarlyVotes(t *testing.T) {
clearTable("communities")
clearTable("community_users")
clearTable("user_achievements")
clearTable("proposals")
clearTable("votes")

communityId := otu.AddCommunities(1)[0]
proposalIdA := otu.AddActiveProposals(communityId, 1)[0]
proposalIdB := otu.AddActiveProposals(communityId, 2)[0]
proposalIdC := otu.AddActiveProposals(communityId, 3)[0]
voteChoice := "a"
earlyVoteBonus := 1
expectedUsers := 1
expectedProposals := 2

otu.CreateVoteAPI(proposalIdA, otu.GenerateValidVotePayload("user1", proposalIdA, voteChoice))
otu.CreateVoteAPI(proposalIdB, otu.GenerateValidVotePayload("user1", proposalIdB, voteChoice))
otu.CreateVoteAPI(proposalIdC, otu.GenerateValidVotePayload("user1", proposalIdC, voteChoice))
otu.CreateVoteAPI(proposalIdA, otu.GenerateValidVotePayload("user2", proposalIdA, voteChoice))
otu.CreateVoteAPI(proposalIdB, otu.GenerateValidVotePayload("user2", proposalIdB, voteChoice))
// user gets single vote for each proposal they voted on
expectedScore := expectedProposals + (expectedProposals * earlyVoteBonus)

otu.GenerateEarlyVoteAchievements(communityId, expectedProposals, expectedUsers)

response := otu.GetCommunityLeaderboardAPI(communityId)
checkResponseCode(t, http.StatusOK, response.Code)

var p test_utils.PaginatedResponseWithLeaderboardUser
json.Unmarshal(response.Body.Bytes(), &p)

assert.Equal(t, 2, len(p.Data))
assert.Equal(t, 3, p.Data[0].Score)
assert.Equal(t, 2, p.Data[1].Score)
}
receivedScore := p.Data[0].Score

assert.Equal(t, expectedUsers, len(p.Data))
assert.Equal(t, expectedScore, receivedScore)
}

func TestGetCommunityLeaderboardWithSingleStreak(t *testing.T) {
clearTable("communities")
clearTable("community_users")
clearTable("user_achievements")
clearTable("proposals")
clearTable("votes")

communityId := otu.AddCommunities(1)[0]
streaks := []int{3, 4}
streakBonus := 1
expectedUsers := 2
expectedUser1Score := 3 + (1 * streakBonus)
expectedUser2Score := 4 + (1 * streakBonus)

otu.GenerateSingleStreakAchievements(communityId, streaks)

response := otu.GetCommunityLeaderboardAPI(communityId)
checkResponseCode(t, http.StatusOK, response.Code)

var p test_utils.PaginatedResponseWithLeaderboardUser
json.Unmarshal(response.Body.Bytes(), &p)

receivedUser1Score := p.Data[0].Score
receivedUser2Score := p.Data[1].Score

assert.Equal(t, expectedUsers, len(p.Data))
assert.Equal(t, expectedUser1Score, receivedUser1Score)
assert.Equal(t, expectedUser2Score, receivedUser2Score)
}

func TestGetCommunityLeaderboardWithMultiStreaks(t *testing.T) {
clearTable("communities")
clearTable("community_users")
clearTable("user_achievements")
clearTable("proposals")
clearTable("votes")
communityId := otu.AddCommunities(1)[0]
streaks := []int{3, 4}
streakBonus := 1
expectedUsers := 1

// user with 7 votes and 2 streaks
expectedUser1Score := 7 + (2 * streakBonus)

otu.GenerateMultiStreakAchievements(communityId, streaks)

response := otu.GetCommunityLeaderboardAPI(communityId)
checkResponseCode(t, http.StatusOK, response.Code)

var p test_utils.PaginatedResponseWithLeaderboardUser
json.Unmarshal(response.Body.Bytes(), &p)

receivedUser1Score := p.Data[0].Score

assert.Equal(t, expectedUsers, len(p.Data))
assert.Equal(t, expectedUser1Score, receivedUser1Score)
}
23 changes: 17 additions & 6 deletions backend/main/models/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Community struct {
Name string `json:"name,omitempty"`
Category *string `json:"category,omitempty" validate:"required"`
Logo *string `json:"logo,omitempty"`
Body *string `json:"body,omitempty" validate:"required"`
Body *string `json:"body,omitempty"`
Strategies *[]Strategy `json:"strategies,omitempty"`
Strategy *string `json:"strategy,omitempty"`
Banner_img_url *string `json:"bannerImgUrl,omitempty"`
Expand Down Expand Up @@ -157,6 +157,7 @@ func GetCommunitiesForHomePage(db *s.Database, start, count int) ([]*Community,
GROUP BY community_id
HAVING COUNT(*) >= 2
)
OR id = 1
LIMIT $1 OFFSET $2
`, count, start)

Expand Down Expand Up @@ -192,11 +193,21 @@ func (c *Community) UpdateCommunity(db *s.Database, p *UpdateCommunityRequestPay
db.Context,
`
UPDATE communities
SET name = $1, body = $2, logo = $3, strategies = $4, strategy = $5,
banner_img_url = $6, website_url = $7, twitter_url = $8, github_url = $9,
discord_url = $10, instagram_url = $11, proposal_validation = $12,
proposal_threshold = $13, category = $14,
terms_and_conditions_url = $15
SET name = COALESCE($1, name),
body = COALESCE($2, body),
logo = COALESCE($3, logo),
strategies = COALESCE($4, strategies),
strategy = COALESCE($5, strategy),
banner_img_url = COALESCE($6, banner_img_url),
website_url = COALESCE($7, website_url),
twitter_url = COALESCE($8, twitter_url),
github_url = COALESCE($9, github_url),
discord_url = COALESCE($10, discord_url),
instagram_url = COALESCE($11, instagram_url),
proposal_validation = COALESCE($12, proposal_validation),
proposal_threshold = COALESCE($13, proposal_threshold),
category = COALESCE($14, category),
terms_and_conditions_url = COALESCE($15, terms_and_conditions_url)
WHERE id = $16
`,
p.Name,
Expand Down
100 changes: 76 additions & 24 deletions backend/main/models/community_users.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import (
"fmt"

s "github.com/DapperCollectives/CAST/backend/main/shared"
"github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgx/v4"
Expand All @@ -21,11 +23,6 @@ type CommunityUserType struct {
Is_member bool `json:"isMember" validate:"required"`
}

type LeaderboardUser struct {
Addr string `json:"addr" validate:"required"`
Score int `json:"score" validate:"required"`
}

type UserTypes []string

var USER_TYPES = UserTypes{"member", "author", "admin"}
Expand All @@ -42,6 +39,18 @@ type CommunityUserPayload struct {
Composite_signatures *[]s.CompositeSignature `json:"compositeSignatures" validate:"required"`
}

type UserAchievements = []struct {
Address string
NumVotes int
EarlyVote int
Streak int
}

type LeaderboardUserPayload struct {
Addr string `json:"addr" validate:"required"`
Score int `json:"score,omitempty"`
}

func GetUsersForCommunity(db *s.Database, communityId, start, count int) ([]CommunityUserType, int, error) {
var users = []CommunityUserType{}
err := pgxscan.Select(db.Context, db.Conn, &users,
Expand Down Expand Up @@ -98,29 +107,27 @@ func GetUsersForCommunityByType(db *s.Database, communityId, start, count int, u
return users, totalUsers, nil
}

func GetCommunityLeaderboard(db *s.Database, communityId, start, count int) ([]LeaderboardUser, int, error) {
var users = []LeaderboardUser{}
err := pgxscan.Select(db.Context, db.Conn, &users,
`
SELECT v.addr, count(*) AS score FROM votes v
JOIN proposals p ON p.id = v.proposal_id
WHERE p.community_id = $1
GROUP BY v.addr
ORDER BY score DESC
LIMIT $2 OFFSET $3
`, communityId, count, start)
func GetCommunityLeaderboard(db *s.Database, communityId, start, count int) ([]LeaderboardUserPayload, int, error) {
var leaderboardUsers = []LeaderboardUserPayload{}
var defaultEarlyVoteWeight = 1
var defaultStreakWeight = 1

if err != nil && err.Error() != pgx.ErrNoRows.Error() {
return nil, 0, err
} else if err != nil && err.Error() == pgx.ErrNoRows.Error() {
return []LeaderboardUser{}, 0, nil
userAchievements, err := getUserAchievements(db, communityId, start, count)

if err != nil {
return leaderboardUsers, 0, err
}

var totalUsers int
countSql := `SELECT COUNT(*) FROM community_users WHERE community_id = $1`
_ = db.Conn.QueryRow(db.Context, countSql, communityId).Scan(&totalUsers)
for _, user := range userAchievements {
var leaderboardUser = LeaderboardUserPayload{}
leaderboardUser.Addr = user.Address
leaderboardUser.Score = user.NumVotes + (user.EarlyVote * defaultEarlyVoteWeight) + (user.Streak * defaultStreakWeight)
leaderboardUsers = append(leaderboardUsers, leaderboardUser)
}

return users, totalUsers, nil
totalUsers := getTotalUsersForCommunity(db, communityId)

return leaderboardUsers, totalUsers, nil
}

func GetCommunitiesForUser(db *s.Database, addr string, start, count int) ([]UserCommunity, int, error) {
Expand Down Expand Up @@ -251,3 +258,48 @@ func EnsureValidRole(userType string) bool {
}
return false
}

func getTotalUsersForCommunity(db *s.Database, communityId int) int {
var totalUsers int
countSql := `SELECT COUNT(*) FROM community_users WHERE community_id = $1`
_ = db.Conn.QueryRow(db.Context, countSql, communityId).Scan(&totalUsers)
return totalUsers
}

func getUserAchievements(db *s.Database, communityId int, start int, count int) (UserAchievements, error) {
var userAchievements UserAchievements
// Retrieve each user in the community with totals for
// their votes and achievements (e.g. early votes, streaks and winning choices)
// Note 1: crosstab is a postgres extension that creates a pivot table.
// Achievements are joined as columns for each user.
// Note 2: Subselect community_id not replaced properly by $1, so has been
// substituted in string first.
sql := fmt.Sprintf(
`
SELECT v.addr as address, count(*) as num_votes,
CASE WHEN a.early_vote is NULL THEN 0 ELSE a.early_vote END as early_vote
FROM votes v
LEFT OUTER JOIN proposals p ON p.id = v.proposal_id
LEFT OUTER JOIN (
SELECT * FROM crosstab(
$$SELECT addr, achievement_type, count(*) FROM user_achievements
WHERE community_id = %d
GROUP BY addr, achievement_type
ORDER BY 1,2$$
) AS ct(address varchar(18), early_vote bigint)
) a ON v.addr = a.address
WHERE p.community_id = $1
GROUP BY v.addr, a.early_vote
LIMIT $2 OFFSET $3
`, communityId)

err := pgxscan.Select(db.Context, db.Conn, &userAchievements, sql, communityId, count, start)

if err != nil && err.Error() != pgx.ErrNoRows.Error() {
return nil, err
} else if err != nil && err.Error() == pgx.ErrNoRows.Error() {
return UserAchievements{}, nil
}

return userAchievements, nil
}
Loading