diff --git a/src/ClientServer/Config/AppConfiguration.cs b/src/ClientServer/Config/AppConfiguration.cs index 7917e82d42139a6f971fe671cd55c1bf2bbaf3d1..11f14d4c7ffbd8afdfc4da5d4b99aba45c6cc94d 100644 --- a/src/ClientServer/Config/AppConfiguration.cs +++ b/src/ClientServer/Config/AppConfiguration.cs @@ -152,6 +152,18 @@ namespace ClientServer.Helpers //5 mb? public static long MaxUploadFileSizeInByte = 1024 * 1024 * 5; + + /// <summary> + /// the <see cref="TestServerTraceLogWorker"/> executes every X minutes where X is this value + /// </summary> + public static int TestServerTraceLogWorkerIntervalInMinutes = 60 * 24; //1 day + + /// <summary> + /// delete trace logs on the test server that are older than this in minues + /// </summary> + public static int TestServerTraceLogDeleteTraceLogOlderThanMinutes = 60 * 24 * 15; // 14 days + + public static int TestServerTraceLogDeleteTraceLogTimeoutInMs = 1000 * 60; //60s //--- methods --- @@ -383,6 +395,44 @@ namespace ClientServer.Helpers overwriteCount++; } } + + if (string.IsNullOrWhiteSpace(ConfigurationRoot[Constants.TestServerTraceLogWorkerIntervalInMinutesKey]) == false) + { + var temp = ConfigurationRoot[Constants.TestServerTraceLogWorkerIntervalInMinutesKey]; + int tempValue; + if (int.TryParse(temp, out tempValue)) + { + //keep the setting + AppConfiguration.TestServerTraceLogWorkerIntervalInMinutes = tempValue; + overwriteCount++; + } + } + + if (string.IsNullOrWhiteSpace(ConfigurationRoot[Constants.TestServerTraceLogDeleteTraceLogOlderThanMinutesKey]) == false) + { + var temp = ConfigurationRoot[Constants.TestServerTraceLogDeleteTraceLogOlderThanMinutesKey]; + int tempValue; + if (int.TryParse(temp, out tempValue)) + { + //keep the setting + AppConfiguration.TestServerTraceLogDeleteTraceLogOlderThanMinutes = tempValue; + overwriteCount++; + } + } + + if (string.IsNullOrWhiteSpace(ConfigurationRoot[Constants.TestServerTraceLogDeleteTraceLogTimeoutInSecondsKey]) == false) + { + var temp = ConfigurationRoot[Constants.TestServerTraceLogDeleteTraceLogTimeoutInSecondsKey]; + int tempValue; + if (int.TryParse(temp, out tempValue)) + { + //keep the setting + AppConfiguration.TestServerTraceLogDeleteTraceLogTimeoutInMs = tempValue; + overwriteCount++; + } + } + + Console.WriteLine($"[INFO] {overwriteCount} app settings overwritten by appsettings.json"); } diff --git a/src/ClientServer/Config/Constants.cs b/src/ClientServer/Config/Constants.cs index f331451ea8556a50b26718d9fe7e0c04fa65a9f2..d1aae06d1f757df43e22d11149b8abecf1fe5d51 100644 --- a/src/ClientServer/Config/Constants.cs +++ b/src/ClientServer/Config/Constants.cs @@ -13,7 +13,7 @@ namespace ClientServer.Helpers /// </summary> public static class Constants { - public static string VersionString = "2.5.12"; + public static string VersionString = "2.6.0"; /// <summary> /// this is only set once at program.cs!! @@ -204,9 +204,10 @@ namespace ClientServer.Helpers public static string MaxUploadFileSizeInByteKey = "MaxUploadFileSizeInByte"; - - - + + public static string TestServerTraceLogWorkerIntervalInMinutesKey = "TestServerTraceLogWorkerIntervalInMinutes"; + public static string TestServerTraceLogDeleteTraceLogOlderThanMinutesKey = "TestServerTraceLogDeleteTraceLogOlderThanMinutes"; + public static string TestServerTraceLogDeleteTraceLogTimeoutInSecondsKey = "TestServerTraceLogDeleteTraceLogTimeoutInSecondsKey"; //--- some core constants --- diff --git a/src/ClientServer/Config/InitialSystemSettings.cs b/src/ClientServer/Config/InitialSystemSettings.cs index 32a34b609a3fb84ebba70354a260edd70ee43b26..99878fd0eec3f5fddfcca43acd30ca74a172d47d 100644 --- a/src/ClientServer/Config/InitialSystemSettings.cs +++ b/src/ClientServer/Config/InitialSystemSettings.cs @@ -40,6 +40,11 @@ namespace ClientServer.Helpers /// the test server stats url /// </summary> public static string TestServerStatsUrl = "http://localhost:8003/stats/"; + + /// <summary> + /// the submit test server stats url + /// </summary> + public static string SubmitTestServerStatsUrl = "http://localhost:8003/stats/"; /// <summary> /// the submit test server url, used to run submit tests @@ -50,6 +55,11 @@ namespace ClientServer.Helpers /// the url to the ui to configure the test server /// </summary> public static string TestServerConfigUiUrl = "http://localhost:8000"; + + /// <summary> + /// the url to the ui to configure the submit test server + /// </summary> + public static string SubmitTestServerConfigUiUrl = "http://localhost:8000"; /// <summary> /// the timeout in ms to wait for a response from the test server before returning a testserver connection error @@ -117,6 +127,11 @@ namespace ClientServer.Helpers /// </summary> public static int CustomProjectTestMaxDiskSpaceInKb = 1000; + /// <summary> + /// the initial test server trace log api url + /// </summary> + public static string TestServerTraceLogApiUrl = "http://localhost:8003/worker/traceLog.php"; + //--- these cannot be used because the default role could have changed and deleted so this is would be invalid... diff --git a/src/ClientServer/Controllers/Core/SystemSettingsController.cs b/src/ClientServer/Controllers/Core/SystemSettingsController.cs index 2956a9b2d6510ca0be59b761e2304ecfdd6bfe59..1156f4c3a7dad59c35aca1624a9961578dca62a1 100644 --- a/src/ClientServer/Controllers/Core/SystemSettingsController.cs +++ b/src/ClientServer/Controllers/Core/SystemSettingsController.cs @@ -72,11 +72,13 @@ namespace ClientServer.Controllers.Core CurrentServerMessage = systemSettings.CurrentServerMessage, SubmitTestServerTimeoutInMs = systemSettings.SubmitTestServerTimeoutInMs, TestServerStatsUrl = systemSettings.TestServerStatsUrl, + SubmitTestServerStatsUrl = systemSettings.SubmitTestServerStatsUrl, TestServerTimeoutInMs = systemSettings.TestServerTimeoutInMs, MaxCustomTestsPerParticipation = systemSettings.MaxCustomTestsPerParticipation, TestServerUrl = systemSettings.TestServerUrl, SubmitTestServerUrl = systemSettings.SubmitTestServerUrl, TestServerConfigUiUrl = systemSettings.TestServerConfigUiUrl, + SubmitTestServerConfigUiUrl = systemSettings.SubmitTestServerConfigUiUrl, MaxCustomProjectsPerUser = systemSettings.MaxCustomProjectsPerUser, MaxNumberOfTestsWithOneRequest = systemSettings.MaxNumberOfTestsWithOneRequest, MaxNumberOfTestsWithOneRequestSubmitTestServer = @@ -91,7 +93,8 @@ namespace ClientServer.Controllers.Core CustomProjectTestMaxDiskSpaceInKb = systemSettings.CustomProjectTestMaxDiskSpaceInKb, DefaultGroupRoleId = systemSettings.DefaultGroupRoleId, DefaultUserGroupId = systemSettings.DefaultUserGroupId, - DefaultGroupCreatorGroupRoleId = systemSettings.DefaultGroupCreatorGroupRoleId + DefaultGroupCreatorGroupRoleId = systemSettings.DefaultGroupCreatorGroupRoleId, + TestServerTraceLogApiUrl = systemSettings.TestServerTraceLogApiUrl, }; } @@ -182,11 +185,13 @@ namespace ClientServer.Controllers.Core CurrentServerMessage = InitialSystemSettings.CurrentServerMessage, SubmitTestServerTimeoutInMs = InitialSystemSettings.SubmitTestServerTimeoutInMs, TestServerStatsUrl = InitialSystemSettings.TestServerStatsUrl, + SubmitTestServerStatsUrl = InitialSystemSettings.SubmitTestServerStatsUrl, TestServerTimeoutInMs = InitialSystemSettings.TestServerTimeoutInMs, MaxCustomTestsPerParticipation = InitialSystemSettings.MaxCustomTestsPerParticipation, TestServerUrl = InitialSystemSettings.TestServerUrl, SubmitTestServerUrl = InitialSystemSettings.SubmitTestServerUrl, TestServerConfigUiUrl = InitialSystemSettings.TestServerConfigUiUrl, + SubmitTestServerConfigUiUrl = InitialSystemSettings.SubmitTestServerConfigUiUrl, MaxCustomProjectsPerUser = InitialSystemSettings.MaxCustomProjectsPerUser, MaxNumberOfTestsWithOneRequest = InitialSystemSettings.MaxNumberOfTestsWithOneRequest, MaxNumberOfTestsWithOneRequestSubmitTestServer = @@ -202,7 +207,8 @@ namespace ClientServer.Controllers.Core //the default group could be changed and deleted so this could be invalid DefaultGroupRoleId = -1, DefaultUserGroupId = -1, - DefaultGroupCreatorGroupRoleId = -1 + DefaultGroupCreatorGroupRoleId = -1, + TestServerTraceLogApiUrl = InitialSystemSettings.TestServerTraceLogApiUrl }; await @@ -272,10 +278,12 @@ namespace ClientServer.Controllers.Core oldSystemSettings.CurrentServerMessage = systemSettings.CurrentServerMessage; oldSystemSettings.SubmitTestServerTimeoutInMs = systemSettings.SubmitTestServerTimeoutInMs; oldSystemSettings.TestServerStatsUrl = systemSettings.TestServerStatsUrl; + oldSystemSettings.SubmitTestServerStatsUrl = systemSettings.SubmitTestServerStatsUrl; oldSystemSettings.TestServerTimeoutInMs = systemSettings.TestServerTimeoutInMs; oldSystemSettings.MaxCustomTestsPerParticipation = systemSettings.MaxCustomTestsPerParticipation; oldSystemSettings.TestServerUrl = systemSettings.TestServerUrl; oldSystemSettings.TestServerConfigUiUrl = systemSettings.TestServerConfigUiUrl; + oldSystemSettings.SubmitTestServerConfigUiUrl = systemSettings.SubmitTestServerConfigUiUrl; oldSystemSettings.SubmitTestServerUrl = systemSettings.SubmitTestServerUrl; oldSystemSettings.MaxCustomProjectsPerUser = systemSettings.MaxCustomProjectsPerUser; oldSystemSettings.MaxNumberOfTestsWithOneRequest = systemSettings.MaxNumberOfTestsWithOneRequest; @@ -295,6 +303,7 @@ namespace ClientServer.Controllers.Core oldSystemSettings.DefaultGroupRoleId = systemSettings.DefaultGroupRoleId; oldSystemSettings.DefaultUserGroupId = systemSettings.DefaultUserGroupId; oldSystemSettings.DefaultGroupCreatorGroupRoleId = systemSettings.DefaultGroupCreatorGroupRoleId; + oldSystemSettings.TestServerTraceLogApiUrl = systemSettings.TestServerTraceLogApiUrl; try { @@ -311,11 +320,13 @@ namespace ClientServer.Controllers.Core CurrentServerMessage = oldSystemSettings.CurrentServerMessage, SubmitTestServerTimeoutInMs = oldSystemSettings.SubmitTestServerTimeoutInMs, TestServerStatsUrl = oldSystemSettings.TestServerStatsUrl, + SubmitTestServerStatsUrl = oldSystemSettings.SubmitTestServerStatsUrl, TestServerTimeoutInMs = oldSystemSettings.TestServerTimeoutInMs, MaxCustomTestsPerParticipation = oldSystemSettings.MaxCustomTestsPerParticipation, TestServerUrl = oldSystemSettings.TestServerUrl, SubmitTestServerUrl = oldSystemSettings.SubmitTestServerUrl, TestServerConfigUiUrl = systemSettings.TestServerConfigUiUrl, + SubmitTestServerConfigUiUrl = systemSettings.SubmitTestServerConfigUiUrl, MaxCustomProjectsPerUser = oldSystemSettings.MaxCustomProjectsPerUser, MaxNumberOfTestsWithOneRequest = oldSystemSettings.MaxNumberOfTestsWithOneRequest, MaxNumberOfTestsWithOneRequestSubmitTestServer = @@ -330,7 +341,8 @@ namespace ClientServer.Controllers.Core CustomProjectTestMaxDiskSpaceInKb = oldSystemSettings.CustomProjectTestMaxDiskSpaceInKb, DefaultGroupRoleId = oldSystemSettings.DefaultGroupRoleId, DefaultUserGroupId = oldSystemSettings.DefaultUserGroupId, - DefaultGroupCreatorGroupRoleId = oldSystemSettings.DefaultGroupCreatorGroupRoleId + DefaultGroupCreatorGroupRoleId = oldSystemSettings.DefaultGroupCreatorGroupRoleId, + TestServerTraceLogApiUrl = oldSystemSettings.TestServerTraceLogApiUrl, }; await @@ -340,13 +352,18 @@ namespace ClientServer.Controllers.Core } - [HttpGet("testserver/settings/public")] - public async Task GetTestServerSettings() + [HttpGet("testserver/settings/public/{isSubmitTestServerSettings}")] + public async Task GetTestServerSettings(bool isSubmitTestServerSettings) { if (!await base.IsLoggedIn()) return; + await _GetSubmitOrNormalTestServerSettings(isSubmitTestServerSettings); + } + private async Task _GetSubmitOrNormalTestServerSettings(bool isSubmitTestServerSettings) + { var systemSettings = await _context.SystemSettings.FirstOrDefaultAsync(); + string errorReportingName = isSubmitTestServerSettings ? "submit" : "normal"; if (systemSettings == null) { @@ -364,8 +381,16 @@ namespace ClientServer.Controllers.Core { var tokenSource = new CancellationTokenSource(); - client.Timeout = new TimeSpan(0, 0, 0, 0, systemSettings.TestServerTimeoutInMs); - tokenSource.CancelAfter(new TimeSpan(0, 0, 0, 0, systemSettings.TestServerTimeoutInMs)); + //abuse test running timeouts here... + + client.Timeout = new TimeSpan(0, 0, 0, 0, isSubmitTestServerSettings + ? systemSettings.SubmitTestServerTimeoutInMs + : systemSettings.TestServerTimeoutInMs); + + tokenSource.CancelAfter(new TimeSpan(0, 0, 0, 0, isSubmitTestServerSettings + ? systemSettings.SubmitTestServerTimeoutInMs + : systemSettings.TestServerTimeoutInMs) + ); var requestPayload = new StringContent("", SubmissionController.UTF8WithoutBom, "application/json"); @@ -373,18 +398,20 @@ namespace ClientServer.Controllers.Core try { jsonResponse = await client.PostAsync( - UrlHelper.GetTestServerStatsUrl(systemSettings.TestServerStatsUrl, + UrlHelper.GetTestServerStatsUrl(isSubmitTestServerSettings + ? systemSettings.SubmitTestServerStatsUrl + : systemSettings.TestServerStatsUrl, Constants.TestServerPublicStatsUrlPart), requestPayload, tokenSource.Token); } catch (Exception ex) { - Console.WriteLine("ERROR getting test server settings: " + ex); + Console.WriteLine($"ERROR getting {errorReportingName} test server settings: " + ex); //throw; await Response.WriteAsync( Jc.Serialize(new BasicResponseWithData<TestServerPublicSettings>(ResponseCode.PartialOk, - "error getting test server settings", InvalidTestServerPublicSettings))); + $"error getting {errorReportingName} test server settings", InvalidTestServerPublicSettings))); return; } } @@ -402,7 +429,7 @@ namespace ClientServer.Controllers.Core if (answerFromTestServer.ResponseCode != (int) TestServerResponseCode.Ok && AppConfiguration.IsDebugMode) { - Console.WriteLine("not ok response code from test server, content: " + contentResult); + Console.WriteLine($"not ok response code from {errorReportingName} test server, content: " + contentResult); } } } @@ -414,7 +441,7 @@ namespace ClientServer.Controllers.Core await Response.WriteAsync( Jc.Serialize(new BasicResponseWithData<TestServerPublicSettings>(ResponseCode.PartialOk, - "could not deserialize test server settings", InvalidTestServerPublicSettings))); + $"could not deserialize test {errorReportingName} server settings", InvalidTestServerPublicSettings))); return; } @@ -422,7 +449,7 @@ namespace ClientServer.Controllers.Core { await Response.WriteAsync( Jc.Serialize(new BasicResponseWithData<TestServerPublicSettings>(ResponseCode.PartialOk, - "received test server config null or could not deserialize", InvalidTestServerPublicSettings))); + $"received {errorReportingName} test server config null or could not deserialize", InvalidTestServerPublicSettings))); return; } @@ -446,12 +473,16 @@ namespace ClientServer.Controllers.Core public string CurrentServerMessage { get; set; } public string TestServerStatsUrl { get; set; } + + public string SubmitTestServerStatsUrl { get; set; } public string TestServerUrl { get; set; } public string SubmitTestServerUrl { get; set; } public string TestServerConfigUiUrl { get; set; } + + public string SubmitTestServerConfigUiUrl { get; set; } public int TestServerTimeoutInMs { get; set; } @@ -486,6 +517,11 @@ namespace ClientServer.Controllers.Core /// the default group when a user creates a new group /// </summary> public int DefaultGroupCreatorGroupRoleId { get; set; } + + /// <summary> + /// the test server trace log api url + /// </summary> + public string TestServerTraceLogApiUrl { get; set; } } /// <summary> diff --git a/src/ClientServer/Controllers/Core/Testing/TestingController.cs b/src/ClientServer/Controllers/Core/Testing/TestingController.cs index bd901ba0d9a03dd117660120ac439f1f778b3629..83483bc2368ceb7c537797f93174eb4a4e9d10e9 100644 --- a/src/ClientServer/Controllers/Core/Testing/TestingController.cs +++ b/src/ClientServer/Controllers/Core/Testing/TestingController.cs @@ -60,7 +60,7 @@ namespace ClientServer.Controllers.Core.Testing /// <summary> /// a unique id for the testing request (use unchecked mode in case we reach max) /// also use the userId as prefix in case we spread across several backend nodes - /// BUT there is still a small chance that one user runs > 1 tests with the same userId_requestSequentialNumber + /// BUT there is still a small chance that one user runs > 1 tests with the same userId_requestSequentialNumber (when using multiple server instances/nodes) /// /// this also needs to be thread safe!! /// </summary> @@ -79,7 +79,7 @@ namespace ClientServer.Controllers.Core.Testing /// <summary> /// compiles the given single file on the test server - /// user can check file that is not yet saved + /// user can check a file that is not yet saved /// THIS only checks if the user is logged in, no time is up check for some exercise or release /// /// files are used from frontend --> readonly replace is not need (replace solution readonly file with exercise template readonly file) @@ -206,7 +206,9 @@ namespace ClientServer.Controllers.Core.Testing { //use the running userid as request distinct number answerFromTestServer = await this._RunTestOnTestServer(solution, new List<AllTest>() {test}, - pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings); + pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings, + $"compileSingleFile_executingUser:{userId}_releaseCode:{runCompileSingleFileCommand.GeneratedCode}" + ); } catch (Exception ex) { @@ -353,7 +355,9 @@ namespace ClientServer.Controllers.Core.Testing { //use the running userid as request distinct number answerFromTestServer = await this._RunTestOnTestServer(solution, new List<AllTest>() {test}, - pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings); + pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings, + $"compileSingleFileCustomProject_executingUser:{userId}_customProjectId:{runCompileSingleFileCommand.CustomProjectId}_plangId:{runCompileSingleFileCommand.PlangId}_mainFileId:{solution.MainFileId}" + ); } catch (Exception ex) { @@ -525,7 +529,9 @@ namespace ClientServer.Controllers.Core.Testing { //use the running userid as request distinct number answerFromTestServer = await this._RunTestOnTestServer(solution, new List<AllTest>() {test}, - pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings); + pLangToUse, systemSettings.TestServerUrl, false, userId, systemSettings, + $"compileSingleFileTutorView_executingUser:{userId}_releaseCode:{runCompileSingleFileCommand.GeneratedCode}" + ); } catch (Exception ex) { @@ -741,13 +747,14 @@ namespace ClientServer.Controllers.Core.Testing //or a normal solution edit (solution can still be edited) Solution userSolution; + AfterSolution afterSolution = null; //check if available working time is up if (ReleaseHelper.IsTimeUp(participation, participation.ExerciseRelease)) { //then the user is editing the after solution... - var afterSolution = await _context.AfterSolutions + afterSolution = await _context.AfterSolutions .Include(p => p.SolutionFiles) .ThenInclude(p => p.TemplateFile) .Include(p => p.TestResults) //only include tests here because its a normal test (not a custom test) @@ -840,8 +847,14 @@ namespace ClientServer.Controllers.Core.Testing //--- run test + string traceStringPartSolution = afterSolution != null + ? TraceStringHelper.GetTraceStringPartAfterSolution(afterSolution) + : TraceStringHelper.GetTraceStringPartSolution(userSolution) + ; - results = await this._RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, false); + results = await this._RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, + $"justRunProgram_executingUser:{userId}_{traceStringPartSolution}", + false); //this is handled in the method if (results == null) return; @@ -864,7 +877,7 @@ namespace ClientServer.Controllers.Core.Testing /// </summary> /// <param name="command"></param> [HttpPost("justrunprogram/custom/project")] - public async Task RequestJustRunProgram([FromBody] JustRunProgramTestCustomProjectCommandForBackend command) + public async Task RequestJustRunProgramCustomProject([FromBody] JustRunProgramTestCustomProjectCommandForBackend command) { if (!await base.IsLoggedIn()) return; @@ -917,13 +930,13 @@ namespace ClientServer.Controllers.Core.Testing //just find the correct version then start another query to get the solution version + files - var solution = await _context.CustomProjectSolutions + var customProjectSolution = await _context.CustomProjectSolutions .Include(p => p.SolutionFiles) .Include(p => p.TestResults) .FirstOrDefaultAsync(p => p.CustomProjectId == customProject.Id && p.PLangId == pLangToUse.Id) ; - if (solution == null) + if (customProjectSolution == null) { await Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "solution not found"))); @@ -931,7 +944,7 @@ namespace ClientServer.Controllers.Core.Testing } - var solutionFiles = solution.SolutionFiles.Select(p => new SolutionFile() + var solutionFiles = customProjectSolution.SolutionFiles.Select(p => new SolutionFile() { Id = p.Id, Content = p.Content, @@ -942,7 +955,7 @@ namespace ClientServer.Controllers.Core.Testing TemplateFileId = -1 }).ToList(); - var mainFile = solutionFiles.FirstOrDefault(p => p.Id == solution.MainFileId); + var mainFile = solutionFiles.FirstOrDefault(p => p.Id == customProjectSolution.MainFileId); if (mainFile == null) { @@ -952,14 +965,14 @@ namespace ClientServer.Controllers.Core.Testing return; } - var solutionVersion = new Solution() + var tmpSolution = new Solution() { - CreatedAt = solution.CreatedAt, + CreatedAt = customProjectSolution.CreatedAt, SolutionFiles = solutionFiles, CustomTestResults = null, Note = "", TestResults = null, - MainFileId = solution.MainFileId, + MainFileId = customProjectSolution.MainFileId, MainFile = mainFile }; @@ -969,8 +982,12 @@ namespace ClientServer.Controllers.Core.Testing var allTests = new List<AllTest>(); allTests.Add(justRunTest); + string traceStringPartSolution = TraceStringHelper.GetTraceStringPartCustomProjectSolution(customProjectSolution); + List<TestAnswerFromBackend> results = - await this._RunTempTests(userId, solutionVersion, pLangToUse, allTests, systemSettings, false); + await this._RunTempTests(userId, tmpSolution, pLangToUse, allTests, systemSettings, + $"requestJustRunProgramCustomProject_executingUser:{userId}_customProjectId:{command.CustomProjectId}_{traceStringPartSolution}", + false); //this is handled in the method if (results == null) return; @@ -1131,7 +1148,9 @@ namespace ClientServer.Controllers.Core.Testing allTests.Add(justRunTest); List<TestAnswerFromBackend> results = - await this._RunTempTests(userId, solution, pLangToUse, allTests, systemSettings, false); + await this._RunTempTests(userId, solution, pLangToUse, allTests, systemSettings, + $"justRunProgramTutor_executingUser:{userId}_releaseId:{exerciseRelease.Id}", + false); //this is handled in the method if (results == null) return; @@ -1326,7 +1345,7 @@ namespace ClientServer.Controllers.Core.Testing /// this does not checks the available working time or if the release is still released! /// </summary> /// <param name="runNormalTestCommand">the test to run (with all dependecies) from frontend</param> - /// <param name="userId">the user id</param> + /// <param name="userId">the executing user id</param> /// <param name="plang">the plang to use</param> /// <param name="exerciseRelease">the exercise release to find the user solution, make sure exercise and coded template is loadedd!</param> /// <param name="isSubmitTest">true: is a submit test, false: not</param> @@ -1473,7 +1492,12 @@ namespace ClientServer.Controllers.Core.Testing //---run the test - var testResultAnswerFromBackend = await _RunTempTests(userId, userSolution, plang, allTests, systemSettings, false); + string solutionTraceString = TraceStringHelper.GetTraceStringPartSolution(userSolution); + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); + + var testResultAnswerFromBackend = await _RunTempTests(userId, userSolution, plang, allTests, systemSettings, + $"runNormalOrSubmitTest_executingUser:{userId}_{solutionTraceString}_{testTraceString}", + false); if (testResultAnswerFromBackend == null) { @@ -1811,8 +1835,13 @@ namespace ClientServer.Controllers.Core.Testing //ignore isSubmit tests and use normal test conditions... this is to protect the submit test server from //user spamming BUT this might introduce inconsistent test results because submit test server can have different settings e.g. timeouts + string solutionTraceString = TraceStringHelper.GetTraceStringPartAfterSolution(afterSolution); + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); + var testResultAnswerFromBackend = - await _RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, false); + await _RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, + $"runAfterTest_normalOrSubmit_executingUser:{userId}_{solutionTraceString}_{testTraceString}", + false); if (testResultAnswerFromBackend == null) @@ -1990,8 +2019,13 @@ namespace ClientServer.Controllers.Core.Testing //ignore isSubmit tests and use normal test conditions... this is to protect the submit test server from //user spamming BUT this might introduce inconsistent test results because submit test server can have different settings e.g. timeouts + string solutionTraceString = TraceStringHelper.GetTraceStringPartAfterSolution(afterSolution); + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); + var testResultAnswerFromBackend = - await _RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, false); + await _RunTempTests(userId, userSolution, pLangToUse, allTests, systemSettings, + $"runAfterTest_customTest_executingUser:{userId}_{solutionTraceString}_{testTraceString}", + false); if (testResultAnswerFromBackend == null) @@ -2224,8 +2258,12 @@ namespace ClientServer.Controllers.Core.Testing return; } + string solutionTraceString = TraceStringHelper.GetTraceStringPartSolution(userSolution); + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); - var testResultAnswerFromBackend = await _RunTempTests(userId, userSolution, plang, allTests, systemSettings, false); + var testResultAnswerFromBackend = await _RunTempTests(userId, userSolution, plang, allTests, systemSettings, + $"runCustomTest_executingUser:{userId}_{solutionTraceString}_{testTraceString}", + false); if (testResultAnswerFromBackend == null) { @@ -2361,13 +2399,13 @@ namespace ClientServer.Controllers.Core.Testing return; } - var solution = await _context.CustomProjectSolutions + var customProjectSolution = await _context.CustomProjectSolutions .Include(p => p.SolutionFiles) .Include(p => p.TestResults) .FirstOrDefaultAsync(p => p.CustomProjectId == customProject.Id && p.PLangId == plang.Id) ; - if (solution == null) + if (customProjectSolution == null) { await Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "solution not found"))); @@ -2418,7 +2456,7 @@ namespace ClientServer.Controllers.Core.Testing } - var solutionFiles = solution.SolutionFiles.Select(p => new SolutionFile() + var solutionFiles = customProjectSolution.SolutionFiles.Select(p => new SolutionFile() { Id = p.Id, Content = p.Content, @@ -2429,7 +2467,7 @@ namespace ClientServer.Controllers.Core.Testing TemplateFileId = -1 }).ToList(); - var mainFile = solutionFiles.FirstOrDefault(p => p.Id == solution.MainFileId); + var mainFile = solutionFiles.FirstOrDefault(p => p.Id == customProjectSolution.MainFileId); if (mainFile == null) { @@ -2441,17 +2479,21 @@ namespace ClientServer.Controllers.Core.Testing var tempSolution = new Solution() { - CreatedAt = solution.CreatedAt, + CreatedAt = customProjectSolution.CreatedAt, SolutionFiles = solutionFiles, CustomTestResults = null, Note = "", TestResults = null, - MainFileId = solution.MainFileId, + MainFileId = customProjectSolution.MainFileId, MainFile = mainFile }; + string solutionTraceString = TraceStringHelper.GetTraceStringPartCustomProjectSolution(customProjectSolution); + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); - var testResultAnswerFromBackend = await _RunTempTests(userId, tempSolution, plang, allTests, systemSettings, false) + var testResultAnswerFromBackend = await _RunTempTests(userId, tempSolution, plang, allTests, systemSettings, + $"runCustomProjectCustomTest_executingUser:{userId}_{solutionTraceString}_{testTraceString}", + false) ; if (testResultAnswerFromBackend == null) @@ -2466,10 +2508,10 @@ namespace ClientServer.Controllers.Core.Testing //get the old custom test result - var oldResult = solution.TestResults + var oldResult = customProjectSolution.TestResults .FirstOrDefault( p => p.CustomProjectTestId == test.Id - && p.CustomProjectSolutionId == solution.Id); + && p.CustomProjectSolutionId == customProjectSolution.Id); //and store test result @@ -2478,7 +2520,7 @@ namespace ClientServer.Controllers.Core.Testing //no old result, create new one oldResult = new CustomProjectTestWithSolutionAsTestResult() { - CustomProjectSolution = solution, + CustomProjectSolution = customProjectSolution, CustomProjectTest = test, Passed = testResult.Passed, Protocol = string.Join(Constants.TestProtocolSplitString, testResult.Protocol), @@ -2803,8 +2845,15 @@ namespace ClientServer.Controllers.Core.Testing #region running tests + + string testTraceString = TraceStringHelper.GetTraceStringPartFromTests(allTests); + string testTypes = tutorCommand.IsNormalTest ? "normalTests" : "submitTests"; + var testResultAnswerFromBackend = - await _RunTempTests(userId, tempSolution, pLangToUse, allTests, systemSettings, tutorCommand.IsSubmitTest) + await _RunTempTests(userId, tempSolution, pLangToUse, allTests, systemSettings, + $"runAnyTestAsTutor_executingUser:{userId}_releaseCode:{tutorCommand.ReleaseCode}_{testTypes}_{testTraceString}", + tutorCommand.IsSubmitTest + ) ; if (testResultAnswerFromBackend == null) @@ -2876,9 +2925,10 @@ namespace ClientServer.Controllers.Core.Testing /// <param name="solution"></param> /// <param name="pLangToUse"></param> /// <param name="tests"></param> + /// <param name="traceString">some string to identify who wants to run the code and what code and test should be run (to trace back errors) <see cref="RunTestCommandForTestServer.TraceString"/> and <see cref="TraceStringHelper"/> for more information</param> /// <param name="isSubmitTest">true: use submit test (timeout), false: normal test</param> private async Task<List<TestAnswerFromBackend>> _RunTempTests(int userId, Solution solution, PLang pLangToUse, - List<AllTest> tests, SystemSetting systemSettings, bool isSubmitTest = false) + List<AllTest> tests, SystemSetting systemSettings, string traceString, bool isSubmitTest = false) { bool tooManyTestsRequested = false; @@ -2923,7 +2973,9 @@ namespace ClientServer.Controllers.Core.Testing var testServerUrl = isSubmitTest ? systemSettings.SubmitTestServerUrl : systemSettings.TestServerUrl; //use the running userid as request distinct number, it's unlikely that the user runs two tests at the same time... answerFromTestServer = await _RunTestOnTestServer(solution, allTests, pLangToUse, - testServerUrl, isSubmitTest, userId, systemSettings); + testServerUrl, isSubmitTest, userId, systemSettings, + traceString + ); } catch (Exception ex) { @@ -3089,10 +3141,12 @@ namespace ClientServer.Controllers.Core.Testing /// <param name="runOnSubmitTestServer">true: use the submit test server timeout, false: use the normal test server timeout</param> /// <param name="userId">a number to help the test server to separate two requests that arrive at the same time /// </param> + /// <param name="traceString">some string to identify who wants to run the code and what code and test should be run (to trace back errors) <see cref="RunTestCommandForTestServer.TraceString"/> and <see cref="TraceStringHelper"/> for more information</param> /// <returns>the answer from the test server </returns> internal async Task<Tuple<ResponseFromTestServer, string>> _RunTestOnTestServer( Solution solution, List<AllTest> tests, - PLang plang, string testServerUrl, bool runOnSubmitTestServer, int userId, SystemSetting systemSettings) + PLang plang, string testServerUrl, bool runOnSubmitTestServer, int userId, SystemSetting systemSettings, + string traceString) { var mainFile = solution.SolutionFiles.FirstOrDefault(p => p.Id == solution.MainFileId); @@ -3137,7 +3191,8 @@ namespace ClientServer.Controllers.Core.Testing }).ToList(), }).ToList(), RequestDistinctionString = userId + "_" + nextSequentialNumber, - CharacterLimitProtocol = YapexDbContext.TestProtocolMaxStringLength + CharacterLimitProtocol = YapexDbContext.TestProtocolMaxStringLength, + TraceString = traceString, }; @@ -3246,6 +3301,7 @@ namespace ClientServer.Controllers.Core.Testing return new Tuple<ResponseFromTestServer, string>(answerFromTestServer, contentResult); } + } /// <summary> @@ -3487,6 +3543,22 @@ namespace ClientServer.Controllers.Core.Testing /// </summary> public string RequestDistinctionString { get; set; } + /// <summary> + /// a string describing what we execute... the has no real shape + /// the idea is that we know where we get the source code for the request in case some code makes trouble + /// we can log this in the test server (or here) and we can lookup the source code and fix the bug + /// + /// so a good thing to start with is the userId who owns the code (even if a tutor runs the code we want the owner) + /// + /// + /// pattern: [user id how owns the code]_[solution primary key: userId, releaseId, plangId]_[first test id]_[number of total tests] + /// + /// the test server then saves the tuple: trace string + executed cmd + /// when a process won't terminate we can lookup the process with hop = the executed command + /// and get the connected trace string and find the source code + test that led to this + /// </summary> + public string TraceString { get; set; } + /// <summary> /// the plang the solution files are written in /// </summary> diff --git a/src/ClientServer/Controllers/dashboard/DashboardController.cs b/src/ClientServer/Controllers/dashboard/DashboardController.cs index 37d00a51925ce470d246c0dbce009e0103d442b3..56f64082846b8988adc483bec4fddf10999bef3c 100644 --- a/src/ClientServer/Controllers/dashboard/DashboardController.cs +++ b/src/ClientServer/Controllers/dashboard/DashboardController.cs @@ -287,15 +287,15 @@ namespace ClientServer.Controllers // int totalSubmissions2 = // await _context.ExerciseReleases // .SumAsync(p => p.ExerciseReleaseWithUserAsParticipations.Count); - + //or maybe only count where we have >2 / >5 submissions? else it's likely to be a test release //also excludes releases where we have no submissions int totalReleases = await _context.ExerciseReleases .CountAsync(p => p.ExerciseReleaseWithUserAsParticipations.Count >= 5); - + // int totalReleases2 = await _context.ExerciseReleases // .CountAsync(); - + int totalTags = await _context.Tags.CountAsync(); var statistic = new LogicalUsageStatistic() @@ -319,8 +319,8 @@ namespace ClientServer.Controllers } - [HttpGet("testserver/settings/private")] - public async Task GetTestServerSettings() + [HttpGet("testserver/settings/private/{isSubmitTestServerSettings}")] + public async Task GetTestServerSettings(bool isSubmitTestServerSettings) { if (!await base.IsLoggedIn()) return; @@ -333,6 +333,14 @@ namespace ClientServer.Controllers return; } + await _GetSubmitOrNormalTestServerSettings(isSubmitTestServerSettings); + } + + private async Task _GetSubmitOrNormalTestServerSettings(bool isSubmitTestServerSettings) + { + + string errorReportingName = isSubmitTestServerSettings ? "submit" : "normal"; + var systemSettings = await _context.SystemSettings.FirstOrDefaultAsync(); if (systemSettings == null) @@ -348,8 +356,16 @@ namespace ClientServer.Controllers { var tokenSource = new CancellationTokenSource(); - client.Timeout = new TimeSpan(0, 0, 0, 0, systemSettings.TestServerTimeoutInMs); - tokenSource.CancelAfter(new TimeSpan(0, 0, 0, 0, systemSettings.TestServerTimeoutInMs)); + //abuse test running timeouts here... + + client.Timeout = new TimeSpan(0, 0, 0, 0, isSubmitTestServerSettings + ? systemSettings.SubmitTestServerTimeoutInMs + : systemSettings.TestServerTimeoutInMs); + + tokenSource.CancelAfter(new TimeSpan(0, 0, 0, 0, isSubmitTestServerSettings + ? systemSettings.SubmitTestServerTimeoutInMs + : systemSettings.TestServerTimeoutInMs) + ); var requestPayload = new StringContent("", SubmissionController.UTF8WithoutBom, "application/json"); @@ -357,19 +373,21 @@ namespace ClientServer.Controllers try { jsonResponse = await client.PostAsync( - UrlHelper.GetTestServerStatsUrl(systemSettings.TestServerStatsUrl, + UrlHelper.GetTestServerStatsUrl(isSubmitTestServerSettings + ? systemSettings.TestServerStatsUrl + : systemSettings.SubmitTestServerStatsUrl, Constants.TestServerPrivateStatsUrlPart), requestPayload, tokenSource.Token); } catch (Exception ex) { - Console.WriteLine("ERROR getting test server settings: " + ex); + Console.WriteLine($"ERROR getting {errorReportingName} test server settings: " + ex); //throw; //only users with permission see this so we can output the full exception await Response.WriteAsync( Jc.Serialize(new BasicResponse(ResponseCode.ServerError, - "error getting test server settings, error: " + ex))); + $"error getting {errorReportingName} test server settings, error: " + ex))); return; } } @@ -387,7 +405,7 @@ namespace ClientServer.Controllers if (answerFromTestServer.ResponseCode != (int) TestServerResponseCode.Ok && AppConfiguration.IsDebugMode) { - Console.WriteLine("not ok response code from test server, content: " + contentResult); + Console.WriteLine($"not ok response code from {errorReportingName} test server, content: " + contentResult); } } } @@ -399,7 +417,7 @@ namespace ClientServer.Controllers await Response.WriteAsync( Jc.Serialize(new BasicResponse(ResponseCode.ServerError, - "could not deserialize test server settings"))); + $"could not deserialize {errorReportingName} test server settings"))); return; } @@ -407,7 +425,7 @@ namespace ClientServer.Controllers { await Response.WriteAsync( Jc.Serialize(new BasicResponse(ResponseCode.ServerError, - "received test server config null"))); + $"received {errorReportingName} test server config null"))); return; } diff --git a/src/ClientServer/Helpers/TraceStringHelper.cs b/src/ClientServer/Helpers/TraceStringHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..c926002409b06759f303afc446afb07cc7b7ff48 --- /dev/null +++ b/src/ClientServer/Helpers/TraceStringHelper.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using ClientServer.Models.CustomProjects; +using ClientServer.Models.Exercises.AfterSolutions; +using ClientServer.Models.Exercises.Solution; +using ClientServer.Models.Interfaces; + +namespace ClientServer.Helpers +{ + public static class TraceStringHelper + { + + /// <summary> + /// returns the trace string part for a solution + /// NOTE that the pk properties must be set! + /// </summary> + /// <param name="solution"></param> + /// <returns></returns> + public static string GetTraceStringPartSolution(Solution solution) + { + return $"solution::userId:{solution.UserId}_releaseId:{solution.ExerciseReleaseId}_plangId:{solution.PLangId}_mainFileId:{solution.MainFileId}"; + } + + /// <summary> + /// returns the trace string part for a solution + /// NOTE that the pk properties must be set! + /// </summary> + /// <param name="solution"></param> + /// <returns></returns> + public static string GetTraceStringPartAfterSolution(AfterSolution solution) + { + return $"afterSolution::userId:{solution.SolutionUserId}_releaseId:{solution.SolutionExerciseReleaseId}_plangId:{solution.SolutionPLangId}_mainFileId:{solution.MainFileId}"; + } + + /// <summary> + /// returns the trace string part for a solution + /// NOTE that the pk properties must be set! + /// </summary> + /// <param name="solution"></param> + /// <returns></returns> + public static string GetTraceStringPartCustomProjectSolution(CustomProjectSolution solution) + { + return $"solution::id:{solution.Id}_plangId:{solution.PLangId}_mainFileId:{solution.MainFileId}"; + } + + public static string GetTraceStringPartFromTests(List<AllTest> tests) + { + return $"tests_{tests.FirstOrDefault()}_{tests.Count}"; + } + } +} diff --git a/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.Designer.cs b/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.Designer.cs new file mode 100755 index 0000000000000000000000000000000000000000..c4a601c065de4a4289107df77eaef2acd46390e3 --- /dev/null +++ b/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.Designer.cs @@ -0,0 +1,2167 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using ClientServer.Db; +using ClientServer.Models.Exercises.Release; + +namespace ClientServer.Migrations +{ + [DbContext(typeof(YapexDbContext))] + [Migration("20191117200002_TestServerTraceLogs")] + partial class TestServerTraceLogs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.3"); + + modelBuilder.Entity("ClientServer.Models.AwaitDummy", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("AwaitDummies"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProject", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("LastEditorPLangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("CustomProjects"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescription", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(50000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId") + .IsUnique(); + + b.ToTable("CustomProjectDescriptions"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescriptionWithFileAsAssetReference", b => + { + b.Property<int>("CustomProjectDescriptionId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomProjectDescriptionId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomProjectDescriptionId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomProjectDescriptionWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolution", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("PLangId"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.ToTable("CustomProjectSolutions"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int?>("CustomProjectSolutionId") + .IsRequired(); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectSolutionId"); + + b.ToTable("CustomProjectSolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTest", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("Weight"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId"); + + b.HasIndex("TestTypeId"); + + b.ToTable("CustomProjectTests"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<byte[]>("Content"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.ToTable("CustomProjectTestAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectTestId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectTestId") + .IsUnique(); + + b.ToTable("CustomProjectTestSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithFileAsAssetReference", b => + { + b.Property<int>("CustomProjectTestId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomProjectTestId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomProjectTestId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomProjectTestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithSolutionAsTestResult", b => + { + b.Property<int>("CustomProjectSolutionId"); + + b.Property<int>("CustomProjectTestId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomProjectSolutionId", "CustomProjectTestId"); + + b.HasIndex("CustomProjectSolutionId"); + + b.HasIndex("CustomProjectTestId"); + + b.ToTable("CustomProjectTestWithSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<int>("SolutionUserId"); + + b.HasKey("Id"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .IsUnique(); + + b.ToTable("AfterSolutions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("AfterSolutionId"); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("TemplateFileId"); + + b.HasKey("Id"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("TemplateFileId"); + + b.ToTable("AfterSolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.CustomTestWithAfterSolutionAsTestResult", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("AfterSolutionId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomTestId", "AfterSolutionId"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("CustomTestId"); + + b.ToTable("CustomTestWithAfterSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.TestWithAfterSolutionAsTestResult", b => + { + b.Property<int>("TestId"); + + b.Property<int>("AfterSolutionId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("TestId", "AfterSolutionId"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("TestId"); + + b.ToTable("TestWithAfterSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CodeTemplate", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("PLangId"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.ToTable("Templates"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CustomTestWithFileAsAssetReference", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomTestId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomTestId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomTestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Exercise", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanUserCreateCustomTests"); + + b.Property<bool>("CanUserCreateFiles"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<bool>("IsOnlyVisibleToOwner"); + + b.Property<bool>("IsPermanentlyLocked"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.Property<string>("ShortDescription") + .HasMaxLength(2000); + + b.Property<int>("UserGroupId"); + + b.Property<int?>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("Exercises"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescription", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(50000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("ExerciseDescriptions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescriptionWithFileAsAssetReference", b => + { + b.Property<int>("ExerciseDescriptionId"); + + b.Property<int>("FileReferenceMarkdownAssetId"); + + b.HasKey("ExerciseDescriptionId", "FileReferenceMarkdownAssetId"); + + b.HasIndex("ExerciseDescriptionId"); + + b.HasIndex("FileReferenceMarkdownAssetId"); + + b.ToTable("ExerciseDescriptionWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.MetaData", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("MetaDatas"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Release.ExerciseRelease", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime?>("AutomaticEndAt"); + + b.Property<DateTime?>("AutomaticStartAt"); + + b.Property<int>("AvailableWorkingTimeInMinutes"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<string>("GeneratedCode") + .IsRequired() + .HasMaxLength(2000); + + b.Property<bool>("HadAutomaticAssessmentErrors"); + + b.Property<bool>("HasAutomaticAssessmentFinished"); + + b.Property<bool>("HasAutomaticAssessmentStarted"); + + b.Property<bool>("HasLimitedWorkingTime"); + + b.Property<bool>("HideExerciseLeaveActions"); + + b.Property<bool>("HideInOverviews"); + + b.Property<bool>("HidePrintOptions"); + + b.Property<bool>("HideSiteHeaderBar"); + + b.Property<bool>("IsReleased"); + + b.Property<bool>("IsVisibleToAllAfterRelease"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MaxManualPoint"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.Property<int>("PLangId"); + + b.Property<int>("ReleaseDurationType"); + + b.Property<int>("ReleaseStartType"); + + b.Property<DateTime?>("ReleasedAt"); + + b.Property<bool>("RunAlsoNormalTests"); + + b.Property<bool>("ShouldAutomaticAssessSubmissions"); + + b.Property<bool>("ShouldClearClipboard"); + + b.Property<bool>("ShowAdditionalLogButton"); + + b.HasKey("Id"); + + b.HasAlternateKey("GeneratedCode") + .HasName("Unique_GeneratedCode"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("PLangId"); + + b.ToTable("ExerciseReleases"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.CustomTestWithSingleSolutionAsTestResult", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomTestId", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.HasIndex("CustomTestId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("CustomTestWithSingleSolutionAsTestResult"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", b => + { + b.Property<int>("UserId"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("LastEditedPLangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("LockSolutionsFlag"); + + b.Property<bool>("ShouldNotCount"); + + b.HasKey("UserId", "ExerciseReleaseId"); + + b.HasIndex("ExerciseReleaseId"); + + b.HasIndex("LastEditedPLangId"); + + b.HasIndex("UserId"); + + b.ToTable("ExerciseReleaseWithUserAsParticipations"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.Solution", b => + { + b.Property<int>("UserId"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<int>("PLangId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("LastEditingIpAddress") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.HasKey("UserId", "ExerciseReleaseId", "PLangId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.HasIndex("UserId", "ExerciseReleaseId"); + + b.ToTable("Solutions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionAssessment", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<string>("FeedbackForStudent") + .HasMaxLength(10000); + + b.Property<string>("LastAssessmentErrorMessage") + .HasMaxLength(80000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("ManualPoints"); + + b.Property<int>("MaxNormalTestPoints"); + + b.Property<int>("MaxSubmitTestPoints"); + + b.Property<int?>("NormalTestPoints"); + + b.Property<string>("NoteForOtherTutors") + .HasMaxLength(10000); + + b.Property<int>("PLangId"); + + b.Property<int?>("SubmitTestPoints"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ExerciseReleaseId", "PLangId") + .IsUnique(); + + b.ToTable("SolutionAssessment"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int?>("TemplateFileId"); + + b.HasKey("Id"); + + b.HasIndex("TemplateFileId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("SolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.TestWithSingleSolutionAsTestResult", b => + { + b.Property<int>("TestId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("TestId", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.HasIndex("TestId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("TestWithSingleSolutionAsTestResult"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tag", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Description") + .HasMaxLength(2000); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("HtmlBackgroundColor") + .HasMaxLength(2000); + + b.Property<string>("HtmlColor") + .HasMaxLength(2000); + + b.Property<string>("HtmlIcon") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("DisplayName") + .HasName("Unique_Tag"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TagWithMetaData", b => + { + b.Property<int>("TagId"); + + b.Property<int>("MetaDataId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("TagId", "MetaDataId"); + + b.HasIndex("MetaDataId"); + + b.HasIndex("TagId"); + + b.ToTable("TagWithMetaDatas"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TemplateFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int?>("CodeTemplateId") + .IsRequired(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsContentVisibleForUser"); + + b.Property<bool>("IsEditableByUser"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CodeTemplateId"); + + b.ToTable("TemplateFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.CustomTest", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("TestTypeId"); + + b.HasIndex("UserId", "ExerciseReleaseId"); + + b.ToTable("CustomTest"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("DefaultCustomTestSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.Test", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("ExerciseId"); + + b.Property<bool>("IsSubmitTest"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("Weight"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("TestTypeId"); + + b.ToTable("Tests"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TestId"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("TestId") + .IsUnique(); + + b.ToTable("TestCaseSettingses"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestType", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("InternalName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("TestTypes"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TestWithFileAsAssetReference", b => + { + b.Property<int>("TestId"); + + b.Property<int>("FileReferenceTestAssetId"); + + b.HasKey("TestId", "FileReferenceTestAssetId"); + + b.HasIndex("FileReferenceTestAssetId"); + + b.HasIndex("TestId"); + + b.ToTable("TestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceMarkdownAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceMarkdownAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceTestAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceTestAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceUserFileAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceUserFileAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Lang", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("LangShortcut") + .HasMaxLength(2000); + + b.Property<string>("Language") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("Langs"); + }); + + modelBuilder.Entity("ClientServer.Models.PLang", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("EditorHighlightModeName") + .HasMaxLength(2000); + + b.Property<string>("FileExtensionsWithDot") + .HasMaxLength(2000); + + b.Property<string>("InternalName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("PLangs"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.AuthToken", b => + { + b.Property<int>("UserId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("CsrfToken") + .HasMaxLength(2000); + + b.Property<DateTime>("ExpirationDateTime"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("RandomSecret") + .HasMaxLength(2000); + + b.Property<string>("UserAuthToken") + .HasMaxLength(2000); + + b.HasKey("UserId"); + + b.HasIndex("UserAuthToken") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AuthTokens"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.ExternalUser", b => + { + b.Property<int>("ExternalId") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<string>("FirstName") + .HasMaxLength(2000); + + b.Property<string>("LastName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("NeedToRefreshData"); + + b.Property<string>("Token") + .IsRequired() + .HasMaxLength(2000); + + b.Property<int>("UserId"); + + b.HasKey("ExternalId"); + + b.HasAlternateKey("Token") + .HasName("Unique_ExternalToken"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ExternalUsers"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRole", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("GroupRoles"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRolePermission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanAddUserToGroup"); + + b.Property<bool>("CanAssessExercises"); + + b.Property<bool>("CanChangeExercises"); + + b.Property<bool>("CanChangeGroupData"); + + b.Property<bool>("CanChangeOtherMembersRole"); + + b.Property<bool>("CanCreateExercises"); + + b.Property<bool>("CanDeleteExercises"); + + b.Property<bool>("CanLockExercisesPermanently"); + + b.Property<bool>("CanManageExerciseReleases"); + + b.Property<bool>("CanRemoveMemberFromGroup"); + + b.Property<bool>("CanSeeExercisesFromOthersInGroup"); + + b.Property<bool>("CanSeeOtherMembers"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("GroupRoleId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("GroupRoleId") + .IsUnique(); + + b.ToTable("GroupRolePermissions"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.CodeEditorSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("FontSize"); + + b.Property<bool>("HighlightCurrentLine"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("ShowInvisibles"); + + b.Property<bool>("ShowLineIndentions"); + + b.Property<bool>("ShowLineNumbers"); + + b.Property<int>("TabSize"); + + b.Property<string>("Theme") + .HasMaxLength(2000); + + b.Property<bool>("UseWrapping"); + + b.Property<int>("UserSettingId"); + + b.HasKey("Id"); + + b.HasIndex("UserSettingId") + .IsUnique(); + + b.ToTable("CodeEditorSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.UserSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int?>("LangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Theme") + .HasMaxLength(2000); + + b.Property<int?>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("LangId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserSettingses"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRole", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("SystemRoles"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRolePermission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanChangeOtherUsersSystemRole"); + + b.Property<bool>("CanChangeRoles"); + + b.Property<bool>("CanChangeSystemSettings"); + + b.Property<bool>("CanChangeUserData"); + + b.Property<bool>("CanCreateGroups"); + + b.Property<bool>("CanCreateRoles"); + + b.Property<bool>("CanDeleteActivatedUsers"); + + b.Property<bool>("CanDeleteGroups"); + + b.Property<bool>("CanDeleteRoles"); + + b.Property<bool>("CanDownloadPersonalDataFromOthers"); + + b.Property<bool>("CanManageNewUsers"); + + b.Property<bool>("CanManageTags"); + + b.Property<bool>("CanViewDashboard"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("SystemRoleId"); + + b.HasKey("Id"); + + b.HasIndex("SystemRoleId") + .IsUnique(); + + b.ToTable("SystemRolePermissions"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("CurrentServerMessage") + .HasMaxLength(10000); + + b.Property<int>("CustomProjectTestCompileTimeoutInMs"); + + b.Property<int>("CustomProjectTestMaxDiskSpaceInKb"); + + b.Property<int>("CustomProjectTestMemoryLimitInKb"); + + b.Property<int>("CustomProjectTestTimeoutInMs"); + + b.Property<int>("DefaultGroupCreatorGroupRoleId"); + + b.Property<int>("DefaultGroupRoleId"); + + b.Property<int>("DefaultUserGroupId"); + + b.Property<int>("JustRunProgramCompileTimeoutInMs"); + + b.Property<int>("JustRunProgramMaxDiskSpaceInKb"); + + b.Property<int>("JustRunProgramMemoryLimitInKb"); + + b.Property<int>("JustRunProgramTimeoutInMs"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxCustomProjectsPerUser"); + + b.Property<int>("MaxCustomTestsPerParticipation"); + + b.Property<int>("MaxNumberOfTestsWithOneRequest"); + + b.Property<int>("MaxNumberOfTestsWithOneRequestSubmitTestServer"); + + b.Property<int>("SubmitTestServerTimeoutInMs"); + + b.Property<string>("SubmitTestServerUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerConfigUiUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerStatsUrl") + .HasMaxLength(2000); + + b.Property<int>("TestServerTimeoutInMs"); + + b.Property<string>("TestServerTraceLogApiUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerUrl") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("DefaultGroupCreatorGroupRoleId") + .IsUnique(); + + b.HasIndex("DefaultGroupRoleId") + .IsUnique(); + + b.HasIndex("DefaultUserGroupId") + .IsUnique(); + + b.ToTable("SystemSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<string>("FirstName") + .HasMaxLength(2000); + + b.Property<bool>("IsActivated"); + + b.Property<DateTime>("LastLoginAt"); + + b.Property<string>("LastName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Password") + .HasMaxLength(2000); + + b.Property<int?>("SystemRoleId"); + + b.Property<string>("Token") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasAlternateKey("Token") + .HasName("Unique_Token"); + + b.HasIndex("SystemRoleId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserGroup", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserWithUserGroup", b => + { + b.Property<int>("UserId"); + + b.Property<int>("UserGroupId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("GroupRoleId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("GroupRoleId"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("UserWithUserGroups"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProject", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("CustomProjects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescription", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithOne("Description") + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectDescription", "CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescriptionWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectDescription", "CustomProjectDescription") + .WithMany("AssetReferences") + .HasForeignKey("CustomProjectDescriptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomProjectDescriptionWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolution", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithMany("Solutions") + .HasForeignKey("CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectSolution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolution") + .WithMany("SolutionFiles") + .HasForeignKey("CustomProjectSolutionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTest", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithMany("Tests") + .HasForeignKey("CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestSettings", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithOne("CustomProjectTestSettings") + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectTestSettings", "CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithMany("AssetReferences") + .HasForeignKey("CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomProjectTestWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolution", "CustomProjectSolution") + .WithMany("TestResults") + .HasForeignKey("CustomProjectSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithMany("TestResults") + .HasForeignKey("CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithOne("AfterSolution") + .HasForeignKey("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("SolutionFiles") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "TemplateFile") + .WithMany() + .HasForeignKey("TemplateFileId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.CustomTestWithAfterSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("CustomTestResults") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("AfterTestResults") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.TestWithAfterSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("TestResults") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("AfterTestResults") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CodeTemplate", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("CodeTemplates") + .HasForeignKey("ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.CodeTemplate", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CustomTestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("AssetReferences") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomTestWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Exercise", b => + { + b.HasOne("ClientServer.Models.Users.UserGroup", "UserGroup") + .WithMany("Exercises") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("Exercises") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescription", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("Description") + .HasForeignKey("ClientServer.Models.Exercises.ExerciseDescription", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescriptionWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Exercises.ExerciseDescription", "ExerciseDescription") + .WithMany("AssetReferences") + .HasForeignKey("ExerciseDescriptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceMarkdownAsset", "FileReferenceMarkdownAsset") + .WithMany("AssetReferences") + .HasForeignKey("FileReferenceMarkdownAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.MetaData", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("MetaData") + .HasForeignKey("ClientServer.Models.Exercises.MetaData", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Release.ExerciseRelease", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("Releases") + .HasForeignKey("ExerciseId"); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.CustomTestWithSingleSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("TestResultsNew") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("CustomTestResults") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", b => + { + b.HasOne("ClientServer.Models.Exercises.Release.ExerciseRelease", "ExerciseRelease") + .WithMany("ExerciseReleaseWithUserAsParticipations") + .HasForeignKey("ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.PLang", "LastEditedPLang") + .WithMany() + .HasForeignKey("LastEditedPLangId"); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("ExerciseReleaseWithUserAsParticipations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.Solution", b => + { + b.HasOne("ClientServer.Models.Exercises.Solution.SolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.Solution.Solution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + + b.HasOne("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", "ExerciseReleaseWithUserAsParticipation") + .WithMany("Solutions") + .HasForeignKey("UserId", "ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionAssessment", b => + { + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithOne("Assessment") + .HasForeignKey("ClientServer.Models.Exercises.Solution.SolutionAssessment", "UserId", "ExerciseReleaseId", "PLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionFile", b => + { + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "TemplateFile") + .WithMany("SolutionParts") + .HasForeignKey("TemplateFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("SolutionFiles") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.TestWithSingleSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("TestResultsNew") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("TestResults") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TagWithMetaData", b => + { + b.HasOne("ClientServer.Models.Exercises.MetaData", "MetaData") + .WithMany("TagWithMetaDatas") + .HasForeignKey("MetaDataId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TemplateFile", b => + { + b.HasOne("ClientServer.Models.Exercises.CodeTemplate") + .WithMany("TemplateFiles") + .HasForeignKey("CodeTemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.CustomTest", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation") + .WithMany("CustomTests") + .HasForeignKey("UserId", "ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("DefaultCustomTestSettings") + .HasForeignKey("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.Test", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("Tests") + .HasForeignKey("ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestSettings", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithOne("TestSettings") + .HasForeignKey("ClientServer.Models.Exercises.Tests.TestSettings", "TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Files.FileReferenceTestAsset", "FileReferenceTestAsset") + .WithMany("AssetReferences") + .HasForeignKey("FileReferenceTestAssetId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("AssetReferences") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.AuthToken", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.AuthToken", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.ExternalUser", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne("ExternalUser") + .HasForeignKey("ClientServer.Models.Users.ExternalUser", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRolePermission", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "GroupRole") + .WithOne("GroupRolePermission") + .HasForeignKey("ClientServer.Models.Users.GroupRolePermission", "GroupRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.CodeEditorSetting", b => + { + b.HasOne("ClientServer.Models.Users.Settings.UserSetting", "UserSetting") + .WithOne("CodeEditorSetting") + .HasForeignKey("ClientServer.Models.Users.Settings.CodeEditorSetting", "UserSettingId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.UserSetting", b => + { + b.HasOne("ClientServer.Models.Lang", "Lang") + .WithMany() + .HasForeignKey("LangId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne("UserSettings") + .HasForeignKey("ClientServer.Models.Users.Settings.UserSetting", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRolePermission", b => + { + b.HasOne("ClientServer.Models.Users.SystemRole", "SystemRole") + .WithOne("SystemRolePermission") + .HasForeignKey("ClientServer.Models.Users.SystemRolePermission", "SystemRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemSetting", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "DefaultGroupCreatorGroupRole") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultGroupCreatorGroupRoleId"); + + b.HasOne("ClientServer.Models.Users.GroupRole", "DefaultGroupRole") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultGroupRoleId"); + + b.HasOne("ClientServer.Models.Users.UserGroup", "DefaultUserGroup") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultUserGroupId"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.User", b => + { + b.HasOne("ClientServer.Models.Users.SystemRole", "SystemRole") + .WithMany() + .HasForeignKey("SystemRoleId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserWithUserGroup", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "GroupRole") + .WithMany("UserUserGroups") + .HasForeignKey("GroupRoleId"); + + b.HasOne("ClientServer.Models.Users.UserGroup", "UserGroup") + .WithMany("UserWithUserGroups") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("UserWithUserGroups") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.cs b/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.cs new file mode 100755 index 0000000000000000000000000000000000000000..3870ce22e0aac56fcb81ddb5f000d76365ed665a --- /dev/null +++ b/src/ClientServer/Migrations/20191117200002_TestServerTraceLogs.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ClientServer.Migrations +{ + public partial class TestServerTraceLogs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<string>( + name: "TestServerTraceLogApiUrl", + table: "SystemSettings", + maxLength: 2000, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "TestServerTraceLogApiUrl", + table: "SystemSettings"); + } + } +} diff --git a/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.Designer.cs b/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.Designer.cs new file mode 100755 index 0000000000000000000000000000000000000000..23d3ac7a7d13d4252e7afdc229c4b2de49b88afd --- /dev/null +++ b/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.Designer.cs @@ -0,0 +1,2173 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using ClientServer.Db; +using ClientServer.Models.Exercises.Release; + +namespace ClientServer.Migrations +{ + [DbContext(typeof(YapexDbContext))] + [Migration("20191117212447_SubmitTestServerStats")] + partial class SubmitTestServerStats + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.3"); + + modelBuilder.Entity("ClientServer.Models.AwaitDummy", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("AwaitDummies"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProject", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("LastEditorPLangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("CustomProjects"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescription", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(50000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId") + .IsUnique(); + + b.ToTable("CustomProjectDescriptions"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescriptionWithFileAsAssetReference", b => + { + b.Property<int>("CustomProjectDescriptionId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomProjectDescriptionId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomProjectDescriptionId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomProjectDescriptionWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolution", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("PLangId"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.ToTable("CustomProjectSolutions"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int?>("CustomProjectSolutionId") + .IsRequired(); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectSolutionId"); + + b.ToTable("CustomProjectSolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTest", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectId"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("Weight"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectId"); + + b.HasIndex("TestTypeId"); + + b.ToTable("CustomProjectTests"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<byte[]>("Content"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.ToTable("CustomProjectTestAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("CustomProjectTestId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("CustomProjectTestId") + .IsUnique(); + + b.ToTable("CustomProjectTestSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithFileAsAssetReference", b => + { + b.Property<int>("CustomProjectTestId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomProjectTestId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomProjectTestId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomProjectTestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithSolutionAsTestResult", b => + { + b.Property<int>("CustomProjectSolutionId"); + + b.Property<int>("CustomProjectTestId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomProjectSolutionId", "CustomProjectTestId"); + + b.HasIndex("CustomProjectSolutionId"); + + b.HasIndex("CustomProjectTestId"); + + b.ToTable("CustomProjectTestWithSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<int>("SolutionUserId"); + + b.HasKey("Id"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .IsUnique(); + + b.ToTable("AfterSolutions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("AfterSolutionId"); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("TemplateFileId"); + + b.HasKey("Id"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("TemplateFileId"); + + b.ToTable("AfterSolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.CustomTestWithAfterSolutionAsTestResult", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("AfterSolutionId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomTestId", "AfterSolutionId"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("CustomTestId"); + + b.ToTable("CustomTestWithAfterSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.TestWithAfterSolutionAsTestResult", b => + { + b.Property<int>("TestId"); + + b.Property<int>("AfterSolutionId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("TestId", "AfterSolutionId"); + + b.HasIndex("AfterSolutionId"); + + b.HasIndex("TestId"); + + b.ToTable("TestWithAfterSolutionAsTestResults"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CodeTemplate", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<int>("PLangId"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.ToTable("Templates"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CustomTestWithFileAsAssetReference", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("FileReferenceUserFileAssetId"); + + b.HasKey("CustomTestId", "FileReferenceUserFileAssetId"); + + b.HasIndex("CustomTestId"); + + b.HasIndex("FileReferenceUserFileAssetId"); + + b.ToTable("CustomTestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Exercise", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanUserCreateCustomTests"); + + b.Property<bool>("CanUserCreateFiles"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<bool>("IsOnlyVisibleToOwner"); + + b.Property<bool>("IsPermanentlyLocked"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.Property<string>("ShortDescription") + .HasMaxLength(2000); + + b.Property<int>("UserGroupId"); + + b.Property<int?>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("Exercises"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescription", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(50000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("ExerciseDescriptions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescriptionWithFileAsAssetReference", b => + { + b.Property<int>("ExerciseDescriptionId"); + + b.Property<int>("FileReferenceMarkdownAssetId"); + + b.HasKey("ExerciseDescriptionId", "FileReferenceMarkdownAssetId"); + + b.HasIndex("ExerciseDescriptionId"); + + b.HasIndex("FileReferenceMarkdownAssetId"); + + b.ToTable("ExerciseDescriptionWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.MetaData", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("MetaDatas"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Release.ExerciseRelease", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime?>("AutomaticEndAt"); + + b.Property<DateTime?>("AutomaticStartAt"); + + b.Property<int>("AvailableWorkingTimeInMinutes"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<string>("GeneratedCode") + .IsRequired() + .HasMaxLength(2000); + + b.Property<bool>("HadAutomaticAssessmentErrors"); + + b.Property<bool>("HasAutomaticAssessmentFinished"); + + b.Property<bool>("HasAutomaticAssessmentStarted"); + + b.Property<bool>("HasLimitedWorkingTime"); + + b.Property<bool>("HideExerciseLeaveActions"); + + b.Property<bool>("HideInOverviews"); + + b.Property<bool>("HidePrintOptions"); + + b.Property<bool>("HideSiteHeaderBar"); + + b.Property<bool>("IsReleased"); + + b.Property<bool>("IsVisibleToAllAfterRelease"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MaxManualPoint"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.Property<int>("PLangId"); + + b.Property<int>("ReleaseDurationType"); + + b.Property<int>("ReleaseStartType"); + + b.Property<DateTime?>("ReleasedAt"); + + b.Property<bool>("RunAlsoNormalTests"); + + b.Property<bool>("ShouldAutomaticAssessSubmissions"); + + b.Property<bool>("ShouldClearClipboard"); + + b.Property<bool>("ShowAdditionalLogButton"); + + b.HasKey("Id"); + + b.HasAlternateKey("GeneratedCode") + .HasName("Unique_GeneratedCode"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("PLangId"); + + b.ToTable("ExerciseReleases"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.CustomTestWithSingleSolutionAsTestResult", b => + { + b.Property<int>("CustomTestId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("CustomTestId", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.HasIndex("CustomTestId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("CustomTestWithSingleSolutionAsTestResult"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", b => + { + b.Property<int>("UserId"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("LastEditedPLangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("LockSolutionsFlag"); + + b.Property<bool>("ShouldNotCount"); + + b.HasKey("UserId", "ExerciseReleaseId"); + + b.HasIndex("ExerciseReleaseId"); + + b.HasIndex("LastEditedPLangId"); + + b.HasIndex("UserId"); + + b.ToTable("ExerciseReleaseWithUserAsParticipations"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.Solution", b => + { + b.Property<int>("UserId"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<int>("PLangId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("LastEditingIpAddress") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("MainFileId"); + + b.Property<string>("Note") + .HasMaxLength(10000); + + b.HasKey("UserId", "ExerciseReleaseId", "PLangId"); + + b.HasIndex("MainFileId") + .IsUnique(); + + b.HasIndex("PLangId"); + + b.HasIndex("UserId", "ExerciseReleaseId"); + + b.ToTable("Solutions"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionAssessment", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<string>("FeedbackForStudent") + .HasMaxLength(10000); + + b.Property<string>("LastAssessmentErrorMessage") + .HasMaxLength(80000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int?>("ManualPoints"); + + b.Property<int>("MaxNormalTestPoints"); + + b.Property<int>("MaxSubmitTestPoints"); + + b.Property<int?>("NormalTestPoints"); + + b.Property<string>("NoteForOtherTutors") + .HasMaxLength(10000); + + b.Property<int>("PLangId"); + + b.Property<int?>("SubmitTestPoints"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "ExerciseReleaseId", "PLangId") + .IsUnique(); + + b.ToTable("SolutionAssessment"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsDisplayed"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int?>("TemplateFileId"); + + b.HasKey("Id"); + + b.HasIndex("TemplateFileId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("SolutionFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.TestWithSingleSolutionAsTestResult", b => + { + b.Property<int>("TestId"); + + b.Property<int>("SolutionUserId"); + + b.Property<int>("SolutionExerciseReleaseId"); + + b.Property<int>("SolutionPLangId"); + + b.Property<bool>("CharacterLimitExceeded"); + + b.Property<int?>("CharacterLimitUsed"); + + b.Property<int?>("CompileTimeoutInMsUsed"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<bool?>("HasCompiled"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool?>("Passed"); + + b.Property<int?>("ProgramExitCode"); + + b.Property<string>("Protocol") + .HasMaxLength(160100); + + b.Property<string>("RunnerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TestResultCode"); + + b.Property<string>("TestServerVersion") + .HasMaxLength(2000); + + b.Property<int?>("TimeForCompiling"); + + b.Property<int?>("TimeForUserProgram"); + + b.Property<int?>("TimeoutInMsUsed"); + + b.HasKey("TestId", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.HasIndex("TestId"); + + b.HasIndex("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId"); + + b.ToTable("TestWithSingleSolutionAsTestResult"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tag", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Description") + .HasMaxLength(2000); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("HtmlBackgroundColor") + .HasMaxLength(2000); + + b.Property<string>("HtmlColor") + .HasMaxLength(2000); + + b.Property<string>("HtmlIcon") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("DisplayName") + .HasName("Unique_Tag"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TagWithMetaData", b => + { + b.Property<int>("TagId"); + + b.Property<int>("MetaDataId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("TagId", "MetaDataId"); + + b.HasIndex("MetaDataId"); + + b.HasIndex("TagId"); + + b.ToTable("TagWithMetaDatas"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TemplateFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int?>("CodeTemplateId") + .IsRequired(); + + b.Property<string>("Content") + .HasMaxLength(70000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("FileNameWithExtension") + .HasMaxLength(2000); + + b.Property<bool>("IsContentVisibleForUser"); + + b.Property<bool>("IsEditableByUser"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("CodeTemplateId"); + + b.ToTable("TemplateFiles"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.CustomTest", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("ExerciseReleaseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("TestTypeId"); + + b.HasIndex("UserId", "ExerciseReleaseId"); + + b.ToTable("CustomTest"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("ExerciseId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId") + .IsUnique(); + + b.ToTable("DefaultCustomTestSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.Test", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<string>("Content") + .HasMaxLength(80000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("DisplayIndex"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<int>("ExerciseId"); + + b.Property<bool>("IsSubmitTest"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("TestTypeId"); + + b.Property<int>("Weight"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseId"); + + b.HasIndex("TestTypeId"); + + b.ToTable("Tests"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestSettings", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<int>("CompileTimeoutInMs"); + + b.Property<string>("CompilerOptions") + .HasMaxLength(2000); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxDiskSpaceInKb"); + + b.Property<int>("MemoryLimitInKb"); + + b.Property<int>("TestId"); + + b.Property<int>("TimeoutInMs"); + + b.HasKey("Id"); + + b.HasIndex("TestId") + .IsUnique(); + + b.ToTable("TestCaseSettingses"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestType", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("InternalName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("TestTypes"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TestWithFileAsAssetReference", b => + { + b.Property<int>("TestId"); + + b.Property<int>("FileReferenceTestAssetId"); + + b.HasKey("TestId", "FileReferenceTestAssetId"); + + b.HasIndex("FileReferenceTestAssetId"); + + b.HasIndex("TestId"); + + b.ToTable("TestWithFileAsAssetReferences"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceMarkdownAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceMarkdownAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceTestAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceTestAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Files.FileReferenceUserFileAsset", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Hash") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("MimeType") + .HasMaxLength(2000); + + b.Property<string>("OriginalName") + .HasMaxLength(2000); + + b.Property<long>("SizeInBytes"); + + b.HasKey("Id"); + + b.ToTable("FileReferenceUserFileAssets"); + }); + + modelBuilder.Entity("ClientServer.Models.Lang", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("LangShortcut") + .HasMaxLength(2000); + + b.Property<string>("Language") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("Langs"); + }); + + modelBuilder.Entity("ClientServer.Models.PLang", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("EditorHighlightModeName") + .HasMaxLength(2000); + + b.Property<string>("FileExtensionsWithDot") + .HasMaxLength(2000); + + b.Property<string>("InternalName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("PLangs"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.AuthToken", b => + { + b.Property<int>("UserId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("CsrfToken") + .HasMaxLength(2000); + + b.Property<DateTime>("ExpirationDateTime"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("RandomSecret") + .HasMaxLength(2000); + + b.Property<string>("UserAuthToken") + .HasMaxLength(2000); + + b.HasKey("UserId"); + + b.HasIndex("UserAuthToken") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AuthTokens"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.ExternalUser", b => + { + b.Property<int>("ExternalId") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<string>("FirstName") + .HasMaxLength(2000); + + b.Property<string>("LastName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("NeedToRefreshData"); + + b.Property<string>("Token") + .IsRequired() + .HasMaxLength(2000); + + b.Property<int>("UserId"); + + b.HasKey("ExternalId"); + + b.HasAlternateKey("Token") + .HasName("Unique_ExternalToken"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ExternalUsers"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRole", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("GroupRoles"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRolePermission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanAddUserToGroup"); + + b.Property<bool>("CanAssessExercises"); + + b.Property<bool>("CanChangeExercises"); + + b.Property<bool>("CanChangeGroupData"); + + b.Property<bool>("CanChangeOtherMembersRole"); + + b.Property<bool>("CanCreateExercises"); + + b.Property<bool>("CanDeleteExercises"); + + b.Property<bool>("CanLockExercisesPermanently"); + + b.Property<bool>("CanManageExerciseReleases"); + + b.Property<bool>("CanRemoveMemberFromGroup"); + + b.Property<bool>("CanSeeExercisesFromOthersInGroup"); + + b.Property<bool>("CanSeeOtherMembers"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("GroupRoleId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.HasIndex("GroupRoleId") + .IsUnique(); + + b.ToTable("GroupRolePermissions"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.CodeEditorSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("FontSize"); + + b.Property<bool>("HighlightCurrentLine"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<bool>("ShowInvisibles"); + + b.Property<bool>("ShowLineIndentions"); + + b.Property<bool>("ShowLineNumbers"); + + b.Property<int>("TabSize"); + + b.Property<string>("Theme") + .HasMaxLength(2000); + + b.Property<bool>("UseWrapping"); + + b.Property<int>("UserSettingId"); + + b.HasKey("Id"); + + b.HasIndex("UserSettingId") + .IsUnique(); + + b.ToTable("CodeEditorSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.UserSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int?>("LangId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Theme") + .HasMaxLength(2000); + + b.Property<int?>("UserId"); + + b.HasKey("Id"); + + b.HasIndex("LangId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserSettingses"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRole", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("SystemRoles"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRolePermission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<bool>("CanChangeOtherUsersSystemRole"); + + b.Property<bool>("CanChangeRoles"); + + b.Property<bool>("CanChangeSystemSettings"); + + b.Property<bool>("CanChangeUserData"); + + b.Property<bool>("CanCreateGroups"); + + b.Property<bool>("CanCreateRoles"); + + b.Property<bool>("CanDeleteActivatedUsers"); + + b.Property<bool>("CanDeleteGroups"); + + b.Property<bool>("CanDeleteRoles"); + + b.Property<bool>("CanDownloadPersonalDataFromOthers"); + + b.Property<bool>("CanManageNewUsers"); + + b.Property<bool>("CanManageTags"); + + b.Property<bool>("CanViewDashboard"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("SystemRoleId"); + + b.HasKey("Id"); + + b.HasIndex("SystemRoleId") + .IsUnique(); + + b.ToTable("SystemRolePermissions"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemSetting", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("CurrentServerMessage") + .HasMaxLength(10000); + + b.Property<int>("CustomProjectTestCompileTimeoutInMs"); + + b.Property<int>("CustomProjectTestMaxDiskSpaceInKb"); + + b.Property<int>("CustomProjectTestMemoryLimitInKb"); + + b.Property<int>("CustomProjectTestTimeoutInMs"); + + b.Property<int>("DefaultGroupCreatorGroupRoleId"); + + b.Property<int>("DefaultGroupRoleId"); + + b.Property<int>("DefaultUserGroupId"); + + b.Property<int>("JustRunProgramCompileTimeoutInMs"); + + b.Property<int>("JustRunProgramMaxDiskSpaceInKb"); + + b.Property<int>("JustRunProgramMemoryLimitInKb"); + + b.Property<int>("JustRunProgramTimeoutInMs"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<int>("MaxCustomProjectsPerUser"); + + b.Property<int>("MaxCustomTestsPerParticipation"); + + b.Property<int>("MaxNumberOfTestsWithOneRequest"); + + b.Property<int>("MaxNumberOfTestsWithOneRequestSubmitTestServer"); + + b.Property<string>("SubmitTestServerConfigUiUrl") + .HasMaxLength(2000); + + b.Property<string>("SubmitTestServerStatsUrl") + .HasMaxLength(2000); + + b.Property<int>("SubmitTestServerTimeoutInMs"); + + b.Property<string>("SubmitTestServerUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerConfigUiUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerStatsUrl") + .HasMaxLength(2000); + + b.Property<int>("TestServerTimeoutInMs"); + + b.Property<string>("TestServerTraceLogApiUrl") + .HasMaxLength(2000); + + b.Property<string>("TestServerUrl") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("DefaultGroupCreatorGroupRoleId") + .IsUnique(); + + b.HasIndex("DefaultGroupRoleId") + .IsUnique(); + + b.HasIndex("DefaultUserGroupId") + .IsUnique(); + + b.ToTable("SystemSettings"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.User", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<string>("FirstName") + .HasMaxLength(2000); + + b.Property<bool>("IsActivated"); + + b.Property<DateTime>("LastLoginAt"); + + b.Property<string>("LastName") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.Property<string>("Password") + .HasMaxLength(2000); + + b.Property<int?>("SystemRoleId"); + + b.Property<string>("Token") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasAlternateKey("Token") + .HasName("Unique_Token"); + + b.HasIndex("SystemRoleId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserGroup", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd(); + + b.Property<DateTime>("CreatedAt"); + + b.Property<string>("DisplayName") + .HasMaxLength(2000); + + b.Property<string>("Email") + .HasMaxLength(2000); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("UserGroups"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserWithUserGroup", b => + { + b.Property<int>("UserId"); + + b.Property<int>("UserGroupId"); + + b.Property<DateTime>("CreatedAt"); + + b.Property<int>("GroupRoleId"); + + b.Property<DateTime>("LastUpdatedAt"); + + b.HasKey("UserId", "UserGroupId"); + + b.HasIndex("GroupRoleId"); + + b.HasIndex("UserGroupId"); + + b.HasIndex("UserId"); + + b.ToTable("UserWithUserGroups"); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProject", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("CustomProjects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescription", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithOne("Description") + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectDescription", "CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectDescriptionWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectDescription", "CustomProjectDescription") + .WithMany("AssetReferences") + .HasForeignKey("CustomProjectDescriptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomProjectDescriptionWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolution", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithMany("Solutions") + .HasForeignKey("CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectSolution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectSolutionFile", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolution") + .WithMany("SolutionFiles") + .HasForeignKey("CustomProjectSolutionId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTest", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProject", "CustomProject") + .WithMany("Tests") + .HasForeignKey("CustomProjectId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestSettings", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithOne("CustomProjectTestSettings") + .HasForeignKey("ClientServer.Models.CustomProjects.CustomProjectTestSettings", "CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithMany("AssetReferences") + .HasForeignKey("CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomProjectTestWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.CustomProjects.CustomProjectTestWithSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectSolution", "CustomProjectSolution") + .WithMany("TestResults") + .HasForeignKey("CustomProjectSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.CustomProjects.CustomProjectTest", "CustomProjectTest") + .WithMany("TestResults") + .HasForeignKey("CustomProjectTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithOne("AfterSolution") + .HasForeignKey("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.AfterSolutionFile", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("SolutionFiles") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "TemplateFile") + .WithMany() + .HasForeignKey("TemplateFileId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.CustomTestWithAfterSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("CustomTestResults") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("AfterTestResults") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.AfterSolutions.TestWithAfterSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.AfterSolutions.AfterSolution", "AfterSolution") + .WithMany("TestResults") + .HasForeignKey("AfterSolutionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("AfterTestResults") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CodeTemplate", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("CodeTemplates") + .HasForeignKey("ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.CodeTemplate", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.CustomTestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("AssetReferences") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceUserFileAsset", "FileReferenceUserFileAsset") + .WithMany("CustomTestWithFileAsAssetReferences") + .HasForeignKey("FileReferenceUserFileAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Exercise", b => + { + b.HasOne("ClientServer.Models.Users.UserGroup", "UserGroup") + .WithMany("Exercises") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("Exercises") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescription", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("Description") + .HasForeignKey("ClientServer.Models.Exercises.ExerciseDescription", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.ExerciseDescriptionWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Exercises.ExerciseDescription", "ExerciseDescription") + .WithMany("AssetReferences") + .HasForeignKey("ExerciseDescriptionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Files.FileReferenceMarkdownAsset", "FileReferenceMarkdownAsset") + .WithMany("AssetReferences") + .HasForeignKey("FileReferenceMarkdownAssetId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.MetaData", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("MetaData") + .HasForeignKey("ClientServer.Models.Exercises.MetaData", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Release.ExerciseRelease", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("Releases") + .HasForeignKey("ExerciseId"); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.CustomTestWithSingleSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.CustomTest", "CustomTest") + .WithMany("TestResultsNew") + .HasForeignKey("CustomTestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("CustomTestResults") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", b => + { + b.HasOne("ClientServer.Models.Exercises.Release.ExerciseRelease", "ExerciseRelease") + .WithMany("ExerciseReleaseWithUserAsParticipations") + .HasForeignKey("ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.PLang", "LastEditedPLang") + .WithMany() + .HasForeignKey("LastEditedPLangId"); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("ExerciseReleaseWithUserAsParticipations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.Solution", b => + { + b.HasOne("ClientServer.Models.Exercises.Solution.SolutionFile", "MainFile") + .WithOne() + .HasForeignKey("ClientServer.Models.Exercises.Solution.Solution", "MainFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.PLang", "PLang") + .WithMany() + .HasForeignKey("PLangId"); + + b.HasOne("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation", "ExerciseReleaseWithUserAsParticipation") + .WithMany("Solutions") + .HasForeignKey("UserId", "ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionAssessment", b => + { + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithOne("Assessment") + .HasForeignKey("ClientServer.Models.Exercises.Solution.SolutionAssessment", "UserId", "ExerciseReleaseId", "PLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.SolutionFile", b => + { + b.HasOne("ClientServer.Models.Exercises.TemplateFile", "TemplateFile") + .WithMany("SolutionParts") + .HasForeignKey("TemplateFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("SolutionFiles") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Solution.TestWithSingleSolutionAsTestResult", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("TestResultsNew") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.Solution", "Solution") + .WithMany("TestResults") + .HasForeignKey("SolutionUserId", "SolutionExerciseReleaseId", "SolutionPLangId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TagWithMetaData", b => + { + b.HasOne("ClientServer.Models.Exercises.MetaData", "MetaData") + .WithMany("TagWithMetaDatas") + .HasForeignKey("MetaDataId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TemplateFile", b => + { + b.HasOne("ClientServer.Models.Exercises.CodeTemplate") + .WithMany("TemplateFiles") + .HasForeignKey("CodeTemplateId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.CustomTest", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Solution.ExerciseReleaseWithUserAsParticipation") + .WithMany("CustomTests") + .HasForeignKey("UserId", "ExerciseReleaseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithOne("DefaultCustomTestSettings") + .HasForeignKey("ClientServer.Models.Exercises.Tests.DefaultCustomTestSettings", "ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.Test", b => + { + b.HasOne("ClientServer.Models.Exercises.Exercise", "Exercise") + .WithMany("Tests") + .HasForeignKey("ExerciseId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.TestType", "TestType") + .WithMany() + .HasForeignKey("TestTypeId"); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.Tests.TestSettings", b => + { + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithOne("TestSettings") + .HasForeignKey("ClientServer.Models.Exercises.Tests.TestSettings", "TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Exercises.TestWithFileAsAssetReference", b => + { + b.HasOne("ClientServer.Models.Files.FileReferenceTestAsset", "FileReferenceTestAsset") + .WithMany("AssetReferences") + .HasForeignKey("FileReferenceTestAssetId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Exercises.Tests.Test", "Test") + .WithMany("AssetReferences") + .HasForeignKey("TestId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.AuthToken", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.AuthToken", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.ExternalUser", b => + { + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne("ExternalUser") + .HasForeignKey("ClientServer.Models.Users.ExternalUser", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.GroupRolePermission", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "GroupRole") + .WithOne("GroupRolePermission") + .HasForeignKey("ClientServer.Models.Users.GroupRolePermission", "GroupRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.CodeEditorSetting", b => + { + b.HasOne("ClientServer.Models.Users.Settings.UserSetting", "UserSetting") + .WithOne("CodeEditorSetting") + .HasForeignKey("ClientServer.Models.Users.Settings.CodeEditorSetting", "UserSettingId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.Settings.UserSetting", b => + { + b.HasOne("ClientServer.Models.Lang", "Lang") + .WithMany() + .HasForeignKey("LangId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithOne("UserSettings") + .HasForeignKey("ClientServer.Models.Users.Settings.UserSetting", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemRolePermission", b => + { + b.HasOne("ClientServer.Models.Users.SystemRole", "SystemRole") + .WithOne("SystemRolePermission") + .HasForeignKey("ClientServer.Models.Users.SystemRolePermission", "SystemRoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("ClientServer.Models.Users.SystemSetting", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "DefaultGroupCreatorGroupRole") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultGroupCreatorGroupRoleId"); + + b.HasOne("ClientServer.Models.Users.GroupRole", "DefaultGroupRole") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultGroupRoleId"); + + b.HasOne("ClientServer.Models.Users.UserGroup", "DefaultUserGroup") + .WithOne() + .HasForeignKey("ClientServer.Models.Users.SystemSetting", "DefaultUserGroupId"); + }); + + modelBuilder.Entity("ClientServer.Models.Users.User", b => + { + b.HasOne("ClientServer.Models.Users.SystemRole", "SystemRole") + .WithMany() + .HasForeignKey("SystemRoleId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("ClientServer.Models.Users.UserWithUserGroup", b => + { + b.HasOne("ClientServer.Models.Users.GroupRole", "GroupRole") + .WithMany("UserUserGroups") + .HasForeignKey("GroupRoleId"); + + b.HasOne("ClientServer.Models.Users.UserGroup", "UserGroup") + .WithMany("UserWithUserGroups") + .HasForeignKey("UserGroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("ClientServer.Models.Users.User", "User") + .WithMany("UserWithUserGroups") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.cs b/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.cs new file mode 100755 index 0000000000000000000000000000000000000000..dc86f423a3397e84ddb02a60fade4b99c5610d08 --- /dev/null +++ b/src/ClientServer/Migrations/20191117212447_SubmitTestServerStats.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ClientServer.Migrations +{ + public partial class SubmitTestServerStats : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<string>( + name: "SubmitTestServerConfigUiUrl", + table: "SystemSettings", + maxLength: 2000, + nullable: true); + + migrationBuilder.AddColumn<string>( + name: "SubmitTestServerStatsUrl", + table: "SystemSettings", + maxLength: 2000, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SubmitTestServerConfigUiUrl", + table: "SystemSettings"); + + migrationBuilder.DropColumn( + name: "SubmitTestServerStatsUrl", + table: "SystemSettings"); + } + } +} diff --git a/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs b/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs index 8b3d9b7c9e972f731a00fd99322893af87f14ebb..e69dd37cd59428f13cdd344edb5b71fa5e6891de 100755 --- a/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs +++ b/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs @@ -1560,6 +1560,12 @@ namespace ClientServer.Migrations b.Property<int>("MaxNumberOfTestsWithOneRequestSubmitTestServer"); + b.Property<string>("SubmitTestServerConfigUiUrl") + .HasMaxLength(2000); + + b.Property<string>("SubmitTestServerStatsUrl") + .HasMaxLength(2000); + b.Property<int>("SubmitTestServerTimeoutInMs"); b.Property<string>("SubmitTestServerUrl") @@ -1573,6 +1579,9 @@ namespace ClientServer.Migrations b.Property<int>("TestServerTimeoutInMs"); + b.Property<string>("TestServerTraceLogApiUrl") + .HasMaxLength(2000); + b.Property<string>("TestServerUrl") .HasMaxLength(2000); diff --git a/src/ClientServer/Models/Users/SystemSetting.cs b/src/ClientServer/Models/Users/SystemSetting.cs index 1998d68fddb1fcad5de93383a085c4b3957ccedb..24227f5ee0e5df0e4e7c2b96060c39385c03e4c5 100644 --- a/src/ClientServer/Models/Users/SystemSetting.cs +++ b/src/ClientServer/Models/Users/SystemSetting.cs @@ -73,10 +73,24 @@ namespace ClientServer.Models.Users /// we then append <see cref="Constants.TestServerPublicStatsUrlPart"/> or <see cref="Constants.TestServerPrivateStatsUrlPart"/> /// via <see cref="UrlHelper.GetTestServerStatsUrl"/> /// - /// NOT NOW we only use on test server but we might to add a SubmitTestServerStatsUrl at some point... /// </summary> [MaxLength(YapexDbContext.DefaultMaxStringLength)] public string TestServerStatsUrl { get; set; } + + /// <summary> + /// the test server stats url ending with / or without (so this is just a base path) + /// we then append <see cref="Constants.TestServerPublicStatsUrlPart"/> or <see cref="Constants.TestServerPrivateStatsUrlPart"/> + /// via <see cref="UrlHelper.GetTestServerStatsUrl"/> + /// + /// </summary> + [MaxLength(YapexDbContext.DefaultMaxStringLength)] + public string SubmitTestServerStatsUrl { get; set; } + + /// <summary> + /// the test server trace log api url + /// </summary> + [MaxLength(YapexDbContext.DefaultMaxStringLength)] + public string TestServerTraceLogApiUrl { get; set; } /// <summary> @@ -97,6 +111,12 @@ namespace ClientServer.Models.Users /// </summary> [MaxLength(YapexDbContext.DefaultMaxStringLength)] public string TestServerConfigUiUrl { get; set; } + + /// <summary> + /// the url to the ui to configure the submit test server + /// </summary> + [MaxLength(YapexDbContext.DefaultMaxStringLength)] + public string SubmitTestServerConfigUiUrl { get; set; } /// <summary> /// the timeout in ms to wait for a response from the test server before returning a testserver connection error diff --git a/src/ClientServer/Program.cs b/src/ClientServer/Program.cs index 497b12e3451a42e71bfa47977cec97bd61fcc7fd..b7a1594106c1ddbb4b694800f8437c02ad21b3e0 100644 --- a/src/ClientServer/Program.cs +++ b/src/ClientServer/Program.cs @@ -47,6 +47,9 @@ namespace ClientServer //delete all files that are (still) in the file system but not in the db FileWorker.NextRun(); //this is sync (because fast) FileWorker.Start(); + + TestServerTraceLogWorker.NextRun(); //this is sync + TestServerTraceLogWorker.Start(); } host.Run(); diff --git a/src/ClientServer/Workers/SubmissionAssessmentWorker.cs b/src/ClientServer/Workers/SubmissionAssessmentWorker.cs index a31984c2fcfb1dd91ca4c78b96f35301c7da5976..c720755ff76f9b9112ca1598dc0e14f477d52b82 100644 --- a/src/ClientServer/Workers/SubmissionAssessmentWorker.cs +++ b/src/ClientServer/Workers/SubmissionAssessmentWorker.cs @@ -177,14 +177,14 @@ namespace ClientServer.Schedulers //Console.WriteLine("Assessment scheduler run # " + runNumber + " running at: " + now.ToLocalTime()); } - var optionsBuilder = new DbContextOptionsBuilder<YapexDbContext>(); - optionsBuilder.UseNpgsql(AppConfiguration.DbConnectionString); - int assessedReleases = 0; try { + var optionsBuilder = new DbContextOptionsBuilder<YapexDbContext>(); + optionsBuilder.UseNpgsql(AppConfiguration.DbConnectionString); + using (var context = new YapexDbContext(optionsBuilder.Options)) { var systemSettings = context.SystemSettings.FirstOrDefault(); @@ -192,7 +192,7 @@ namespace ClientServer.Schedulers if (systemSettings == null) { Console.WriteLine( - "ERROR system settings not found, cannot run submissions scheduler.NextRun"); + "ERROR system settings not found, cannot run submissions worker.NextRun"); return; } @@ -251,7 +251,7 @@ namespace ClientServer.Schedulers } catch (Exception ex) { - Console.WriteLine("ERROR: " + ex); + Console.WriteLine("ERROR submission assessment worker NextRun: " + ex); } if (AppConfiguration.IsDebugMode && assessedReleases > 0) @@ -985,7 +985,9 @@ namespace ClientServer.Schedulers testParts, solution.PLang, systemSettings.SubmitTestServerUrl, true, runNumber, - systemSettings); //we assess so always use the submit test server + systemSettings, + $"SubmissionAssessmentWorker_normalTests_{TraceStringHelper.GetTraceStringPartSolution(solution)}_normalTests:{TraceStringHelper.GetTraceStringPartFromTests(testParts)}" + ); //we assess so always use the submit test server } catch (Exception ex) { @@ -1132,7 +1134,9 @@ namespace ClientServer.Schedulers submitAnswersFromTestServer = await testingController._RunTestOnTestServer(solution, testParts, solution.PLang, - systemSettings.SubmitTestServerUrl, true, runNumber, systemSettings); + systemSettings.SubmitTestServerUrl, true, runNumber, systemSettings, + $"SubmissionAssessmentWorker_submitTests_{TraceStringHelper.GetTraceStringPartSolution(solution)}_submitTests:{TraceStringHelper.GetTraceStringPartFromTests(testParts)}" + ); } catch (Exception ex) { diff --git a/src/ClientServer/Workers/TestServerTraceLogWorker.cs b/src/ClientServer/Workers/TestServerTraceLogWorker.cs new file mode 100644 index 0000000000000000000000000000000000000000..b0094f109cee2561a84f690b71e0aa4a8f5bd746 --- /dev/null +++ b/src/ClientServer/Workers/TestServerTraceLogWorker.cs @@ -0,0 +1,148 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Reactive.Linq; +using System.Threading; +using System.Threading.Tasks; +using ClientServer.Controllers.Core.Exercises; +using ClientServer.Controllers.Core.Testing; +using ClientServer.Db; +using ClientServer.Helpers; +using ClientServer.Models.Users; +using Microsoft.EntityFrameworkCore; + +namespace ClientServer.Workers +{ + /// <summary> + /// a worker to request deletion of old trace logs on the test server + /// </summary> + public static class TestServerTraceLogWorker + { + + private static bool _isRunning = false; + private static readonly Object Locker = new object(); + + + public static void Start() + { + Observable + .Interval(TimeSpan.FromMinutes(AppConfiguration.TestServerTraceLogWorkerIntervalInMinutes)) + .Subscribe((min) => { NextRun(); }); + } + + public static void NextRun() + { + + if (_isRunning) + { + return; + } + + lock (Locker) + { + _isRunning = true; + + try + { + + var optionsBuilder = new DbContextOptionsBuilder<YapexDbContext>(); + optionsBuilder.UseNpgsql(AppConfiguration.DbConnectionString); + + SystemSetting systemSettings = null; + + using (var context = new YapexDbContext(optionsBuilder.Options)) + { + systemSettings = context.SystemSettings.FirstOrDefault(); + + if (systemSettings == null) + { + Console.WriteLine( + "ERROR system settings not found, cannot run trace log worker.NextRun"); + return; + } + } + + + if (string.IsNullOrEmpty(systemSettings.TestServerTraceLogApiUrl)) + { + Console.WriteLine("WARNING trace log worker url was empty!"); + return; + } + + var reqObj = new TestServerRquestDelteOldTraceLogs() + { + DeleteTraceLogOlderThanMinutes = AppConfiguration.TestServerTraceLogDeleteTraceLogOlderThanMinutes + }; + + ResponseFromTestServer answerFromTestServer = null; + + var requestPayload = new StringContent(Jc.SerializePlain(reqObj), SubmissionController.UTF8WithoutBom, + "application/json"); + + + HttpResponseMessage jsonResponse = null; + var tokenSource = new CancellationTokenSource(); + + using (var client = new HttpClient()) + { + client.Timeout = new TimeSpan(0,0,0,0,AppConfiguration.TestServerTraceLogDeleteTraceLogTimeoutInMs); + tokenSource.CancelAfter(new TimeSpan(0,0,0,0,AppConfiguration.TestServerTraceLogDeleteTraceLogTimeoutInMs)); + + try + { + jsonResponse = client.PostAsync(systemSettings.TestServerTraceLogApiUrl, requestPayload, tokenSource.Token).Result; + } + catch (Exception ex) + { + if (ex is TaskCanceledException) + { + //timeout hit... + Console.WriteLine("ERROR timeout deleting trace logs on test server"); + } + else + { + Console.WriteLine("ERROR deleting trace logs on test server: " + ex); + } + } + } + + string contentResult = null; + + if (jsonResponse != null) + contentResult = jsonResponse.Content.ReadAsStringAsync().Result; + + tokenSource.Dispose(); + + try + { + answerFromTestServer = Jc.Deserialize<ResponseFromTestServer>(contentResult); + + if (answerFromTestServer != null) + { + if (answerFromTestServer.ResponseCode != (int) TestServerResponseCode.Ok && AppConfiguration.IsDebugMode) + { + Console.WriteLine("[WARNING] not ok response code from test server (trace log worker), content: " + contentResult); + } + } + } + catch (Exception ex) + { + Console.WriteLine("ERROR trace log worker: deserializing result, message: " + ex); + } + } + catch (Exception ex) + { + Console.WriteLine("ERROR trace log worker NextRun: " + ex); + } + + _isRunning = false; + } + + } + } + + class TestServerRquestDelteOldTraceLogs + { + public int DeleteTraceLogOlderThanMinutes { get; set; } + } +} diff --git a/src/ClientServer/appsettings_example.json b/src/ClientServer/appsettings_example.json index 003c88244a9a046309cbec4b99fe0a7207011d51..c7a73a54fd1e96ca67ffce03494dbe98697fca23 100644 --- a/src/ClientServer/appsettings_example.json +++ b/src/ClientServer/appsettings_example.json @@ -23,6 +23,9 @@ "EnsureFileUploadDirStructure": true, "OnlySecureFlaggedCookies": false, "IsInitControllerEnabled": true, - "MaxUploadFileSizeInByte": "1048576" + "MaxUploadFileSizeInByte": "1048576", + "TestServerTraceLogWorkerIntervalInMinutes": 1440, + "TestServerTraceLogDeleteTraceLogOlderThanMinutes" : 21600, + "TestServerTraceLogDeleteTraceLogTimeoutInMs": 60000 }