server/services/friends.js

Summary

Maintainability
A
45 mins
Test Coverage
//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

import UserGId from '../lib/userGId';

const notification = require('../lib/notification');
const User = require('../models/user');
const Friend = require('../models/friend');

const EPOCH_DATE = new Date(1);

exports.sendUpdateFriends = function sendUpdateFriends(user, sessionId) {
  sendUpdateFriendsNtf(user, sessionId);
};

exports.sendConfirmFriends = async function sendConfirmFriends(user, sessionId) {
  const friendAsDst = await Friend.find({ srcUserId: user.id });
  const friendUsers = friendAsDst.filter(friend => friend.get('state') === 'pending');

  if (friendUsers.length > 0) {
    // Uses userId property so that related ADD_USERS notification is send automatically
    // See lib/notification.js for the details.
    const ntf = {
      type: 'CONFIRM_FRIENDS',
      friends: friendUsers.map(friendUser => {
        const friendUserGId = UserGId.create({
          id: friendUser.get('dstUserId'),
          type: 'mas'
        });

        return { userId: friendUserGId.toString() };
      })
    };

    if (sessionId) {
      await notification.send(user, sessionId, ntf);
    } else {
      await notification.broadcast(user, ntf);
    }
  }
};

exports.informStateChange = async function informStateChange(user, eventType) {
  const command = {
    type: 'UPDATE_FRIENDS',
    reset: false,
    friends: [
      {
        userId: user.gId.toString(),
        online: eventType === 'login',
        last: Math.round(Date.now() / 1000)
      }
    ]
  };

  if (eventType === 'logout') {
    await user.set('lastLogout', new Date());
  }

  const friendUsers = await getFriendUsers(user);

  for (const friendUser of friendUsers) {
    await notification.broadcast(friendUser, command);
  }
};

exports.createPending = async function createPending(user, friendUser) {
  await Friend.create({
    srcUserId: user.id,
    dstUserId: friendUser.id,
    state: 'asking'
  });

  await Friend.create({
    srcUserId: friendUser.id,
    dstUserId: user.id,
    state: 'pending'
  });
};

exports.activateFriends = async function activeFriends(user, friendUser) {
  const srcFriend = await Friend.findFirst({
    srcUserId: user.id,
    dstUserId: friendUser.id
  });

  const dstFriend = await Friend.findFirst({
    srcUserId: friendUser.id,
    dstUserId: user.id
  });

  await srcFriend.set({ state: 'active' });
  await dstFriend.set({ state: 'active' });

  // Inform both parties
  await sendUpdateFriendsNtf(user);
  await sendUpdateFriendsNtf(friendUser);
};

exports.removeFriends = async function removeFriends(user, friendUser) {
  const srcFriend = await Friend.findFirst({
    srcUserId: user.id,
    dstUserId: friendUser.id
  });

  const dstFriend = await Friend.findFirst({
    srcUserId: friendUser.id,
    dstUserId: user.id
  });

  await srcFriend.delete();
  await dstFriend.delete();
};

exports.removeUser = async function removeUser(user) {
  const [friendAsSrc, friendAsDst] = await Promise.all([
    Friend.find({ srcUserId: user.id }),
    Friend.find({ dstUserId: user.id })
  ]);

  const friends = friendAsSrc.concat(friendAsDst);

  for (const friend of friends) {
    await friend.delete();
  }
};

async function sendUpdateFriendsNtf(user, sessionId) {
  const command = {
    type: 'UPDATE_FRIENDS',
    reset: true,
    friends: []
  };

  const friendUsers = await getFriendUsers(user);

  for (const friendUser of friendUsers) {
    const last = friendUser.get('lastLogout');
    const online = await friendUser.isOnline();

    const friendData = {
      userId: `m${friendUser.id}`,
      online
    };

    if (!online) {
      friendData.last = last < EPOCH_DATE ? -1 : Math.floor(last.getTime() / 1000);
    }

    command.friends.push(friendData);
  }

  if (sessionId) {
    await notification.send(user, sessionId, command);
  } else {
    await notification.broadcast(user, command);
  }
}

async function getFriendUsers(user) {
  const friends = await Friend.find({ srcUserId: user.id });
  const activeFriends = friends.filter(friend => friend.get('state') === 'active');
  const friendUsers = [];

  for (const friend of activeFriends) {
    friendUsers.push(await User.fetch(friend.get('dstUserId')));
  }

  return friendUsers;
}