Ejemplo n.º 1
0
        /// <summary>
        /// Loads the reservations to be displayed, and optionally performs a checkin or checkout
        /// if the corresponding reservation reference id is provided. The checkin/checkout comes
        /// first so we retrieve the reservation list AFTER those changes are applied.
        /// </summary>
        /// <param name="checkin">Optional reference id of a reservation to check in</param>
        /// <param name="checkout">Optional reference id of a reservation to check out</param>
        /// <returns>Task returning an error message (or null) from the checkin or checkout</returns>
        public async Task<string> LoadDataAsync(string checkin, string checkout)
        {
            var clubInfo = EnvironmentDefinition.Instance.MapClubIdToClubInfo[this.ClubId];

            if (!BookedSchedulerCache.Instance[this.ClubId].IsInitialized)
            {
                this.Reservations = new JArray();
                return $"Please wait while the BoatTracker service initializes. This page will automatically refresh in one minute.";
            }

            // We only need the timezone for the user.
            this.BotUserState = await BookedSchedulerCache.Instance[this.ClubId].GetBotUserStateAsync();

            BookedSchedulerRetryClient client = null;

            try
            {
                client = new BookedSchedulerRetryClient(this.ClubId, true);
                await client.SignInAsync(clubInfo.UserName, clubInfo.Password);

                string message = null;

                if (!string.IsNullOrEmpty(checkin))
                {
                    try
                    {
                        await client.CheckInReservationAsync(checkin);
                    }
                    catch (Exception ex)
                    {
                        message = $"Checkin failed: {ex.Message}";
                    }
                }
                else if (!string.IsNullOrEmpty(checkout))
                {
                    try
                    {
                        await client.CheckOutReservationAsync(checkout);
                    }
                    catch (Exception ex)
                    {
                        message = $"Checkout failed: {ex.Message}";
                    }
                }

                // TODO: figure out how to narrow this down
                var reservations = await client.GetReservationsAsync(
                    start: DateTime.UtcNow - TimeSpan.FromDays(1),
                    end: DateTime.UtcNow + TimeSpan.FromDays(1));

                this.Reservations = reservations;

                return message;
            }
            catch (Exception ex)
            {
                this.Reservations = new JArray();
                return $"Unable to fetch reservations, currently. This page will automatically refresh in one minute. ({ex.Message})";
            }
            finally
            {
                if (client != null && client.IsSignedIn)
                {
                    try
                    {
                        await client.SignOutAsync();
                    }
                    catch (Exception)
                    {
                        // best effort only
                    }
                }
            }
        }
Ejemplo n.º 2
0
            public async Task RefreshCacheAsync(bool failSilently = true)
            {
                BookedSchedulerRetryClient client = null;

                try
                {
                    //
                    // If there's a refresh in progress on another thread, we return and let
                    // the caller proceed with date that's soon to be replaced. The priority
                    // is to ensure that two threads aren't updating the cache at once.
                    //
                    if (Interlocked.CompareExchange(ref this.refreshInProgress, 1, 0) != 0)
                    {
                        return;
                    }

                    var clubInfo = EnvironmentDefinition.Instance.MapClubIdToClubInfo[this.clubId];

                    client = new BookedSchedulerRetryClient(this.clubId, false);

                    await client.SignInAsync(clubInfo.UserName, clubInfo.Password);

                    var newResources = await client.GetResourcesAsync();
                    var users = await client.GetUsersAsync();
                    var newUserMap = new Dictionary<long, JToken>();

                    foreach (var u in users)
                    {
                        var fullUser = await client.GetUserAsync(u.Id());
                        newUserMap.Add(fullUser.Id(), fullUser);
                    }

                    var groups = await client.GetGroupsAsync();
                    var newGroupMap = new Dictionary<long, JToken>();

                    foreach (var g in groups)
                    {
                        var fullGroup = await client.GetGroupAsync(g.Id());
                        newGroupMap.Add(fullGroup.Id(), fullGroup);
                    }

#if UNUSED
                    var newSchedules = await client.GetSchedulesAsync();
                    this.schedules = newSchedules;
#endif

                    this.resources = newResources;
                    this.userMap = newUserMap;
                    this.groupMap = newGroupMap;

                    // Schedule the next cache refresh
                    this.refreshTime = DateTime.Now + CacheTimeout;
                }
                catch (Exception)
                {
                    //
                    // During normal operation, we fail silently since we have existing (but stale) data
                    // that we can continue to use. During initialization, we must rethrow so we can retry.
                    //
                    if (failSilently)
                    {
                        // If there were any errors, leave the stale data in place and schedule another
                        // refresh fairly soon.
                        this.refreshTime = DateTime.Now + CacheRetryTime;
                    }
                    else
                    {
                        throw;
                    }
                }
                finally
                {
                    this.refreshInProgress = 0;

                    if (client != null && client.IsSignedIn)
                    {
                        try
                        {
                            await client.SignOutAsync();
                        }
                        catch (Exception)
                        {
                            // best effort only
                        }
                    }
                }
            }
Ejemplo n.º 3
0
        private static async Task<ValidateResult> ValidatePassword(SignInForm state, object value)
        {
            string password = (string)value;
            var clubInfo = EnvironmentDefinition.Instance.MapClubIdToClubInfo[state.ClubInitials];

            var client = new BookedSchedulerRetryClient(clubInfo.Id, true);

            try
            {
                await client.SignInAsync(state.UserName, password);
            }
            catch (HttpRequestException)
            {
                return new ValidateResult
                {
                    IsValid = false,
                    Value = null,
                    Feedback = $"I'm sorry but your password is incorrect. Please try again."
                };
            }
            finally
            {
                if (client != null && client.IsSignedIn)
                {
                    try
                    {
                        await client.SignOutAsync();
                    }
                    catch (Exception)
                    {
                        // best effort only
                    }
                }
            }

            return new ValidateResult
            {
                IsValid = true,
                Value = password
            };
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Generates a daily report of usage and policy violations and emails it to the configured recipients.
        /// </summary>
        /// <param name="clubId">The club of interest</param>
        /// <param name="log">The writer for logging.</param>
        /// <returns>A task that completes when the daily report has been generated and sent.</returns>
        private static async Task RunDailyReport(string clubId, TextWriter log)
        {
            var clubInfo = EnvironmentDefinition.Instance.MapClubIdToClubInfo[clubId];

            var client = new BookedSchedulerRetryClient(clubId, false);

            await client.SignInAsync(clubInfo.UserName, clubInfo.Password);

            // Get all reservations for the last day
            var reservations = await client.GetReservationsAsync(start: DateTime.UtcNow - TimeSpan.FromDays(1), end: DateTime.UtcNow);

            var compliant = new List<string>();
            var abandoned = new List<string>();
            var failedToCheckOut = new List<string>();
            var unknownParticipants = new List<string>();
            var withGuest = new List<string>();

            foreach (var reservation in reservations)
            {
                DateTime? checkInDate = reservation.CheckInDate();
                DateTime? checkOutDate = reservation.CheckOutDate();

                var user = await client.GetUserAsync(reservation.UserId());
                var boatName = reservation.ResourceName();

                var localStartTime = ConvertToLocalTime(user, reservation.StartDate()).ToShortTimeString();
                var localEndTime = ConvertToLocalTime(user, reservation.EndDate()).ToShortTimeString();

                string basicInfo = $"{user.FullName()} ({user.EmailAddress()}) - '{boatName}' @ {localStartTime} - {localEndTime}";

                if (checkInDate.HasValue)
                {
                    var localCheckInTime = ConvertToLocalTime(user, checkInDate.Value).ToShortTimeString();

                    if (checkOutDate.HasValue)
                    {
                        var localCheckOutTime = ConvertToLocalTime(user, checkOutDate.Value).ToShortTimeString();

                        compliant.Add($"{basicInfo} (actual: {localCheckInTime} - {localCheckOutTime})");
                    }
                    else
                    {
                        failedToCheckOut.Add($"{basicInfo} (actual: {localCheckInTime} - ??)");
                    }
                }
                else
                {
                    abandoned.Add(basicInfo);
                }

                var invitedGuests = reservation["invitedGuests"] as JArray ?? new JArray();
                var participatingGuests = reservation["participatingGuests"] as JArray ?? new JArray();

                // Get the "full" reservation to make sure the participants list is given as an array
                var fullReservation = await client.GetReservationAsync(reservation.ReferenceNumber());
                var participants = (JArray)fullReservation["participants"];

                var boat = await client.GetResourceAsync(reservation.ResourceId());

                //
                // See if the number of recorded participants is less than the boat capacity. We always
                // have to add one for the reservation owner.
                //
                if (participants.Count + invitedGuests.Count + participatingGuests.Count + 1 < boat.MaxParticipants())
                {
                    unknownParticipants.Add(basicInfo);
                }

                // Check for reservations involving a guest rower
                if (invitedGuests.Count > 0 || participatingGuests.Count > 0)
                {
                    var guestEmail = invitedGuests.Count > 0 ? invitedGuests[0].Value<string>() : participatingGuests[0].Value<string>();
                    withGuest.Add($"{basicInfo} with {guestEmail}");
                }
            }

            // Get all reservations for the next two days
            var upcomingReservations = await client.GetReservationsAsync(start: DateTime.UtcNow, end: DateTime.UtcNow + TimeSpan.FromDays(2));
            var upcomingWithGuest = new List<string>();

            foreach (var reservation in upcomingReservations)
            {
                var invitedGuests = reservation["invitedGuests"] as JArray ?? new JArray();
                var participatingGuests = reservation["participatingGuests"] as JArray ?? new JArray();

                if (invitedGuests.Count > 0 || participatingGuests.Count > 0)
                {
                    DateTime? checkInDate = reservation.CheckInDate();
                    DateTime? checkOutDate = reservation.CheckOutDate();

                    var user = await client.GetUserAsync(reservation.UserId());
                    var boatName = reservation.ResourceName();

                    var localStartDate = ConvertToLocalTime(user, reservation.StartDate()).ToShortDateString();
                    var localStartTime = ConvertToLocalTime(user, reservation.StartDate()).ToShortTimeString();
                    var localEndTime = ConvertToLocalTime(user, reservation.EndDate()).ToShortTimeString();

                    string basicInfo = $"{user.FullName()} ({user.EmailAddress()}) - '{boatName}' @ {localStartDate} {localStartTime} - {localEndTime}";

                    var guestEmail = invitedGuests.Count > 0 ? invitedGuests[0].Value<string>() : participatingGuests[0].Value<string>();
                    upcomingWithGuest.Add($"{basicInfo} with {guestEmail}");
                }
            }

            await client.SignOutAsync();

            var body = new StringBuilder();

            body.AppendLine($"<h2>BoatTracker Daily Report for: {clubInfo.Name}</h2>");
            body.AppendLine($"<p>Total reservations: {reservations.Count}</p>");

            AddReservationsToReport(body, compliant, "Compliant reservations");
            AddReservationsToReport(body, abandoned, "Unused reservations");
            AddReservationsToReport(body, failedToCheckOut, "Unclosed reservations");
            AddReservationsToReport(body, unknownParticipants, "Incomplete rosters");
            AddReservationsToReport(body, withGuest, "Guest rowers");
            AddReservationsToReport(body, upcomingWithGuest, "Upcoming guest rowers");

            log.Write(body.ToString());

            await SendDailyReportEmail(
                log,
                clubInfo,
                $"BoatTracker daily report for {clubInfo.Name}" + (EnvironmentDefinition.Instance.IsDevelopment ? " (DEV)" : string.Empty),
                body.ToString());
        }