Esempio n. 1
0
        public static void OutsiderLevelsModel()
        {
            var encodedSaveData = File.ReadAllText("TestData\\ValidZlib.txt");
            var savedGame       = SavedGame.Parse(encodedSaveData);
            var outsiderLevels  = new OutsiderLevelsModel(MockGameData.RealData, savedGame);

            var expectedOutsiderLevels = new Dictionary <int, long>
            {
                { 1, 0 },
                { 2, 0 },
                { 3, 0 },
                { 5, 0 },
                { 6, 0 },
                { 7, 0 },
                { 8, 0 },
                { 9, 0 },
                { 10, 0 },
            };

            Assert.NotNull(outsiderLevels.OutsiderLevels);
            Assert.Equal(expectedOutsiderLevels.Count, outsiderLevels.OutsiderLevels.Count);
            foreach (var pair in expectedOutsiderLevels)
            {
                Assert.Equal(pair.Value, outsiderLevels.OutsiderLevels[pair.Key]);
            }
        }
Esempio n. 2
0
        private bool ProcessMessage(CloudQueueMessage queueMessage)
        {
            var properties = new Dictionary <string, string>
            {
                { "CloudQueueMessage-DequeueCount", queueMessage.DequeueCount.ToString() },
                { "CloudQueueMessage-InsertionTime", queueMessage.InsertionTime.ToString() },
                { "CloudQueueMessage-Id", queueMessage.Id },
            };

            this.telemetryClient.TrackEvent("UploadProcessor-Recieved", properties);

            using (var counterProvider = new CounterProvider(this.telemetryClient))
                using (var databaseCommandFactory = new DatabaseCommandFactory(
                           this.databaseSettingsOptions,
                           counterProvider))
                    using (counterProvider.Measure(Counter.ProcessUpload))
                    {
                        int uploadId = -1;
                        try
                        {
                            var message = JsonConvert.DeserializeObject <UploadProcessingMessage>(queueMessage.AsString);
                            if (message == null)
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseMessage", properties);
                                return(false);
                            }

                            uploadId = message.UploadId;
                            properties.Add("UploadId", uploadId.ToString());

                            this.CurrentUploadId = uploadId;

                            GetUploadDetails(databaseCommandFactory, uploadId, out var uploadContent, out var userId, out var playStyle);
                            properties.Add("UserId", userId);
                            if (string.IsNullOrWhiteSpace(uploadContent))
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-FetchUpload", properties);
                                return(false);
                            }

                            // Handle legacy uplaods where the upload content was missing.
                            if (uploadContent.Equals("LEGACY", StringComparison.OrdinalIgnoreCase))
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Complete-Legacy", properties);
                                using (var command = databaseCommandFactory.Create())
                                {
                                    command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId;
                                    command.ExecuteNonQuery();
                                }

                                return(true);
                            }

                            var savedGame = SavedGame.Parse(uploadContent);
                            if (savedGame == null)
                            {
                                this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-ParseUpload", properties);
                                return(false);
                            }

                            this.telemetryClient.TrackEvent("UploadProcessor-Processing", properties);
                            var ancientLevels = new AncientLevelsModel(
                                this.gameData,
                                savedGame,
                                this.telemetryClient);
                            var outsiderLevels = new OutsiderLevelsModel(
                                this.gameData,
                                savedGame,
                                this.telemetryClient);
                            var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame);

                            /* Build a query that looks like this:
                             *  MERGE INTO AncientLevels WITH (HOLDLOCK)
                             *  USING
                             *      (VALUES (123, 1, 100), (123, 2, 100), ... )
                             *          AS Input(UploadId, AncientId, Level)
                             *      ON AncientLevels.UploadId = Input.UploadId
                             *      AND AncientLevels.AncientId = Input.AncientId
                             *  WHEN MATCHED THEN
                             *      UPDATE
                             *      SET
                             *          Level = Input.Level
                             *  WHEN NOT MATCHED THEN
                             *      INSERT (UploadId, AncientId, Level)
                             *      VALUES (Input.UploadId, Input.AncientId, Input.Level);
                             */
                            var ancientLevelsCommandText = new StringBuilder();
                            if (ancientLevels.AncientLevels.Count > 0)
                            {
                                ancientLevelsCommandText.Append(@"
                        MERGE INTO AncientLevels WITH (HOLDLOCK)
                        USING
                            ( VALUES ");
                                var isFirst = true;
                                foreach (var pair in ancientLevels.AncientLevels)
                                {
                                    if (!isFirst)
                                    {
                                        ancientLevelsCommandText.Append(",");
                                    }

                                    // No need to sanitize, these are all just numbers
                                    ancientLevelsCommandText.Append("(");
                                    ancientLevelsCommandText.Append(uploadId);
                                    ancientLevelsCommandText.Append(",");
                                    ancientLevelsCommandText.Append(pair.Key);
                                    ancientLevelsCommandText.Append(",");
                                    ancientLevelsCommandText.Append(pair.Value.AncientLevel);
                                    ancientLevelsCommandText.Append(")");

                                    isFirst = false;
                                }

                                ancientLevelsCommandText.Append(@"
                            )
                                AS Input(UploadId, AncientId, Level)
                            ON AncientLevels.UploadId = Input.UploadId
                            AND AncientLevels.AncientId = Input.AncientId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                Level = Input.Level
                        WHEN NOT MATCHED THEN
                            INSERT (UploadId, AncientId, Level)
                            VALUES (Input.UploadId, Input.AncientId, Input.Level);");
                            }

                            /* Build a query that looks like this:
                             *  MERGE INTO OutsiderLevels WITH (HOLDLOCK)
                             *  USING
                             *      (VALUES (123, 1, 100), (123, 2, 100), ... )
                             *          AS Input(UploadId, OutsiderId, Level)
                             *      ON OutsiderLevels.UploadId = Input.UploadId
                             *      AND OutsiderLevels.OutsiderId = Input.OutsiderId
                             *  WHEN MATCHED THEN
                             *      UPDATE
                             *      SET
                             *          Level = Input.Level
                             *  WHEN NOT MATCHED THEN
                             *      INSERT (UploadId, OutsiderId, Level)
                             *      VALUES (Input.UploadId, Input.OutsiderId, Input.Level);
                             */
                            var outsiderLevelsCommandText = new StringBuilder();
                            if (outsiderLevels.OutsiderLevels.Count > 0)
                            {
                                outsiderLevelsCommandText.Append(@"
                        MERGE INTO OutsiderLevels WITH (HOLDLOCK)
                        USING
                            ( VALUES ");
                                var isFirst = true;
                                foreach (var pair in outsiderLevels.OutsiderLevels)
                                {
                                    if (!isFirst)
                                    {
                                        outsiderLevelsCommandText.Append(",");
                                    }

                                    // No need to sanitize, these are all just numbers
                                    outsiderLevelsCommandText.Append("(");
                                    outsiderLevelsCommandText.Append(uploadId);
                                    outsiderLevelsCommandText.Append(",");
                                    outsiderLevelsCommandText.Append(pair.Key);
                                    outsiderLevelsCommandText.Append(",");
                                    outsiderLevelsCommandText.Append(pair.Value.Level);
                                    outsiderLevelsCommandText.Append(")");

                                    isFirst = false;
                                }

                                outsiderLevelsCommandText.Append(@"
                            )
                                AS Input(UploadId, OutsiderId, Level)
                            ON OutsiderLevels.UploadId = Input.UploadId
                            AND OutsiderLevels.OutsiderId = Input.OutsiderId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                Level = Input.Level
                        WHEN NOT MATCHED THEN
                            INSERT (UploadId, OutsiderId, Level)
                            VALUES (Input.UploadId, Input.OutsiderId, Input.Level);");
                            }

                            const string ComputedStatsCommandText       = @"
                        MERGE INTO ComputedStats WITH (HOLDLOCK)
                        USING
                            (VALUES (
                                    @UploadId,
                                    @TitanDamage,
                                    @SoulsSpent,
                                    @HeroSoulsSacrificed,
                                    @TotalAncientSouls,
                                    @TranscendentPower,
                                    @Rubies,
                                    @HighestZoneThisTranscension,
                                    @HighestZoneLifetime,
                                    @AscensionsThisTranscension,
                                    @AscensionsLifetime,
                                    @MaxTranscendentPrimalReward,
                                    @BossLevelToTranscendentPrimalCap) )
                                AS Input(
                                    UploadId,
                                    TitanDamage,
                                    SoulsSpent,
                                    HeroSoulsSacrificed,
                                    TotalAncientSouls,
                                    TranscendentPower,
                                    Rubies,
                                    HighestZoneThisTranscension,
                                    HighestZoneLifetime,
                                    AscensionsThisTranscension,
                                    AscensionsLifetime,
                                    MaxTranscendentPrimalReward,
                                    BossLevelToTranscendentPrimalCap)
                            ON ComputedStats.UploadId = Input.UploadId
                        WHEN MATCHED THEN
                            UPDATE
                            SET
                                TitanDamage = Input.TitanDamage,
                                SoulsSpent = Input.SoulsSpent,
                                HeroSoulsSacrificed = Input.HeroSoulsSacrificed,
                                TotalAncientSouls = Input.TotalAncientSouls,
                                TranscendentPower = Input.TranscendentPower,
                                Rubies = Input.Rubies,
                                HighestZoneThisTranscension = Input.HighestZoneThisTranscension,
                                HighestZoneLifetime = Input.HighestZoneLifetime,
                                AscensionsThisTranscension = Input.AscensionsThisTranscension,
                                AscensionsLifetime = Input.AscensionsLifetime,
                                MaxTranscendentPrimalReward = Input.MaxTranscendentPrimalReward,
                                BossLevelToTranscendentPrimalCap = Input.BossLevelToTranscendentPrimalCap
                        WHEN NOT MATCHED THEN
                            INSERT (
                                UploadId,
                                TitanDamage,
                                SoulsSpent,
                                HeroSoulsSacrificed,
                                TotalAncientSouls,
                                TranscendentPower,
                                Rubies,
                                HighestZoneThisTranscension,
                                HighestZoneLifetime,
                                AscensionsThisTranscension,
                                AscensionsLifetime,
                                MaxTranscendentPrimalReward,
                                BossLevelToTranscendentPrimalCap)
                            VALUES (
                                Input.UploadId,
                                Input.TitanDamage,
                                Input.SoulsSpent,
                                Input.HeroSoulsSacrificed,
                                Input.TotalAncientSouls,
                                Input.TranscendentPower,
                                Input.Rubies,
                                Input.HighestZoneThisTranscension,
                                Input.HighestZoneLifetime,
                                Input.AscensionsThisTranscension,
                                Input.AscensionsLifetime,
                                Input.MaxTranscendentPrimalReward,
                                Input.BossLevelToTranscendentPrimalCap);";
                            var          computedStatsCommandParameters = new Dictionary <string, object>
                            {
                                { "@UploadId", uploadId },
                                { "@TitanDamage", miscellaneousStatsModel.TitanDamage },
                                { "@SoulsSpent", miscellaneousStatsModel.HeroSoulsSpent },
                                { "@HeroSoulsSacrificed", miscellaneousStatsModel.HeroSoulsSacrificed },
                                { "@TotalAncientSouls", miscellaneousStatsModel.TotalAncientSouls },
                                { "@TranscendentPower", miscellaneousStatsModel.TranscendentPower },
                                { "@Rubies", miscellaneousStatsModel.Rubies },
                                { "@HighestZoneThisTranscension", miscellaneousStatsModel.HighestZoneThisTranscension },
                                { "@HighestZoneLifetime", miscellaneousStatsModel.HighestZoneLifetime },
                                { "@AscensionsThisTranscension", miscellaneousStatsModel.AscensionsThisTranscension },
                                { "@AscensionsLifetime", miscellaneousStatsModel.AscensionsLifetime },
                                { "@MaxTranscendentPrimalReward", miscellaneousStatsModel.MaxTranscendentPrimalReward },
                                { "@BossLevelToTranscendentPrimalCap", miscellaneousStatsModel.BossLevelToTranscendentPrimalCap },
                            };

                            using (var command = databaseCommandFactory.Create())
                            {
                                command.BeginTransaction();

                                if (ancientLevelsCommandText.Length > 0)
                                {
                                    command.CommandText = ancientLevelsCommandText.ToString();
                                    command.ExecuteNonQuery();
                                }

                                if (outsiderLevelsCommandText.Length > 0)
                                {
                                    command.CommandText = outsiderLevelsCommandText.ToString();
                                    command.ExecuteNonQuery();
                                }

                                command.CommandText = ComputedStatsCommandText;
                                command.Parameters  = computedStatsCommandParameters;
                                command.ExecuteNonQuery();

                                command.CommandText = "UPDATE Uploads SET LastComputeTime = getutcdate() WHERE Id = " + uploadId;
                                command.ExecuteNonQuery();

                                var commited = command.CommitTransaction();
                                if (!commited)
                                {
                                    this.telemetryClient.TrackEvent("UploadProcessor-Abandoned-CommitTransaction", properties);
                                    return(false);
                                }
                            }

                            this.telemetryClient.TrackEvent("UploadProcessor-Complete", properties);
                            return(true);
                        }
                        catch (Exception e)
                        {
                            this.telemetryClient.TrackException(e, properties);
                            return(false);
                        }
                        finally
                        {
                            this.CurrentUploadId = null;
                        }
                    }
        }
        public async Task <ActionResult <int> > Add([FromForm] UploadRequest uploadRequest)
        {
            // Only associate it with the user if they requested that it be added to their progress.
            var userId = uploadRequest.AddToProgress && this.User.Identity.IsAuthenticated
                ? this.userManager.GetUserId(this.User)
                : null;

            var savedGame = SavedGame.Parse(uploadRequest.EncodedSaveData);

            if (savedGame == null)
            {
                // Not a valid save
                return(this.BadRequest());
            }

            // Kick off a clan update in parallel
            var gameUserId     = savedGame.Object.Value <string>("uniqueId");
            var passwordHash   = savedGame.Object.Value <string>("passwordHash");
            var updateClanTask = this.clanManager.UpdateClanAsync(userId, gameUserId, passwordHash);

            PlayStyle playStyle;

            if (uploadRequest.PlayStyle.HasValue)
            {
                playStyle = uploadRequest.PlayStyle.Value;
            }
            else
            {
                var userSettings = await this.userSettingsProvider.GetAsync(userId);

                playStyle = userSettings.PlayStyle.GetValueOrDefault(PlayStyle.Hybrid);
            }

            // unixTimestamp is in milliseconds instead of seconds
            var saveTime = (savedGame.Object.Value <double>("unixTimestamp") / 1000).UnixTimeStampToDateTime();

            var ancientLevels = new AncientLevelsModel(
                this.gameData,
                savedGame);
            var outsiderLevels = new OutsiderLevelsModel(
                this.gameData,
                savedGame);
            var computedStats = new ComputedStats(savedGame);

            int uploadId;

            using (var command = this.databaseCommandFactory.Create())
            {
                await command.BeginTransactionAsync();

                // Insert Upload
                command.CommandText = @"
	                INSERT INTO Uploads(UserId, UploadContent, PlayStyle, SaveTime)
                    VALUES(@UserId, @UploadContent, @PlayStyle, @SaveTime);
                    SELECT SCOPE_IDENTITY();";
                command.Parameters  = new Dictionary <string, object>
                {
                    { "@UserId", userId },
                    { "@UploadContent", uploadRequest.EncodedSaveData },
                    { "@PlayStyle", playStyle.ToString() },
                    { "@SaveTime", saveTime },
                };
                uploadId = Convert.ToInt32(await command.ExecuteScalarAsync());

                // Insert computed stats
                command.CommandText = @"
                    INSERT INTO ComputedStats(
                        UploadId,
                        TitanDamage,
                        SoulsSpent,
                        HeroSoulsSacrificed,
                        TotalAncientSouls,
                        TranscendentPower,
                        Rubies,
                        HighestZoneThisTranscension,
                        HighestZoneLifetime,
                        AscensionsThisTranscension,
                        AscensionsLifetime)
                    VALUES(
                        @UploadId,
                        @TitanDamage,
                        @SoulsSpent,
                        @HeroSoulsSacrificed,
                        @TotalAncientSouls,
                        @TranscendentPower,
                        @Rubies,
                        @HighestZoneThisTranscension,
                        @HighestZoneLifetime,
                        @AscensionsThisTranscension,
                        @AscensionsLifetime);";
                command.Parameters  = new Dictionary <string, object>
                {
                    { "@UploadId", uploadId },
                    { "@TitanDamage", computedStats.TitanDamage },
                    { "@SoulsSpent", computedStats.HeroSoulsSpent },
                    { "@HeroSoulsSacrificed", computedStats.HeroSoulsSacrificed },
                    { "@TotalAncientSouls", computedStats.TotalAncientSouls },
                    { "@TranscendentPower", computedStats.TranscendentPower },
                    { "@Rubies", computedStats.Rubies },
                    { "@HighestZoneThisTranscension", computedStats.HighestZoneThisTranscension },
                    { "@HighestZoneLifetime", computedStats.HighestZoneLifetime },
                    { "@AscensionsThisTranscension", computedStats.AscensionsThisTranscension },
                    { "@AscensionsLifetime", computedStats.AscensionsLifetime },
                };
                await command.ExecuteNonQueryAsync();

                // Insert ancient levels
                var ancientLevelsCommandText = new StringBuilder("INSERT INTO AncientLevels(UploadId, AncientId, Level) VALUES");
                var ancientLevelsParameters  = new Dictionary <string, object>
                {
                    { "@UploadId", uploadId },
                };
                var isFirstAncient = true;
                foreach (var pair in ancientLevels.AncientLevels)
                {
                    if (!isFirstAncient)
                    {
                        ancientLevelsCommandText.Append(',');
                    }

                    // Looks like: (@UploadId, @AncientId{AncientId}, @AncientLevel{AncientId})
                    var idParamName    = "@AncientId" + pair.Key;
                    var levelParamName = "@AncientLevel" + pair.Key;
                    ancientLevelsCommandText.Append("(@UploadId,");
                    ancientLevelsCommandText.Append(idParamName);
                    ancientLevelsCommandText.Append(',');
                    ancientLevelsCommandText.Append(levelParamName);
                    ancientLevelsCommandText.Append(')');

                    ancientLevelsParameters.Add(idParamName, pair.Key);
                    ancientLevelsParameters.Add(levelParamName, pair.Value.ToTransportableString());

                    isFirstAncient = false;
                }

                command.CommandText = ancientLevelsCommandText.ToString();
                command.Parameters  = ancientLevelsParameters;
                await command.ExecuteNonQueryAsync();

                // Insert outsider levels
                var outsiderLevelsCommandText = new StringBuilder("INSERT INTO OutsiderLevels(UploadId, OutsiderId, Level) VALUES");
                var outsiderLevelsParameters  = new Dictionary <string, object>
                {
                    { "@UploadId", uploadId },
                };
                var isFirstOutsider = true;
                foreach (var pair in outsiderLevels.OutsiderLevels)
                {
                    if (!isFirstOutsider)
                    {
                        outsiderLevelsCommandText.Append(',');
                    }

                    // Looks like: (@UploadId, @OutsiderId{OutsiderId}, @Level{OutsiderId})
                    var idParamName    = "@OutsiderId" + pair.Key;
                    var levelParamName = "@OutsiderLevel" + pair.Key;
                    outsiderLevelsCommandText.Append("(@UploadId,");
                    outsiderLevelsCommandText.Append(idParamName);
                    outsiderLevelsCommandText.Append(',');
                    outsiderLevelsCommandText.Append(levelParamName);
                    outsiderLevelsCommandText.Append(')');

                    outsiderLevelsParameters.Add(idParamName, pair.Key);
                    outsiderLevelsParameters.Add(levelParamName, pair.Value);

                    isFirstOutsider = false;
                }

                command.CommandText = outsiderLevelsCommandText.ToString();
                command.Parameters  = outsiderLevelsParameters;
                await command.ExecuteNonQueryAsync();

                command.CommitTransaction();
            }

            // Wait for the task to finish, but don't fail the request if it fails
            try
            {
                await updateClanTask;
            }
            catch (Exception e)
            {
                var properties = new Dictionary <string, string>
                {
                    { "UploadId", uploadId.ToString() },
                };
                this.telemetryClient.TrackException(e, properties);
            }

            return(uploadId);
        }
        public IActionResult Details(int uploadId)
        {
            if (uploadId < 0)
            {
                return(this.BadRequest());
            }

            var userId       = this.userManager.GetUserId(this.User);
            var userSettings = this.userSettingsProvider.Get(userId);

            var uploadIdParameters = new Dictionary <string, object>
            {
                { "@UploadId", uploadId },
            };

            string    uploadContent;
            PlayStyle playStyle;
            var       upload = new Upload {
                Id = uploadId
            };
            const string GetUploadDataCommandText = @"
	            SELECT UserId, UserName, UploadTime, UploadContent, PlayStyle
                FROM Uploads
                LEFT JOIN AspNetUsers
                ON Uploads.UserId = AspNetUsers.Id
                WHERE Uploads.Id = @UploadId";

            using (var command = this.databaseCommandFactory.Create(
                       GetUploadDataCommandText,
                       uploadIdParameters))
                using (var reader = command.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        var uploadUserId   = reader["UserId"].ToString();
                        var uploadUserName = reader["UserName"].ToString();

                        if (!string.IsNullOrEmpty(uploadUserId))
                        {
                            upload.User = new User()
                            {
                                Id   = uploadUserId,
                                Name = uploadUserName,
                            };
                        }

                        // The DateTime is a datetime2 which has no timezone so comes out as DateTimeKind.Unknown. Se need to specify the kind so it gets serialized correctly.
                        upload.TimeSubmitted = DateTime.SpecifyKind(Convert.ToDateTime(reader["UploadTime"]), DateTimeKind.Utc);

                        uploadContent = reader["UploadContent"].ToString();
                        playStyle     = reader["PlayStyle"].ToString().SafeParseEnum <PlayStyle>();
                    }
                    else
                    {
                        // If we didn't get data, it's an upload that doesn't exist
                        return(this.NotFound());
                    }
                }

            var isAdmin            = this.User.IsInRole("Admin");
            var isUploadAnonymous  = upload.User == null;
            var isOwn              = !isUploadAnonymous && string.Equals(userId, upload.User.Id, StringComparison.OrdinalIgnoreCase);
            var uploadUserSettings = isOwn ? userSettings : this.userSettingsProvider.Get(upload.User?.Id);
            var isPublic           = isUploadAnonymous || uploadUserSettings.AreUploadsPublic;
            var isPermitted        = isOwn || isPublic || isAdmin;

            if (!isPermitted)
            {
                return(this.Unauthorized());
            }

            // Only return the raw upload content if it's the requesting user's or an admin requested it.
            if (isOwn || isAdmin)
            {
                upload.UploadContent = uploadContent;
            }

            // Set the play style.
            upload.PlayStyle = playStyle;

            var savedGame = SavedGame.Parse(uploadContent);

            upload.Stats = new Dictionary <StatType, string>();

            // Get ancient level stats
            var ancientLevelsModel = new AncientLevelsModel(
                this.gameData,
                savedGame,
                this.telemetryClient);

            foreach (var ancientLevelInfo in ancientLevelsModel.AncientLevels)
            {
                var ancientLevel = ancientLevelInfo.Value.AncientLevel;
                if (ancientLevel > 0)
                {
                    upload.Stats.Add(AncientIds.GetAncientStatType(ancientLevelInfo.Key), ancientLevel.ToTransportableString());
                }

                var itemLevel = ancientLevelInfo.Value.ItemLevel;
                if (itemLevel > 0)
                {
                    upload.Stats.Add(AncientIds.GetItemStatType(ancientLevelInfo.Key), itemLevel.ToString());
                }
            }

            // Get outsider level stats
            var outsiderLevelsModel = new OutsiderLevelsModel(
                this.gameData,
                savedGame,
                this.telemetryClient);

            foreach (var pair in outsiderLevelsModel.OutsiderLevels)
            {
                var outsiderLevel = pair.Value.Level;
                if (outsiderLevel > 0)
                {
                    upload.Stats.Add(OutsiderIds.GetOusiderStatType(pair.Key), outsiderLevel.ToString());
                }
            }

            // Get misc stats
            var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame);

            upload.Stats.Add(StatType.AscensionsLifetime, miscellaneousStatsModel.AscensionsLifetime.ToString());
            upload.Stats.Add(StatType.AscensionsThisTranscension, miscellaneousStatsModel.AscensionsThisTranscension.ToString());
            upload.Stats.Add(StatType.HeroSoulsSacrificed, miscellaneousStatsModel.HeroSoulsSacrificed.ToTransportableString());
            upload.Stats.Add(StatType.HeroSoulsSpent, miscellaneousStatsModel.HeroSoulsSpent.ToTransportableString());
            upload.Stats.Add(StatType.HighestZoneLifetime, miscellaneousStatsModel.HighestZoneLifetime.ToString());
            upload.Stats.Add(StatType.HighestZoneThisTranscension, miscellaneousStatsModel.HighestZoneThisTranscension.ToString());
            upload.Stats.Add(StatType.Rubies, miscellaneousStatsModel.Rubies.ToString());
            upload.Stats.Add(StatType.TitanDamage, miscellaneousStatsModel.TitanDamage.ToTransportableString());
            upload.Stats.Add(StatType.TotalAncientSouls, miscellaneousStatsModel.TotalAncientSouls.ToString());
            upload.Stats.Add(StatType.TranscendentPower, miscellaneousStatsModel.TranscendentPower.ToString());
            upload.Stats.Add(StatType.MaxTranscendentPrimalReward, miscellaneousStatsModel.MaxTranscendentPrimalReward.ToTransportableString());
            upload.Stats.Add(StatType.BossLevelToTranscendentPrimalCap, miscellaneousStatsModel.BossLevelToTranscendentPrimalCap.ToString());
            upload.Stats.Add(StatType.HeroSouls, miscellaneousStatsModel.HeroSouls.ToTransportableString());
            upload.Stats.Add(StatType.PendingSouls, miscellaneousStatsModel.PendingSouls.ToTransportableString());

            return(this.Ok(upload));
        }
        public IActionResult Add(UploadRequest uploadRequest)
        {
            if (uploadRequest.EncodedSaveData == null)
            {
                // Not a valid save
                return(this.BadRequest());
            }

            // Only associate it with the user if they requested that it be added to their progress.
            var userId = uploadRequest.AddToProgress && this.User.Identity.IsAuthenticated
                ? this.userManager.GetUserId(this.User)
                : null;

            var savedGame = SavedGame.Parse(uploadRequest.EncodedSaveData);

            if (savedGame == null)
            {
                // Not a valid save
                return(this.BadRequest());
            }

            PlayStyle playStyle;

            if (uploadRequest.PlayStyle.HasValue)
            {
                playStyle = uploadRequest.PlayStyle.Value;
            }
            else
            {
                var userSettings = this.userSettingsProvider.Get(userId);
                playStyle = userSettings.PlayStyle;
            }

            var ancientLevels = new AncientLevelsModel(
                this.gameData,
                savedGame,
                this.telemetryClient);
            var outsiderLevels = new OutsiderLevelsModel(
                this.gameData,
                savedGame,
                this.telemetryClient);
            var miscellaneousStatsModel = new MiscellaneousStatsModel(savedGame);

            int uploadId;

            using (var command = this.databaseCommandFactory.Create())
            {
                command.BeginTransaction();

                // Insert Upload
                command.CommandText = @"
	                INSERT INTO Uploads(UserId, UploadContent, PlayStyle)
                    VALUES(@UserId, @UploadContent, @PlayStyle);
                    SELECT SCOPE_IDENTITY();";
                command.Parameters  = new Dictionary <string, object>
                {
                    { "@UserId", userId },
                    { "@UploadContent", uploadRequest.EncodedSaveData },
                    { "@PlayStyle", playStyle.ToString() },
                };
                uploadId = Convert.ToInt32(command.ExecuteScalar());

                // Insert computed stats
                command.CommandText = @"
                    INSERT INTO ComputedStats(
                        UploadId,
                        TitanDamage,
                        SoulsSpent,
                        HeroSoulsSacrificed,
                        TotalAncientSouls,
                        TranscendentPower,
                        Rubies,
                        HighestZoneThisTranscension,
                        HighestZoneLifetime,
                        AscensionsThisTranscension,
                        AscensionsLifetime,
                        MaxTranscendentPrimalReward,
                        BossLevelToTranscendentPrimalCap)
                    VALUES(
                        @UploadId,
                        @TitanDamage,
                        @SoulsSpent,
                        @HeroSoulsSacrificed,
                        @TotalAncientSouls,
                        @TranscendentPower,
                        @Rubies,
                        @HighestZoneThisTranscension,
                        @HighestZoneLifetime,
                        @AscensionsThisTranscension,
                        @AscensionsLifetime,
                        @MaxTranscendentPrimalReward,
                        @BossLevelToTranscendentPrimalCap);";
                command.Parameters  = new Dictionary <string, object>
                {
                    { "@UploadId", uploadId },
                    { "@TitanDamage", miscellaneousStatsModel.TitanDamage.ToTransportableString() },
                    { "@SoulsSpent", miscellaneousStatsModel.HeroSoulsSpent.ToTransportableString() },
                    { "@HeroSoulsSacrificed", miscellaneousStatsModel.HeroSoulsSacrificed.ToTransportableString() },
                    { "@TotalAncientSouls", miscellaneousStatsModel.TotalAncientSouls },
                    { "@TranscendentPower", miscellaneousStatsModel.TranscendentPower },
                    { "@Rubies", miscellaneousStatsModel.Rubies },
                    { "@HighestZoneThisTranscension", miscellaneousStatsModel.HighestZoneThisTranscension },
                    { "@HighestZoneLifetime", miscellaneousStatsModel.HighestZoneLifetime },
                    { "@AscensionsThisTranscension", miscellaneousStatsModel.AscensionsThisTranscension },
                    { "@AscensionsLifetime", miscellaneousStatsModel.AscensionsLifetime },
                    { "@MaxTranscendentPrimalReward", miscellaneousStatsModel.MaxTranscendentPrimalReward.ToTransportableString() },
                    { "@BossLevelToTranscendentPrimalCap", miscellaneousStatsModel.BossLevelToTranscendentPrimalCap },
                };
                command.ExecuteNonQuery();

                // Insert ancient levels
                foreach (var pair in ancientLevels.AncientLevels)
                {
                    command.CommandText = @"
                        INSERT INTO AncientLevels(UploadId, AncientId, Level)
                        VALUES(@UploadId, @AncientId, @Level);";
                    command.Parameters  = new Dictionary <string, object>
                    {
                        { "@UploadId", uploadId },
                        { "@AncientId", pair.Key },
                        { "@Level", pair.Value.AncientLevel.ToTransportableString() },
                    };
                    command.ExecuteNonQuery();
                }

                // Insert outsider levels
                foreach (var pair in outsiderLevels.OutsiderLevels)
                {
                    command.CommandText = @"
                        INSERT INTO OutsiderLevels(UploadId, OutsiderId, Level)
                        VALUES(@UploadId, @OutsiderId, @Level);";
                    command.Parameters  = new Dictionary <string, object>
                    {
                        { "@UploadId", uploadId },
                        { "@OutsiderId", pair.Key },
                        { "@Level", pair.Value.Level },
                    };
                    command.ExecuteNonQuery();
                }

                var commited = command.CommitTransaction();
                if (!commited)
                {
                    return(this.StatusCode((int)HttpStatusCode.InternalServerError));
                }
            }

            return(this.Ok(uploadId));
        }