public Result <string> RefreshUser(long loginId, string refreshKey)
        {
            var login = _db.Logins.Include(x => x.User).SingleOrDefault(x => x.Id == loginId);

            if (login is null)
            {
                _logger.LogWarning($"Attempted to refresh missing login id {loginId}");
                return(Result.Failure <string>("Invalid login id"));
            }

            if (login.RefreshKey != refreshKey)
            {
                _logger.LogWarning($"Attempted to refresh login id {loginId} with invalid refresh key");
                return(Result.Failure <string>("Invalid refresh key"));
            }

            // mark entity as modified so the modified date will get updated
            _db.Entry(login).State = EntityState.Modified;
            _db.SaveChanges();

            var accessKey = GenerateAccessToken(login.User, loginId);

            return(Result.Success(accessKey));
        }
Example #2
0
        public async Task <Result <ActivityDetails, TurnError> > TakeTurnAsync(DateTimeOffset activityModifiedDate, int activityId, int byUserId, int forUserId, DateTimeOffset when)
        {
            try
            {
                var now = DateTimeOffset.Now;

                _db.Turns.Add(new Turn
                {
                    ActivityId = activityId,
                    CreatorId  = byUserId,
                    UserId     = forUserId,
                    Occurred   = when
                });

                var activity = GetActivity(activityId, false, true);
                if (activity == null)
                {
                    _logger.LogError($"Activity {activityId} is missing");
                    return(Result.Failure <ActivityDetails, TurnError>(TurnError.ActivityMissing));
                }

                if (_appSettings.Value.ValidateActivityModifiedDate && activity.ModifiedDate != activityModifiedDate)
                {
                    _logger.LogWarning($"Activity {activity.Id} was modified {activity.ModifiedDate} and doesn't match {activityModifiedDate}");
                    return(Result.Failure <ActivityDetails, TurnError>(TurnError.ActivityModified));
                }

                var details = ActivityDetails.Calculate(activity, byUserId, _mapper);

                var turnTaker = await _db.Users.FindAsync(forUserId);

                FormattableString fs = $"{turnTaker.DisplayName} took a turn.";
                var myTurnBuilder    = new StringBuilder().AppendFormattable(fs);
                var otherTurnBuilder = new StringBuilder().AppendFormattable(fs);
                if (details.CurrentTurnUserId.HasValue)
                {
                    otherTurnBuilder.AppendFormattable($" It's {details.CurrentTurnUserDisplayName}'s turn.");
                    myTurnBuilder.AppendFormattable($" It's your turn.");
                }
                if (details.Due.HasValue)
                {
                    fs = $" Due in {(details.Due.Value - now).ToDisplayString()}.";
                    otherTurnBuilder.AppendFormattable(fs);
                    myTurnBuilder.AppendFormattable(fs);
                }

                var myTurnMessage    = myTurnBuilder.ToString();
                var otherTurnMessage = otherTurnBuilder.ToString();
                var url = $"{_appSettings.Value.PushNotifications.ServerUrl}/activity/{activityId}";

                var failures = new List <PushFailure>();

                foreach (var participant in activity.Participants)
                {
                    var pushNotified = false;

                    foreach (var setting in participant.NotificationSettings.OrderBy(x => x.Type))
                    {
                        switch (setting.Type)
                        {
                        case NotificationType.OverdueAnybody:
                        case NotificationType.OverdueMine:
                            setting.NextCheck = now;

                            // send a close push notification in case they still have a previous notification open but not if they
                            // already got a notification about a turn being taken because that will replace any existing notification
                            if (setting.Push && !pushNotified)
                            {
                                failures.AddRange(await _pushNotificationService.SendCloseToAllDevicesAsync("turn", setting.Participant.UserId, activityId.ToString()));
                                pushNotified = true;
                            }
                            break;

                        case NotificationType.TurnTakenAnybody:
                            if (setting.Push)
                            {
                                failures.AddRange(await _pushNotificationService.SendToAllDevicesAsync("turn", setting.Participant.UserId,
                                                                                                       activity.Name, otherTurnMessage, url, activityId.ToString()));
                                pushNotified = true;
                            }
                            break;

                        case NotificationType.TurnTakenMine:
                            if (details.CurrentTurnUserId.HasValue && setting.Push && setting.Participant.UserId == details.CurrentTurnUserId)
                            {
                                failures.AddRange(await _pushNotificationService.SendToAllDevicesAsync("turn", setting.Participant.UserId,
                                                                                                       activity.Name, myTurnMessage, url, activityId.ToString()));
                                pushNotified = true;
                            }
                            break;

                        default:
                            _logger.LogError($"Unhandled notification type {setting.Type}");
                            break;
                        }
                    }
                }

                // Ensure we are done sending each push message before cleaning up failures and continuing
                await _pushNotificationService.CleanupFailuresAsync(failures);

                // Always mark the activity as modified when taking a turn so our checks elsewhere that compare
                // the modified timestamp will still work for activities that wouldn't normally have anything update,
                // like when they're non-periodic or the next-turn user doesn't change.
                _db.Entry(activity).State = EntityState.Modified;
                await _db.SaveChangesAsync();

                details.Update(activity);

                return(Result.Success <ActivityDetails, TurnError>(details));
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Failed to take a turn");
                return(Result.Failure <ActivityDetails, TurnError>(TurnError.Exception));
            }
        }