Skip to content
This repository was archived by the owner on Jun 26, 2025. It is now read-only.
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
34 changes: 22 additions & 12 deletions db/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,32 +336,42 @@ func (pg *PostgresDB) EditNode(ctx context.Context, user db.User, nodeID string,
}
func (pg *PostgresDB) AddEdgeWeightVote(ctx context.Context, user db.User, edgeID string, weight float64) error {
return pg.db.Transaction(func(tx *gorm.DB) error {
edgeedit := EdgeEdit{
EdgeID: atoi(edgeID),
UserID: atoi(user.Key),
Type: db.EdgeEditTypeVote,
Weight: weight,
}
if err := tx.Create(&edgeedit).Error; err != nil {
return err
}
{
// TODO(skep): should move aggregation to separate module/application
edge := Edge{Model: gorm.Model{ID: atoi(edgeID)}}
if err := tx.First(&edge).Error; err != nil {
return err
}
edits := []EdgeEdit{}
if err := tx.Where(&EdgeEdit{EdgeID: edge.ID}).Find(&edits).Error; err != nil {
query := `
WITH RankedVotes AS (
SELECT *,
-- Assign rank to each vote per user, most recent first
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) as rownumber
FROM edge_edits
WHERE edge_id = ?
)
-- Select only the most recent vote for each user (i.e. rownumber 1)
SELECT * FROM RankedVotes WHERE rownumber = 1;
`
if err := tx.Raw(query, edge.ID).Scan(&edits).Error; err != nil {
return err
}
sum := db.Sum(edits, func(edit EdgeEdit) float64 { return edit.Weight })
averageWeight := (sum + weight) / float64(len(edits)+1)
edge.Weight = averageWeight
edge.Weight = sum / float64(len(edits))
if err := tx.Save(&edge).Error; err != nil {
return err
}
}
edgeedit := EdgeEdit{
EdgeID: atoi(edgeID),
UserID: atoi(user.Key),
Type: db.EdgeEditTypeVote,
Weight: weight,
}
if err := tx.Create(&edgeedit).Error; err != nil {
return err
}
return nil
})
}
Expand Down
116 changes: 96 additions & 20 deletions db/postgres/postgres_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,38 +187,114 @@ func TestPostgresDB_CreateEdge(t *testing.T) {

func TestPostgresDB_AddEdgeWeightVote(t *testing.T) {
for _, test := range []struct {
Name string
Name string
TargetEdgeID uint
PreexistingNodes []Node
PreexistingEdges []Edge
PreexistingEdgeEdits []EdgeEdit
PreexistingUsers []User
ExpectedWeight float64
ExpectedEdgeEdits int
}{
{
Name: "good case",
Name: "three votes, all from different users",
TargetEdgeID: 88,
PreexistingUsers: []User{
{Model: gorm.Model{ID: 111}, Username: "asdf", PasswordHash: "000", EMail: "a@b"},
{Model: gorm.Model{ID: 222}, Username: "fasd", PasswordHash: "111", EMail: "c@d"},
{Model: gorm.Model{ID: 333}, Username: "dfas", PasswordHash: "222", EMail: "e@f"},
},
PreexistingNodes: []Node{
{Model: gorm.Model{ID: 1}, Description: db.Text{"en": "A"}},
{Model: gorm.Model{ID: 2}, Description: db.Text{"en": "B"}},
},
PreexistingEdges: []Edge{
{Model: gorm.Model{ID: 88}, FromID: 1, ToID: 2, Weight: 10},
{Model: gorm.Model{ID: 99}, FromID: 2, ToID: 1, Weight: 5},
},
PreexistingEdgeEdits: []EdgeEdit{
{EdgeID: 88, UserID: 222, Weight: 10, Type: db.EdgeEditTypeCreate},
{EdgeID: 88, UserID: 333, Weight: 5, Type: db.EdgeEditTypeVote},
},
ExpectedWeight: 6.33333333333333333, // = (10.0 + 5 + 4) / 3
ExpectedEdgeEdits: 3, // two existing ones plus the new vote from the test case
},
{
Name: "three votes, but two from the same user (new vote unaffected)",
TargetEdgeID: 88,
PreexistingUsers: []User{
{Model: gorm.Model{ID: 111}, Username: "asdf", PasswordHash: "000", EMail: "a@b"},
{Model: gorm.Model{ID: 222}, Username: "fasd", PasswordHash: "111", EMail: "c@d"},
{Model: gorm.Model{ID: 333}, Username: "dfas", PasswordHash: "222", EMail: "e@f"},
},
PreexistingNodes: []Node{
{Model: gorm.Model{ID: 1}, Description: db.Text{"en": "A"}},
{Model: gorm.Model{ID: 2}, Description: db.Text{"en": "B"}},
},
PreexistingEdges: []Edge{
{Model: gorm.Model{ID: 88}, FromID: 1, ToID: 2, Weight: 10},
{Model: gorm.Model{ID: 99}, FromID: 2, ToID: 1, Weight: 5},
},
PreexistingEdgeEdits: []EdgeEdit{
{EdgeID: 88, UserID: 222, Weight: 5, Type: db.EdgeEditTypeCreate},
{EdgeID: 88, UserID: 222, Weight: 10, Type: db.EdgeEditTypeVote},
},
ExpectedWeight: 7.0, // = (10.0 + 4) / 2
ExpectedEdgeEdits: 3, // two existing ones plus the new vote from the test case, but only two are counted since only a single vote per user is taken
},
{
Name: "three votes, but two from the same user (new vote creates duplicate)",
TargetEdgeID: 88,
PreexistingUsers: []User{
{Model: gorm.Model{ID: 111}, Username: "asdf", PasswordHash: "000", EMail: "a@b"},
{Model: gorm.Model{ID: 222}, Username: "fasd", PasswordHash: "111", EMail: "c@d"},
{Model: gorm.Model{ID: 333}, Username: "dfas", PasswordHash: "222", EMail: "e@f"},
},
PreexistingNodes: []Node{
{Model: gorm.Model{ID: 1}, Description: db.Text{"en": "A"}},
{Model: gorm.Model{ID: 2}, Description: db.Text{"en": "B"}},
},
PreexistingEdges: []Edge{
{Model: gorm.Model{ID: 88}, FromID: 1, ToID: 2, Weight: 10},
{Model: gorm.Model{ID: 99}, FromID: 2, ToID: 1, Weight: 5},
},
PreexistingEdgeEdits: []EdgeEdit{
{EdgeID: 88, UserID: 222, Weight: 10, Type: db.EdgeEditTypeCreate},
{EdgeID: 88, UserID: 111, Weight: 5, Type: db.EdgeEditTypeVote},
},
ExpectedWeight: 7.0,
ExpectedEdgeEdits: 3,
},
} {
t.Run(test.Name, func(t *testing.T) {
pg := setupDB(t)
ctx := context.Background()
assert := assert.New(t)
A := Node{Description: db.Text{"en": "A"}}
assert.NoError(pg.db.Create(&A).Error)
B := Node{Description: db.Text{"en": "B"}}
assert.NoError(pg.db.Create(&B).Error)
user := User{Username: "123", PasswordHash: "000", EMail: "a@b"}
assert.NoError(pg.db.Create(&user).Error)
edge := Edge{Model: gorm.Model{ID: 88}, From: A, To: B, Weight: 10}
assert.NoError(pg.db.Create(&edge).Error)
assert.NoError(pg.db.Create(&Edge{Model: gorm.Model{ID: 99}, From: B, To: A, Weight: 5}).Error)
existing_edits := []EdgeEdit{
{EdgeID: edge.ID, UserID: user.ID, Weight: 10, Type: db.EdgeEditTypeCreate},
{EdgeID: 99, UserID: user.ID, Weight: 5, Type: db.EdgeEditTypeCreate},
for _, node := range test.PreexistingNodes {
assert.NoError(pg.db.Create(&node).Error)
}
assert.NoError(pg.db.Create(&existing_edits).Error)
currentUser := db.User{Document: db.Document{Key: itoa(user.ID)}}
err := pg.AddEdgeWeightVote(ctx, currentUser, itoa(edge.ID), 4)
for _, edge := range test.PreexistingEdges {
assert.NoError(pg.db.Create(&edge).Error)
}
for _, user := range test.PreexistingUsers {
assert.NoError(pg.db.Create(&user).Error)
}
for _, edgeedit := range test.PreexistingEdgeEdits {
assert.NoError(pg.db.Create(&edgeedit).Error)
}
currentUser := db.User{Document: db.Document{Key: itoa(111)}}
err := pg.AddEdgeWeightVote(ctx, currentUser, itoa(test.TargetEdgeID), 4)
assert.NoError(err)
edgeedits := []EdgeEdit{}
assert.NoError(pg.db.Where(&EdgeEdit{EdgeID: edge.ID}).Find(&edgeedits).Error)
assert.Len(edgeedits, 2)
assert.NoError(pg.db.Where(&EdgeEdit{EdgeID: test.TargetEdgeID}).Find(&edgeedits).Error)
t.Log("edge edits for target edge:")
for _, edit := range edgeedits {
t.Logf("edgeID:%v userID:%v weight:%v type:%v", edit.EdgeID, edit.UserID, edit.Weight, edit.Type)
}
assert.Len(edgeedits, test.ExpectedEdgeEdits)
edge := Edge{}
assert.NoError(pg.db.First(&edge).Error)
assert.Equal(7.0, edge.Weight)
assert.Equal(test.ExpectedWeight, edge.Weight)
})
}
}
Expand Down