Skip to content
Draft
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
13 changes: 9 additions & 4 deletions backend/main/models/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Community struct {
Timestamp string `json:"timestamp" validate:"required"`
Composite_signatures *[]s.CompositeSignature `json:"compositeSignatures"`
Creator_addr string `json:"creatorAddr" validate:"required"`
Creator_image *string `json:"creatorImage,omitempty"`
Signing_addr *string `json:"signingAddr,omitempty"`
Voucher *shared.Voucher `json:"voucher,omitempty"`
Created_at *time.Time `json:"createdAt,omitempty"`
Expand Down Expand Up @@ -96,7 +97,7 @@ type CanUserCreateProposalResponse struct {
IsAuthor bool `json:"isAuthor"`
HasPermission bool `json:"hasPermission"`
Reason string `json:"reason,omitempty"`
Contract_type string `json:"contractType,omitempty"`
Contract_type string `json:"contractType,omitempty"`
Error error `json:"error,omitempty"`
}

Expand Down Expand Up @@ -201,15 +202,19 @@ func GetCommunityTypes(db *s.Database) ([]*CommunityType, error) {

func (c *Community) GetCommunity(db *s.Database) error {
return pgxscan.Get(db.Context, db.Conn, c,
`SELECT * from communities WHERE id = $1`,
c.ID)
`SELECT communities.*, users.profile_image AS creator_image
FROM communities
JOIN users on users.addr = communities.creator_addr
WHERE id = $1`, c.ID)
}

func GetCommunities(db *s.Database, pageParams shared.PageParams) ([]*Community, int, error) {
var communities []*Community
err := pgxscan.Select(db.Context, db.Conn, &communities,
`
SELECT * FROM communities
SELECT communities.*, users.profile_image AS creator_image
FROM communities
JOIN users on users.addr = communities.creator_addr
LIMIT $1 OFFSET $2
`, pageParams.Count, pageParams.Start)

Expand Down
16 changes: 13 additions & 3 deletions backend/main/models/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Proposal struct {
Max_weight *float64 `json:"maxWeight,omitempty"`
Min_balance *float64 `json:"minBalance,omitempty"`
Creator_addr string `json:"creatorAddr" validate:"required"`
Creator_image *string `json:"creatorImage,omitempty"`
Start_time time.Time `json:"startTime" validate:"required"`
Result *string `json:"result,omitempty"`
End_time time.Time `json:"endTime" validate:"required"`
Expand Down Expand Up @@ -87,7 +88,14 @@ func GetProposalsForCommunity(
var err error

// Get Proposals
sql := fmt.Sprintf(`SELECT *, %s FROM proposals WHERE community_id = $3`, computedStatusSQL)
sql := fmt.Sprintf(
`SELECT p.*, u.profile_image as creator_image, %s
FROM proposals as p
left join users as u on u.addr = p.creator_addr
WHERE community_id = $3
`,
computedStatusSQL,
)

statusesFilterSql := generateStatusesFilterSQL(statuses)
orderBySql := fmt.Sprintf(` ORDER BY created_at %s`, params.Order)
Expand All @@ -114,10 +122,12 @@ func GetProposalsForCommunity(

func (p *Proposal) GetProposalById(db *s.Database) error {
sql := `
SELECT p.*, %s, count(v.id) as total_votes from proposals as p
SELECT p.*, %s, u.profile_image as creator_image,
COUNT(v.id) as total_votes from proposals as p
left join votes as v on v.proposal_id = p.id
left join users as u on u.addr = p.creator_addr
WHERE p.id = $1
GROUP BY p.id`
GROUP BY p.id, u.profile_image`
sql = fmt.Sprintf(sql, computedStatusSQL)
return pgxscan.Get(db.Context, db.Conn, p, sql, p.ID)
}
Expand Down
114 changes: 114 additions & 0 deletions backend/main/models/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package models

import (
"github.com/DapperCollectives/CAST/backend/main/shared"
"github.com/google/uuid"
)

type User struct {
Uuid *string `json:"uuid,omitempty"`
Addr *string `json:"address,validate:required"`
Composite_signatures *[]shared.CompositeSignature `json:"compositeSignatures,validate:required"`
Timestamp *string `json:"timestamp,validate:required"`
Profile_image *string `json:"profileImage,omitempty"`
Name *string `json:"name,omitempty"`
Website *string `json:"website,omitempty"`
Bio *string `json:"bio,omitempty"`
Twitter *string `json:"twitter,omitempty"`
Discord *string `json:"discord,omitempty"`
Instagram *string `json:"instagram,omitempty"`
}

func (u *User) CreateUser(db *shared.Database, payload *User) error {
err := db.Conn.QueryRow(db.Context,
`INSERT INTO users (uuid, addr, profile_image, name, website, bio,
twitter, discord, instagram)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING *`,
uuid.New(),
payload.Addr,
payload.Profile_image,
payload.Name,
payload.Website,
payload.Bio,
payload.Twitter,
payload.Discord,
payload.Instagram).
Scan(
&u.Uuid,
&u.Addr,
&u.Profile_image,
&u.Name,
&u.Website,
&u.Bio,
&u.Twitter,
&u.Discord,
&u.Instagram,
)

if err != nil {
return err
}
return nil
}

func (u *User) GetUser(db *shared.Database, addr string) error {
err := db.Conn.QueryRow(
db.Context,
"SELECT * FROM users WHERE addr = $1",
addr).Scan(
&u.Uuid,
&u.Addr,
&u.Profile_image,
&u.Name,
&u.Website,
&u.Bio,
&u.Twitter,
&u.Discord,
&u.Instagram,
)

if err != nil {
return err
}
return nil
}

func (u *User) UpdateUser(db *shared.Database, payload *User) error {
err := db.Conn.QueryRow(
db.Context,
`UPDATE users
SET profile_image = COALESCE($1,profile_image),
name = COALESCE($2, name),
website = COALESCE($3, website),
bio = COALESCE($4, bio),
twitter = COALESCE($5, twitter),
discord = COALESCE($6, discord),
instagram = COALESCE($7, instagram)
WHERE addr = $8
RETURNING *`,
payload.Profile_image,
payload.Name,
payload.Website,
payload.Bio,
payload.Twitter,
payload.Discord,
payload.Instagram,
payload.Addr,
).Scan(
&u.Uuid,
&u.Addr,
&u.Profile_image,
&u.Name,
&u.Website,
&u.Bio,
&u.Twitter,
&u.Discord,
&u.Instagram,
)

if err != nil {
return err
}
return nil
}
68 changes: 68 additions & 0 deletions backend/main/server/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ var (
Details: "The proposal you are trying to access no longer exists.",
}

errUserNotFound = errorResponse{
StatusCode: http.StatusNotFound,
ErrorCode: "ERR_1015",
Message: "User not found",
Details: "The user you are trying to access does not exist.",
}

nilErr = errorResponse{}
)

Expand Down Expand Up @@ -910,6 +917,67 @@ func (a *App) createCommunityUser(w http.ResponseWriter, r *http.Request) {
respondWithJSON(w, http.StatusCreated, "OK")
}

func (a *App) getUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
addr := vars["addr"]

var user models.User
if err := user.GetUser(a.DB, addr); err != nil {
log.Error().Err(err).Msg("Error user not found")
respondWithError(w, errUserNotFound)
return
}

respondWithJSON(w, http.StatusOK, user)
}

func (a *App) createUser(w http.ResponseWriter, r *http.Request) {
payload := models.User{}

if err := validatePayload(r.Body, &payload); err != nil {
log.Error().Err(err).Msg("Error validating payload")
respondWithError(w, errIncompleteRequest)
return
}

if err := helpers.validateUser(
*payload.Addr,
*payload.Timestamp,
payload.Composite_signatures,
); err != nil {
log.Error().Err(err).Msg("Error validating signature")
respondWithError(w, errIncompleteRequest)
return
}

var user models.User
if err := user.CreateUser(a.DB, &payload); err != nil {
log.Error().Err(err).Msg("Error creating user")
respondWithError(w, errIncompleteRequest)
return
}
respondWithJSON(w, http.StatusCreated, user)
}

func (a *App) updateUser(w http.ResponseWriter, r *http.Request) {
payload := models.User{}

if err := validatePayload(r.Body, &payload); err != nil {
log.Error().Err(err).Msg("Error validating payload")
respondWithError(w, errIncompleteRequest)
return
}

var user models.User

if err := user.UpdateUser(a.DB, &payload); err != nil {
log.Error().Err(err).Msg("Error updating user")
respondWithError(w, errUserNotFound)
return
}
respondWithJSON(w, http.StatusOK, user)
}

func (a *App) getCommunityUsers(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
communityId, err := strconv.Atoi(vars["communityId"])
Expand Down
1 change: 0 additions & 1 deletion backend/main/server/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,6 @@ func (h *Helpers) validateTimestamp(timestamp string, expiry int) error {
}

func (h *Helpers) validateUser(addr, timestamp string, compositeSignatures *[]shared.CompositeSignature) error {

if err := h.validateTimestamp(timestamp, 60); err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions backend/main/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func (a *App) initializeRoutes() {
a.Router.HandleFunc("/voting-strategies", a.getVotingStrategies).Methods("GET")
a.Router.HandleFunc("/community-categories", a.getCommunityCategories).Methods("GET")
// Users
a.Router.HandleFunc("/user/{addr:0x[a-zA-Z0-9]+}", a.getUser).Methods("GET")
a.Router.HandleFunc("/user", a.updateUser).Methods("PUT", "OPTIONS")
a.Router.HandleFunc("/user", a.createUser).Methods("POST", "OPTIONS")

a.Router.HandleFunc("/users/{addr:0x[a-zA-Z0-9]{16}}/communities", a.getUserCommunities).Methods("GET")
a.Router.HandleFunc("/users/{addr:0x[a-zA-Z0-9]{16}}/proposals", a.getUserProposals).Methods("GET")
a.Router.HandleFunc("/communities/{communityId:[0-9]+}/users", a.createCommunityUser).Methods("POST", "OPTIONS")
Expand Down
1 change: 1 addition & 0 deletions backend/migrations/000044_create_user_table.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS users;
12 changes: 12 additions & 0 deletions backend/migrations/000044_create_user_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE users (
uuid UUID PRIMARY KEY NOT NULL,
addr VARCHAR(255) NOT NULL,
created_at TIMESTAMP without time zone default (now() at time zone 'utc'),
profile_image TEXT,
name VARCHAR(50),
website VARCHAR(50),
bio VARCHAR(255),
twitter VARCHAR(50),
discord VARCHAR(50),
instagram VARCHAR(50)
);
79 changes: 79 additions & 0 deletions backend/tests/test_utils/user_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package test_utils

import (
"bytes"
"encoding/json"
"fmt"
"time"

"net/http"
"net/http/httptest"

"github.com/DapperCollectives/CAST/backend/main/models"
)

var (
dummyProfileImage = "https://pbs.twimg.com/profile_images/1277734310/IMG_0001_400x400.JPG"
dummyName = "Test User"
dummyBio = "This is a test bio"
dummyTwitter = "https://twitter.com/testuser"
dummyDiscord = "https://discord.com/testuser"
dummyInstagram = "https://instagram.com/testuser"
)

func (otu *OverflowTestUtils) CreateUserAPI(user *models.User) *httptest.ResponseRecorder {
json, _ := json.Marshal(user)
req, _ := http.NewRequest("POST", "/user", bytes.NewBuffer(json))
req.Header.Set("Content-Type", "application/json")
return otu.ExecuteRequest(req)
}

func (otu *OverflowTestUtils) GetUserAPI(user *models.User) *httptest.ResponseRecorder {
req, _ := http.NewRequest("GET", fmt.Sprintf("/user/%s", *user.Addr), nil)
return otu.ExecuteRequest(req)
}

func (otu *OverflowTestUtils) UpdateUserAPI(user *models.User) *httptest.ResponseRecorder {
json, _ := json.Marshal(user)
req, _ := http.NewRequest("PUT", "/user", bytes.NewBuffer(json))
req.Header.Set("Content-Type", "application/json")
return otu.ExecuteRequest(req)
}

func (otu *OverflowTestUtils) GenerateUserStruct(signer string) *models.User {
account, _ := otu.O.State.Accounts().ByName(fmt.Sprintf("emulator-%s", signer))
address := fmt.Sprintf("0x%s", account.Address().String())

return &models.User{
Addr: &address,
Profile_image: &dummyProfileImage,
Name: &dummyName,
Bio: &dummyBio,
Twitter: &dummyTwitter,
Discord: &dummyDiscord,
Instagram: &dummyInstagram,
}
}

func (otu *OverflowTestUtils) GenerateUserPayload(signer string, user models.User) *models.User {
payload := user
timestamp := fmt.Sprint(time.Now().UnixNano() / int64(time.Millisecond))
compositeSignatures := otu.GenerateCompositeSignatures(signer, timestamp)
payload.Timestamp = &timestamp
payload.Composite_signatures = compositeSignatures
return &payload
}

func (otu *OverflowTestUtils) GenerateFailUserStruct() *models.User {
nonExistentAccount := "0x696969"

return &models.User{
Addr: &nonExistentAccount,
Profile_image: &dummyProfileImage,
Name: &dummyName,
Bio: &dummyBio,
Twitter: &dummyTwitter,
Discord: &dummyDiscord,
Instagram: &dummyInstagram,
}
}
Loading