Commit f1ab88bb authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Additional group-related notifications.

parent 1174be79
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
- Allow runtime email authentication calls to authenticate with username/password instead of email/password.
- New authoritative match dispatcher function to defer message broadcasts until the end of the tick.
- New runtime function to retrieve multiple user accounts by user ID.
- Send notifications to admins of non-open groups when a user requests to join.
- Send notifications to users when their request to join a group is accepted.

### Changed
- Replace standard logger supplied to the Go runtime with a more powerful interface.
+5 −4
Original line number Diff line number Diff line
@@ -244,6 +244,7 @@ func (s *ApiServer) DeleteGroup(ctx context.Context, in *api.DeleteGroupRequest)

func (s *ApiServer) JoinGroup(ctx context.Context, in *api.JoinGroupRequest) (*empty.Empty, error) {
	userID := ctx.Value(ctxUserIDKey{}).(uuid.UUID)
	username := ctx.Value(ctxUsernameKey{}).(string)

	// Before hook.
	if fn := s.runtime.BeforeJoinGroup(); fn != nil {
@@ -256,7 +257,7 @@ func (s *ApiServer) JoinGroup(ctx context.Context, in *api.JoinGroupRequest) (*e

		// Extract request information and execute the hook.
		clientIP, clientPort := extractClientAddress(s.logger, ctx)
		result, err, code := fn(ctx, s.logger, userID.String(), ctx.Value(ctxUsernameKey{}).(string), ctx.Value(ctxExpiryKey{}).(int64), clientIP, clientPort, in)
		result, err, code := fn(ctx, s.logger, userID.String(), username, ctx.Value(ctxExpiryKey{}).(int64), clientIP, clientPort, in)
		if err != nil {
			return nil, status.Error(code, err.Error())
		}
@@ -281,7 +282,7 @@ func (s *ApiServer) JoinGroup(ctx context.Context, in *api.JoinGroupRequest) (*e
		return nil, status.Error(codes.InvalidArgument, "Group ID must be a valid ID.")
	}

	err = JoinGroup(ctx, s.logger, s.db, groupID, userID)
	err = JoinGroup(ctx, s.logger, s.db, s.router, groupID, userID, username)
	if err != nil {
		if err == ErrGroupNotFound {
			return nil, status.Error(codes.NotFound, "Group not found.")
@@ -301,7 +302,7 @@ func (s *ApiServer) JoinGroup(ctx context.Context, in *api.JoinGroupRequest) (*e

		// Extract request information and execute the hook.
		clientIP, clientPort := extractClientAddress(s.logger, ctx)
		fn(ctx, s.logger, userID.String(), ctx.Value(ctxUsernameKey{}).(string), ctx.Value(ctxExpiryKey{}).(int64), clientIP, clientPort, in)
		fn(ctx, s.logger, userID.String(), username, ctx.Value(ctxExpiryKey{}).(int64), clientIP, clientPort, in)

		// Stats measurement end boundary.
		span.End()
@@ -430,7 +431,7 @@ func (s *ApiServer) AddGroupUsers(ctx context.Context, in *api.AddGroupUsersRequ
		userIDs = append(userIDs, uid)
	}

	err = AddGroupUsers(ctx, s.logger, s.db, userID, groupID, userIDs)
	err = AddGroupUsers(ctx, s.logger, s.db, s.router, userID, groupID, userIDs)
	if err != nil {
		if err == ErrGroupPermissionDenied {
			return nil, status.Error(codes.NotFound, "Group not found or permission denied.")
+1 −1
Original line number Diff line number Diff line
@@ -771,7 +771,7 @@ AND EXISTS
				Subject:    subject,
				Content:    string(content),
				SenderId:   userID.String(),
				Code:       NOTIFICATION_FRIEND_JOIN_GAME,
				Code:       NotificationCodeFriendJoinGame,
				Persistent: true,
				CreateTime: &timestamp.Timestamp{Seconds: createTime},
			}}
+2 −2
Original line number Diff line number Diff line
@@ -178,10 +178,10 @@ func AddFriends(ctx context.Context, logger *zap.Logger, db *sql.DB, messageRout
	content, _ := json.Marshal(map[string]interface{}{"username": username})
	for id, isFriendAccept := range notificationToSend {
		uid := uuid.FromStringOrNil(id)
		code := NOTIFICATION_FRIEND_REQUEST
		code := NotificationCodeFriendRequest
		subject := fmt.Sprintf("%v wants to add you as a friend", username)
		if isFriendAccept {
			code = NOTIFICATION_FRIEND_ACCEPT
			code = NotificationCodeFriendAccept
			subject = fmt.Sprintf("%v accepted your friend request", username)
		}
		notifications[uid] = []*api.Notification{{
+86 −10
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import (
	"database/sql"
	"encoding/base64"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"
@@ -268,7 +270,7 @@ func DeleteGroup(ctx context.Context, logger *zap.Logger, db *sql.DB, groupID uu
	return nil
}

func JoinGroup(ctx context.Context, logger *zap.Logger, db *sql.DB, groupID uuid.UUID, userID uuid.UUID) error {
func JoinGroup(ctx context.Context, logger *zap.Logger, db *sql.DB, router MessageRouter, groupID uuid.UUID, userID uuid.UUID, username string) error {
	query := `
SELECT id, creator_id, name, description, avatar_url, state, edge_count, lang_tag, max_count, metadata, create_time, update_time
FROM groups 
@@ -311,6 +313,52 @@ WHERE (id = $1) AND (disable_time = '1970-01-01 00:00:00')`
			return err
		}

		// If it's a private group notify superadmins/admins that someone has requested to join.
		// Prepare notification data.
		notificationContentBytes, err := json.Marshal(map[string]string{"username": username})
		if err != nil {
			logger.Error("Could not encode notification content.", zap.Error(err))
		} else {
			notificationContent := string(notificationContentBytes)
			notificationSubject := fmt.Sprintf("User %v wants to join your group", username)
			notifications := make(map[uuid.UUID][]*api.Notification)

			query = "SELECT destination_id FROM group_edge WHERE source_id = $1::UUID AND (state = 0 OR state = 1)"
			rows, err := db.QueryContext(ctx, query, groupID)
			if err != nil {
				// Errors here will not cause the join operation to fail.
				logger.Error("Error looking up group admins to notify of join request.", zap.Error(err))
			} else {
				defer rows.Close()

				for rows.Next() {
					var id string
					if err = rows.Scan(&id); err != nil {
						// Errors here will not cause the join operation to fail.
						logger.Error("Error reading up group admins to notify of join request.", zap.Error(err))
						break
					}

					adminID := uuid.FromStringOrNil(id)
					notifications[adminID] = []*api.Notification{
						&api.Notification{
							Id:         uuid.Must(uuid.NewV4()).String(),
							Subject:    notificationSubject,
							Content:    notificationContent,
							SenderId:   userID.String(),
							Code:       NotificationCodeGroupJoinRequest,
							Persistent: true,
							CreateTime: &timestamp.Timestamp{Seconds: time.Now().UTC().Unix()},
						},
					}
				}
			}

			if len(notifications) > 0 {
				NotificationSend(ctx, logger, db, router, notifications)
			}
		}

		logger.Info("Added join request to group.", zap.String("group_id", groupID.String()), zap.String("user_id", userID.String()))
		return nil
	}
@@ -421,7 +469,7 @@ func LeaveGroup(ctx context.Context, logger *zap.Logger, db *sql.DB, groupID uui
	return nil
}

func AddGroupUsers(ctx context.Context, logger *zap.Logger, db *sql.DB, caller uuid.UUID, groupID uuid.UUID, userIDs []uuid.UUID) error {
func AddGroupUsers(ctx context.Context, logger *zap.Logger, db *sql.DB, router MessageRouter, caller uuid.UUID, groupID uuid.UUID, userIDs []uuid.UUID) error {
	if caller != uuid.Nil {
		var dbState sql.NullInt64
		query := "SELECT state FROM group_edge WHERE source_id = $1::UUID AND destination_id = $2::UUID"
@@ -440,17 +488,16 @@ func AddGroupUsers(ctx context.Context, logger *zap.Logger, db *sql.DB, caller u
		}
	}

	var groupExists sql.NullBool
	query := "SELECT EXISTS (SELECT id FROM groups WHERE id = $1 AND disable_time = '1970-01-01 00:00:00')"
	err := db.QueryRowContext(ctx, query, groupID).Scan(&groupExists)
	if err != nil {
		logger.Error("Could not look up group when adding users.", zap.Error(err), zap.String("group_id", groupID.String()))
		return err
	}
	if !groupExists.Bool {
	var groupName sql.NullString
	query := "SELECT name FROM groups WHERE id = $1 AND disable_time = '1970-01-01 00:00:00'"
	if err := db.QueryRowContext(ctx, query, groupID).Scan(&groupName); err != nil {
		if err == sql.ErrNoRows {
			logger.Info("Cannot add users to disabled group.", zap.String("group_id", groupID.String()))
			return ErrGroupNotFound
		}
		logger.Error("Could not look up group when adding users.", zap.Error(err), zap.String("group_id", groupID.String()))
		return err
	}

	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
@@ -458,7 +505,20 @@ func AddGroupUsers(ctx context.Context, logger *zap.Logger, db *sql.DB, caller u
		return err
	}

	// Prepare notification data.
	notificationContentBytes, err := json.Marshal(map[string]string{"name": groupName.String})
	if err != nil {
		logger.Error("Could not encode notification content.", zap.Error(err))
		return err
	}
	notificationContent := string(notificationContentBytes)
	notificationSubject := fmt.Sprintf("You've been added to group %v", groupName.String)
	var notifications map[uuid.UUID][]*api.Notification

	if err := crdb.ExecuteInTx(ctx, tx, func() error {
		// If the transaction is retried ensure we wipe any notifications that may have been prepared by previous attempts.
		notifications = make(map[uuid.UUID][]*api.Notification, len(userIDs))

		for _, uid := range userIDs {
			if uid == caller {
				continue
@@ -504,12 +564,28 @@ func AddGroupUsers(ctx context.Context, logger *zap.Logger, db *sql.DB, caller u
					return ErrGroupFull
				}
			}

			notifications[uid] = []*api.Notification{
				&api.Notification{
					Id:         uuid.Must(uuid.NewV4()).String(),
					Subject:    notificationSubject,
					Content:    notificationContent,
					SenderId:   caller.String(),
					Code:       NotificationCodeGroupAdd,
					Persistent: true,
					CreateTime: &timestamp.Timestamp{Seconds: time.Now().UTC().Unix()},
				},
			}
		}
		return nil
	}); err != nil {
		return err
	}

	if len(notifications) > 0 {
		NotificationSend(ctx, logger, db, router, notifications)
	}

	return nil
}

Loading