Commit 76ac7617 authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Improve friend add queries. Merge #55

parent e305cdb5
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -7,6 +7,13 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p
### Added
- Optionally allow JSON encoding in user login/register operations and responses.

### Changed
- Improve user email storage and comparison.
- Allow group batch fetch by both ID and name.

### Fixed
- Fix Facebook unlink operation.

## [0.12.0] - 2017-03-19
### Added
- Dynamic leaderboards feature.
+12 −1
Original line number Diff line number Diff line
@@ -300,8 +300,19 @@ message TGroupRemove {

message TGroupsSelfList {}
message TGroupsFetch {
  message GroupIds {
    repeated bytes group_ids = 1;
  }

  message Names {
    repeated string names = 1;
  }

  oneof set {
    GroupIds group_ids = 1;
    Names names = 2;
  }
}
message TGroupsList {
  int64 page_limit = 1;
  bool order_by_asc = 2;
+36 −29
Original line number Diff line number Diff line
@@ -211,7 +211,7 @@ func (p *pipeline) friendAdd(l zap.Logger, session *session, envelope *Envelope)
	logger := l.With(zap.String("friend_id", friendID.String()))
	friendIDBytes := friendID.Bytes()

	if friendID.String() == session.userID.String() {
	if friendID == session.userID {
		logger.Warn("Cannot add self", zap.Error(err))
		session.Send(ErrorMessageBadInput(envelope.CollationId, "Cannot add self"))
		return
@@ -245,50 +245,57 @@ func (p *pipeline) friendAdd(l zap.Logger, session *session, envelope *Envelope)
	}()

	updatedAt := nowMs()
	res, err := tx.Exec("UPDATE user_edge SET state = 0, updated_at = $3 WHERE source_id = $1 AND destination_id = $2 AND state = 2", friendIDBytes, session.userID.Bytes(), updatedAt)
	// Mark an invite as accepted, if one was in place.
	res, err := tx.Exec(`
UPDATE user_edge SET state = 0, updated_at = $3
WHERE (source_id = $1 AND destination_id = $2 AND state = 2)
OR (source_id = $2 AND destination_id = $1 AND state = 1)
  `, friendIDBytes, session.userID.Bytes(), updatedAt)
	if err != nil {
		return
	}

	state := 2
	rowsAffected, _ := res.RowsAffected()
	if rowsAffected == 1 {
		state = 0
	// If both edges were updated, it was accepting an invite was successful.
	if rowsAffected, _ := res.RowsAffected(); rowsAffected == 2 {
		return
	}

	// If no edge updates took place, it's a new invite being set up.
	res, err = tx.Exec(`
INSERT INTO user_edge (source_id, destination_id, state, position, updated_at)
SELECT $1, $2, $3, $4, $4
WHERE EXISTS (SELECT id FROM users WHERE id=$2)
	`, session.userID.Bytes(), friendIDBytes, state, updatedAt)
SELECT source_id, destination_id, state, position, updated_at
FROM (VALUES
  ($1::BYTEA, $2::BYTEA, 2, $3::BIGINT, $3::BIGINT),
  ($2::BYTEA, $1::BYTEA, 1, $3::BIGINT, $3::BIGINT)
) AS ue(source_id, destination_id, state, position, updated_at)
WHERE EXISTS (SELECT id FROM users WHERE id = $2::BYTEA)
	`, session.userID.Bytes(), friendIDBytes, updatedAt)
	if err != nil {
		return
	}

	rowsAffected, _ = res.RowsAffected()
	if rowsAffected == 0 {
		err = errors.New("did not find friend ID in users table")
	// An invite was successfully added if both components were inserted.
	if rowsAffected, _ := res.RowsAffected(); rowsAffected != 2 {
		err = errors.New("user ID not found or unavailable")
		return
	}

	if state == 2 {
		_, err = tx.Exec("INSERT INTO user_edge (source_id, destination_id, state, position, updated_at) VALUES ($1, $2, $3, $4, $4)",
			friendIDBytes, session.userID.Bytes(), 1, updatedAt)

	// Update the user edge metadata counts.
	res, err = tx.Exec(`
UPDATE user_edge_metadata
SET count = count + 1, updated_at = $1
WHERE source_id = $2
OR source_id = $3`,
		updatedAt, session.userID.Bytes(), friendIDBytes)
	if err != nil {
		return
	}

		_, err = tx.Exec("UPDATE user_edge_metadata SET count = count + 1, updated_at = $1 WHERE source_id = $2", updatedAt, friendIDBytes)

		if err != nil {
	if rowsAffected, _ := res.RowsAffected(); rowsAffected != 2 {
		err = errors.New("could not update user friend counts")
		return
	}
}

	_, err = tx.Exec("UPDATE user_edge_metadata SET count = count + 1, updated_at = $1 WHERE source_id = $2", updatedAt, session.userID.Bytes())
}

func (p *pipeline) friendRemove(l zap.Logger, session *session, envelope *Envelope) {
	removeFriendRequest := envelope.GetFriendRemove()
	if len(removeFriendRequest.UserId) == 0 {
@@ -305,7 +312,7 @@ func (p *pipeline) friendRemove(l zap.Logger, session *session, envelope *Envelo
	logger := l.With(zap.String("friend_id", friendID.String()))
	friendIDBytes := friendID.Bytes()

	if friendID.String() == session.userID.String() {
	if friendID == session.userID {
		logger.Warn("Cannot remove self", zap.Error(err))
		session.Send(ErrorMessageBadInput(envelope.CollationId, "Cannot remove self"))
		return
@@ -373,7 +380,7 @@ func (p *pipeline) friendBlock(l zap.Logger, session *session, envelope *Envelop
	logger := l.With(zap.String("user_id", userID.String()))
	userIDBytes := userID.Bytes()

	if userID.String() == session.userID.String() {
	if userID == session.userID {
		logger.Warn("Cannot block self", zap.Error(err))
		session.Send(ErrorMessageBadInput(envelope.CollationId, "Cannot block self"))
		return
+29 −12
Original line number Diff line number Diff line
@@ -347,23 +347,40 @@ AND
func (p *pipeline) groupsFetch(logger zap.Logger, session *session, envelope *Envelope) {
	g := envelope.GetGroupsFetch()

	validGroupIds := make([]interface{}, 0)
	statements := make([]string, 0)
	statements := []string{}
	params := []interface{}{}

	for _, gid := range g.GroupIds {
	switch g.Set.(type) {
	case *TGroupsFetch_GroupIds_:
		for _, gid := range g.GetGroupIds().GroupIds {
			groupID, err := uuid.FromBytes(gid)
			if err != nil {
			logger.Warn("Could not get group")
		} else {
			validGroupIds = append(validGroupIds, groupID.Bytes())
			statements = append(statements, "id = $"+strconv.Itoa(len(validGroupIds)))
				params = append(params, groupID.Bytes())
				statements = append(statements, "id = $"+strconv.Itoa(len(params)))
			}
		}
	case *TGroupsFetch_Names_:
		for _, name := range g.GetNames().Names {
			params = append(params, name)
			statements = append(statements, "name = $"+strconv.Itoa(len(params)))
		}
	case nil:
		session.Send(ErrorMessageBadInput(envelope.CollationId, "A fetch set is required"))
		return
	default:
		session.Send(ErrorMessageBadInput(envelope.CollationId, "Unknown fetch set"))
		return
	}

	if len(statements) == 0 {
		session.Send(ErrorMessageBadInput(envelope.CollationId, "One or more fetch set values are required"))
		return
	}

	rows, err := p.db.Query(
		`SELECT id, creator_id, name, description, avatar_url, lang, utc_offset_ms, metadata, state, count, created_at, updated_at
FROM groups WHERE disabled_at = 0 AND ( `+strings.Join(statements, " OR ")+" )",
		validGroupIds...)
		params...)
	if err != nil {
		logger.Error("Could not get groups", zap.Error(err))
		session.Send(ErrorMessageRuntimeException(envelope.CollationId, "Could not get groups"))
@@ -893,7 +910,7 @@ func (p *pipeline) groupUserKick(l zap.Logger, session *session, envelope *Envel
		return
	}

	if userID.String() == session.userID.String() {
	if userID == session.userID {
		session.Send(ErrorMessageBadInput(envelope.CollationId, "You can't kick yourself from the group"))
		return
	}
@@ -989,7 +1006,7 @@ func (p *pipeline) groupUserPromote(l zap.Logger, session *session, envelope *En
		return
	}

	if userID.String() == session.userID.String() {
	if userID == session.userID {
		session.Send(ErrorMessageBadInput(envelope.CollationId, "You can't promote yourself"))
		return
	}
+8 −7
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import (

	"github.com/uber-go/zap"
	"golang.org/x/crypto/bcrypt"
	"strings"
)

func (p *pipeline) linkID(logger zap.Logger, session *session, envelope *Envelope) {
@@ -311,7 +312,7 @@ AND NOT EXISTS
     FROM users
     WHERE email = $2)`,
		session.userID.Bytes(),
		email.Email,
		strings.ToLower(email.Email),
		hashedPassword,
		nowMs())

@@ -435,7 +436,7 @@ AND (EXISTS (SELECT id FROM users WHERE id = $1 AND
	case *TUnlink_Facebook:
		query = `UPDATE users SET facebook_id = NULL, updated_at = $3
WHERE id = $1
AND custom_id = $2
AND facebook_id = $2
AND ((google_id IS NOT NULL
      OR gamecenter_id IS NOT NULL
      OR steam_id IS NOT NULL
@@ -447,7 +448,7 @@ AND ((google_id IS NOT NULL
	case *TUnlink_Google:
		query = `UPDATE users SET google_id = NULL, updated_at = $3
WHERE id = $1
AND custom_id = $2
AND google_id = $2
AND ((facebook_id IS NOT NULL
      OR gamecenter_id IS NOT NULL
      OR steam_id IS NOT NULL
@@ -459,7 +460,7 @@ AND ((facebook_id IS NOT NULL
	case *TUnlink_GameCenter:
		query = `UPDATE users SET gamecenter_id = NULL, updated_at = $3
WHERE id = $1
AND custom_id = $2
AND gamecenter_id = $2
AND ((facebook_id IS NOT NULL
      OR google_id IS NOT NULL
      OR steam_id IS NOT NULL
@@ -471,7 +472,7 @@ AND ((facebook_id IS NOT NULL
	case *TUnlink_Steam:
		query = `UPDATE users SET steam_id = NULL, updated_at = $3
WHERE id = $1
AND custom_id = $2
AND steam_id = $2
AND ((facebook_id IS NOT NULL
      OR google_id IS NOT NULL
      OR gamecenter_id IS NOT NULL
@@ -483,7 +484,7 @@ AND ((facebook_id IS NOT NULL
	case *TUnlink_Email:
		query = `UPDATE users SET email = NULL, password = NULL, updated_at = $3
WHERE id = $1
AND custom_id = $2
AND email = $2
AND ((facebook_id IS NOT NULL
      OR google_id IS NOT NULL
      OR gamecenter_id IS NOT NULL
@@ -491,7 +492,7 @@ AND ((facebook_id IS NOT NULL
      OR custom_id IS NOT NULL)
     OR
     EXISTS (SELECT id FROM user_device WHERE user_id = $1 LIMIT 1))`
		param = envelope.GetUnlink().GetEmail()
		param = strings.ToLower(envelope.GetUnlink().GetEmail())
	case *TUnlink_Custom:
		query = `UPDATE users SET custom_id = NULL, updated_at = $3
WHERE id = $1
Loading