public async Task LinkDeviceToUser(long deviceTag, int userId)
 {
     // Store in database
     using (var db = new HellolingoEntities()) {
         db.UsersDevices.AddOrUpdate(new UsersDevice {
             DeviceTag = deviceTag, UserId = userId, LastIPV4 = 0L
         });
         await db.SaveChangesAsync();
     }
 }
 public async Task AddMessageAsync(ITextChatMessage msg)
 {
     using (var db = new HellolingoEntities()) {
         var record = new DataAccess.TextChat {
             When       = msg.When,
             RoomId     = msg.RoomId,
             UserId     = msg.UserId,
             DeviceTag  = msg.DeviceTag,
             FirstName  = msg.FirstName,
             LastName   = msg.LastName,
             Text       = msg.Text,
             Visibility = (byte)msg.Visibility,
         };
         db.TextChats.Add(record);
         await db.SaveChangesAsync();
     }
 }
        public static async Task <Result <bool> > JoinRoom(User user, RoomId roomId)
        {
            var result = Result <bool> .True;

            if (!roomId.IsPrivate())
            {
                return(result);
            }

            int userIdEf = user.Id; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.
            int partnerId = ChatModel.PartnerIdFrom(roomId, user.Id);

            using (var db = new HellolingoEntities()) {
                var userTracker    = db.TextChatTrackers.Find(userIdEf, roomIdEf);
                var partnerTracker = db.TextChatTrackers.Find(partnerId, roomIdEf);

                // Handle people directly joining by the url from outside the chat
                if (userTracker == null || partnerTracker == null)
                {
                    await RequestPrivateChat(user, roomId, partnerId);

                    result.Reports.AddReport(LogTag.ChatRequestAddedFromJoinRoom, new { userId = user.Id, roomId });
                    return(result);
                }

                // Set new statuses according to current status
                if ((userTracker.Status == TrackerStatus.Invited || userTracker.Status == TrackerStatus.IgnoredInvite) && partnerTracker.Status == TrackerStatus.Inviting)
                {
                    userTracker.Status    = TrackerStatus.AcceptedInvite;
                    partnerTracker.Status = TrackerStatus.InviteAccepted;
                    userTracker.StatusAt  = partnerTracker.StatusAt = DateTime.Now;
                    result.Reports        = new LogReports(LogTag.ChatRequestAccepted);
                }
                else if (userTracker.Status == TrackerStatus.InviteAccepted)
                {
                    userTracker.Status   = TrackerStatus.SeenInviteResponse;
                    userTracker.StatusAt = DateTime.Now;
                }

                await db.SaveChangesAsync();
            }

            return(result);
        }
        public static async Task <Result <bool> > MarkAllCaughtUp(UserId userId, RoomId roomId)
        {
            int userIdEf = userId; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.
            var result = Result <bool> .True;

            using (var db = new HellolingoEntities()) {
                var userRecord = db.TextChatTrackers.Find(userIdEf, roomIdEf);
                if (userRecord == null)
                {
                    return(new Result <bool>(false, LogTag.UnexpectedTrackerStatus, new { method = "MarkAllCaughtUp", userId, roomId }, LogLevel.Error));
                }
                if (userRecord.Status == TrackerStatus.AllCaughtUp || userRecord.Status == TrackerStatus.Inviting || userRecord.Status == TrackerStatus.Invited)
                {
                    return(result);
                }
                userRecord.Status   = TrackerStatus.AllCaughtUp;
                userRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }
            return(result);
        }
        public static async Task <Result <bool> > IgnorePrivateChat(UserId userId, RoomId roomId, UserId partnerId)
        {
            int userIdEf = userId, partnerIdEf = partnerId; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.

            using (var db = new HellolingoEntities()) {
                var inviterRecord = db.TextChatTrackers.Find(partnerIdEf, roomIdEf);
                var inviteeRecord = db.TextChatTrackers.Find(userIdEf, roomIdEf);

                if (inviterRecord == null || inviteeRecord == null)
                {
                    return(new Result <bool>(false, LogTag.IgnorePrivateChatMissingTrackerRecord, new { userId, roomId, partnerId }, LogLevel.Error));
                }

                // Set record for invitee
                inviteeRecord.Status   = TrackerStatus.IgnoredInvite;
                inviteeRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }

            return(Result <bool> .True);
        }
        public static async Task <Result <bool> > PostTo(UserId userId, RoomId roomId)
        {
            var    result    = Result <bool> .True;
            string roomIdEf  = roomId;            // Casting named types into their base type because EF doesn't get it otherwise.
            int    partnerId = ChatModel.PartnerIdFrom(roomId, userId);

            using (var db = new HellolingoEntities()) {
                var partnerRecord = db.TextChatTrackers.Find(partnerId, roomIdEf);
                if (partnerRecord == null)
                {
                    return(new Result <bool>(false, LogTag.UnexpectedTrackerStatus, new { method = "PostTo", userId, roomId }, LogLevel.Error));
                }
                if (partnerRecord.Status == TrackerStatus.Invited || partnerRecord.Status == TrackerStatus.AutoBlocked || partnerRecord.Status == TrackerStatus.DeclinedInvite || partnerRecord.Status == TrackerStatus.IgnoredInvite || partnerRecord.Status == TrackerStatus.UnreadMessages)
                {
                    return(new Result <bool>(true));
                }
                partnerRecord.Status   = TrackerStatus.UnreadMessages;
                partnerRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }
            return(result);
        }
        public static async Task <Result <Propagate> > RequestPrivateChat(User user, RoomId roomId, UserId partnerId)
        {
            int partnerIdEf = partnerId; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.
            var toStatus  = TrackerStatus.Invited;
            var propagate = true;

            using (var db = new HellolingoEntities()) {
                var inviterRecord = db.TextChatTrackers.Find(user.Id, roomIdEf);
                var inviteeRecord = db.TextChatTrackers.Find(partnerIdEf, roomIdEf);
                var partner       = db.Users.Find(partnerIdEf);

                if (inviterRecord != null || inviteeRecord != null)
                {
                    return(new Result <Propagate>(false, LogTag.RequestPrivateChatHasExistingRecord, new { user.Id, roomId, partnerId }, LogLevel.Error));
                }

                // Check if it's a restricted invite
                var restricted = false;
                var userKnows  = new List <byte?> {
                    user.KnowsId, user.Knows2Id, user.Knows3Id
                };
                var userLearns = new List <byte?> {
                    user.LearnsId, user.Learns2Id, user.Learns3Id
                };
                var isHelper = userKnows.Contains(partner.LearnsId) || (partner.Learns2Id != null && userKnows.Contains(partner.Learns2Id)) || (partner.Learns3Id != null && userKnows.Contains(partner.Learns3Id));

                if (!WhitelistedMembers.Contains(user.Id) && !isHelper)
                {
                    // Reset RestrictedMembers every hour, which allows them to send on restricted invite every hour
                    if (DateTime.Now.Hour != RestrictionResetHour)
                    {
                        RestrictedMembers    = new HashSet <UserId>();
                        RestrictionResetHour = DateTime.Now.Hour;
                    }

                    // Detect if the invite is restricted
                    var partnerKnows = new List <byte?> {
                        partner.KnowsId, partner.Knows2Id, partner.Knows3Id
                    };
                    var partnerLearns = new List <byte?> {
                        partner.LearnsId, partner.Learns2Id, partner.Learns3Id
                    };
                    Func <InviteRule, bool> inviteCheck = control => control.FromGender == user.Gender && control.ToGender == partner.Gender &&
                                                          (control.FromKnows == null || userKnows.Contains(control.FromKnows)) &&
                                                          (control.ToKnows == null || partnerKnows.Contains(control.ToKnows)) &&
                                                          (control.ToLearns == null || partnerLearns.Contains(control.ToLearns));

                    // Mark restricted if it matches pattern of restricted invites
                    if (RestictedInvites.Any(restrict => inviteCheck(restrict)))
                    {
                        restricted = true;
                    }

                    // Mark if users only match on native language
                    if ((userKnows.Contains(partner.KnowsId) || (partner.Knows2Id != null && userKnows.Contains(partner.Knows2Id)) || (partner.Knows3Id != null && userKnows.Contains(partner.Knows3Id))) &&
                        !(userLearns.Contains(partner.KnowsId) || (partner.Knows2Id != null && userLearns.Contains(partner.Knows2Id)) || (partner.Knows3Id != null && userLearns.Contains(partner.Knows3Id))) &&
                        !(userKnows.Contains(partner.LearnsId) || (partner.Learns2Id != null && userKnows.Contains(partner.Learns2Id)) || (partner.Learns3Id != null && userKnows.Contains(partner.Learns3Id))) &&
                        !(userLearns.Contains(partner.LearnsId) || (partner.Learns2Id != null && userLearns.Contains(partner.Learns2Id)) || (partner.Learns3Id != null && userLearns.Contains(partner.Learns3Id)))
                        )
                    {
                        restricted = true;
                    }

                    // Derestrict if whitelisted
                    if (WhitelistedInvites.Any(allow => inviteCheck(allow)))
                    {
                        restricted = false;
                    }

                    // If restricted, block it, unless it's the first one from this user.
                    if (restricted)
                    {
                        if (RestrictedMembers.Contains(user.Id))
                        {
                            toStatus  = TrackerStatus.AutoBlocked;
                            propagate = false;
                        }
                        else
                        {
                            RestrictedMembers.Add(user.Id);
                        }
                    }
                }

                // Set record for inviter
                db.TextChatTrackers.Add(new TextChatTracker {
                    UserId = user.Id, RoomId = roomIdEf, PartnerId = partnerIdEf,
                    Status = TrackerStatus.Inviting, StatusAt = DateTime.Now, Initiator = true
                });

                // Set record for invitee
                db.TextChatTrackers.Add(new TextChatTracker {
                    UserId = partnerIdEf, RoomId = roomIdEf, PartnerId = user.Id,
                    Status = toStatus, StatusAt = DateTime.Now, Initiator = false
                });

                await db.SaveChangesAsync();
            }

            return(new Result <Propagate>(propagate));
        }