From d2cd5b95bb347d901b48e7d96fe225f02b7f4c08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Janis=20Daniel=20Da=CC=88hne?=
 <janis.daehne2@student.uni-halle.de>
Date: Mon, 30 Sep 2019 13:59:50 +0200
Subject: [PATCH] - added download personal data feature

---
 src/ClientServer/Config/Constants.cs          |    9 +-
 .../Core/Exercises/SubmissionController.cs    |    3 +-
 src/ClientServer/Controllers/Core/Init.cs     |    1 +
 .../Core/Testing/TestingController.cs         |    2 +
 .../Core/Users/PrivacyController.cs           |  521 ++++
 .../Core/Users/SystemRolesController.cs       |   29 +-
 .../Controllers/Core/Users/UsersController.cs |    3 +-
 src/ClientServer/Helpers/Files.cs             |    4 +-
 ...missionCanDownloadPersonalData.Designer.cs | 2164 +++++++++++++++++
 ...emRolePermissionCanDownloadPersonalData.cs |   25 +
 .../Migrations/YapexDbContextModelSnapshot.cs |    2 +
 ...stomProjectTestWithSolutionAsTestResult.cs |    4 +-
 .../Exercises/AfterSolutions/AfterSolution.cs |    1 -
 ...CustomTestWithAfterSolutionAsTestResult.cs |    2 +-
 .../TestWithAfterSolutionAsTestResult.cs      |    2 +-
 ...ustomTestWithSingleSolutionAsTestResult.cs |    2 +-
 .../ExerciseReleaseWithUserAsParticipation.cs |    4 +-
 .../Models/Exercises/Solution/Solution.cs     |    3 +-
 .../TestWithSingleSolutionAsTestResult.cs     |    2 +-
 .../Models/Exercises/Tests/Test.cs            |    6 +
 .../Files/FileReferenceMarkdownAsset.cs       |    4 +-
 .../Models/Files/FileReferenceTestAsset.cs    |    6 +-
 .../Files/FileReferenceUserFileAsset.cs       |    4 +-
 .../Models/Files/IFileReference.cs            |    3 +
 .../Models/Users/SystemRolePermission.cs      |    5 +
 src/ClientServer/Models/Users/User.cs         |    2 +-
 26 files changed, 2785 insertions(+), 28 deletions(-)
 create mode 100644 src/ClientServer/Controllers/Core/Users/PrivacyController.cs
 create mode 100755 src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.Designer.cs
 create mode 100755 src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.cs

diff --git a/src/ClientServer/Config/Constants.cs b/src/ClientServer/Config/Constants.cs
index e39f097..6440579 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.4.2";
+        public static string VersionString = "2.5.1";
 
         /// <summary>
         /// this is only set once at program.cs!!
@@ -24,6 +24,13 @@ namespace ClientServer.Helpers
         /// the port to use
         /// </summary>
         public static int Port = 5000;
+
+        /// <summary>
+        /// needed when the user downloads personal data and we need to provide the schema some explanation (only the root dir)
+        /// </summary>
+        public static string SourceCodeUrlBase = "https://gitlab.informatik.uni-halle.de/Syndrom/ClientServer";
+
+        public static string SourceCodeModelsDir = "/tree/master/src/ClientServer/Models";
         
         /// <summary>
         /// break the automatic assessment loop after an assessment threw an error (e.g. test server not reachable...)
diff --git a/src/ClientServer/Controllers/Core/Exercises/SubmissionController.cs b/src/ClientServer/Controllers/Core/Exercises/SubmissionController.cs
index a46c28d..0b1dccb 100644
--- a/src/ClientServer/Controllers/Core/Exercises/SubmissionController.cs
+++ b/src/ClientServer/Controllers/Core/Exercises/SubmissionController.cs
@@ -1286,7 +1286,7 @@ namespace ClientServer.Controllers.Core.Exercises
         ///
         /// include the csv file for the tutors that can then be filled and uploaded again
         /// for every plang we create a separate directory (better sorted)
-        /// we also include in every plang direcotry a default submission (with all template files) to be able to compare submissions against the code templates
+        /// we also include in every plang directory a default submission (with all template files) to be able to compare submissions against the code templates
         ///
         /// include also the file name for the submission in the csv file e.g in case we needed to change the filename (ä,ö,ü, ... bad for cross platform filenames)
         /// </summary>
@@ -1294,6 +1294,7 @@ namespace ClientServer.Controllers.Core.Exercises
         [HttpGet("download/submission/all/{releaseCode}")]
         public async Task DownloadAllSubmissionsAsZip(string releaseCode)
         {
+            // because we use a link we don't get and need the csrf token...
             if (!await base.IsLoggedIn(null, false, false)) return;
 
             int userId = GetUserId();
diff --git a/src/ClientServer/Controllers/Core/Init.cs b/src/ClientServer/Controllers/Core/Init.cs
index ee6fe3b..6de39c4 100644
--- a/src/ClientServer/Controllers/Core/Init.cs
+++ b/src/ClientServer/Controllers/Core/Init.cs
@@ -99,6 +99,7 @@ namespace ClientServer.Controllers.Core
                 CanChangeUserData = true,
                 CanManageTags = true,
                 CanViewDashboard = true,
+                CanDownloadPersonalDataFromOthers = true,
             };
 
             //the first system role cannot be changed/deleted!
diff --git a/src/ClientServer/Controllers/Core/Testing/TestingController.cs b/src/ClientServer/Controllers/Core/Testing/TestingController.cs
index 88eb480..4e20fcc 100644
--- a/src/ClientServer/Controllers/Core/Testing/TestingController.cs
+++ b/src/ClientServer/Controllers/Core/Testing/TestingController.cs
@@ -1278,6 +1278,8 @@ namespace ClientServer.Controllers.Core.Testing
 
         /// <summary>
         /// validates the file names for the soltuion files and the test assets (files)
+        ///
+        /// TODO maybe use <see cref="Files.ValidFileName"/>??
         /// </summary>
         /// <param name="solutionFiles">the solution files</param>
         /// <param name="testAssets">the test assets to check too </param>
diff --git a/src/ClientServer/Controllers/Core/Users/PrivacyController.cs b/src/ClientServer/Controllers/Core/Users/PrivacyController.cs
new file mode 100644
index 0000000..60ffed1
--- /dev/null
+++ b/src/ClientServer/Controllers/Core/Users/PrivacyController.cs
@@ -0,0 +1,521 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using ClientServer.Controllers.Core.Exercises;
+using ClientServer.Db;
+using ClientServer.Helpers;
+using ClientServer.Models;
+using ClientServer.Models.Files;
+using ClientServer.Models.Users;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+using NuGet.Packaging;
+
+namespace ClientServer.Controllers.Core.Users
+{
+    [Route(Constants.ApiPrefix + "users/privacy")]
+    public class PrivacyController : ControllerWithDb
+    {
+        public PrivacyController(YapexDbContext context) : base(context)
+        {
+        }
+
+
+        /// <summary>
+        /// returns the user data if the user is logged in else not
+        ///
+        /// <param name="targetUser">the target user (needs system permission) or null for the current user</param>
+        ///
+        /// <remarks>
+        /// here is a list with the current personal data from the user (only types, [] is an array, (is the property) (2019.09.13) - v2.4.2
+        ///
+        ///- user
+        ///  - UserWithUserGroup []
+        ///    - GroupRole
+        ///      (- GroupRolePermission)
+        ///  - UserSetting
+        ///  	- CodeEditorSetting
+        ///  	- Lang
+        ///
+        ///  - Exercise (owner)
+        ///    - MetaData
+        ///      - TagWithMetaDatas []
+        ///      - Tag
+        ///    - CodeTemplates []
+        ///      - TemplateFiles []
+        ///      - PLang
+        ///    - Description
+        ///      - AssetReferences []
+        ///        - FileReferenceMarkdownAsset
+        ///    - Releases []
+        ///      - PLang
+        ///      //ignore ExerciseReleaseWithUserAsParticipations here because this is not really from the user | would hurt other ///users privacy
+        ///    - Tests []
+        ///      - AssetReferences []
+        ///        - FileReferenceTestAsset
+        ///      - TestSettings
+        ///      - TestType
+        ///    - DefaultCustomTestSettings
+        ///
+        ///  - CustomProject
+        ///    - CustomProjectDescription
+        ///    - CustomProjectSolution []
+        ///      - PLang
+        ///      - CustomProjectSolutionFile (main)
+        ///      - CustomProjectSolutionFile []
+        ///      - CustomProjectTestWithSolutionAsTestResult []
+        ///    - CustomProjectTest []
+        ///      - TestType
+        ///      - CustomProjectTestWithFileAsAssetReference [] n-n
+        ///        - FileReferenceUserFileAsset
+        ///      - CustomProjectTestSettings
+        ///      - CustomProjectTestWithSolutionAsTestResult []
+        ///
+        ///  - SystemRole
+        ///  (- SystemRolePermission)
+        ///
+        ///  - ExternalUser
+        ///  - ExerciseReleaseWithUserAsParticipation []
+        ///    - ExerciseRelease (omit release code because hidden in overviews?)
+        ///    - CustomTest
+        ///      - CustomTestWithFileAsAssetReference []
+        ///        - FileReferenceUserFileAsset
+        ///        - TestType
+        ///      - CustomTestWithSingleSolutionAsTestResult [] (TestResultsNew)
+        ///      - CustomTestWithAfterSolutionAsTestResult [] (AfterTestResults)
+        ///    - Solution
+        ///      - PLang
+        ///      - SolutionAssessment
+        ///      - SolutionFile (main)
+        ///        (- TemplateFile (not really "from" the user...)(might be hidden??))
+        ///      - SolutionFile []
+        ///        (- TemplateFile (not really "from" the user...)(might be hidden??))
+        ///      - TestWithSingleSolutionAsTestResult []
+        ///      - CustomTestWithSingleSolutionAsTestResult []
+        ///      - AfterSolution []
+        ///        - AfterSolutionFile (main)
+        ///        - AfterSolutionFile [] 
+        ///        - TestWithAfterSolutionAsTestResult
+        ///        - CustomTestWithAfterSolutionAsTestResult
+        ///    - PLang
+        ///
+        /// </remarks>
+        /// </summary>
+        [HttpGet("download/{targetUser?}")]
+        public async Task DownloadAllPrivateDataAsZip(int? targetUser)
+        {
+            // because we use a link we don't get and need the csrf token...
+            if (!await base.IsLoggedIn(null, false, false)) return;
+
+            int userId = GetUserId();
+
+            if (targetUser == null)
+            {
+                targetUser = userId;
+            }
+
+            if (targetUser != userId)
+            {
+                if (!await HasSystemPermission(
+                    permission => permission != null && permission.SystemRolePermission.CanDownloadPersonalDataFromOthers))
+                {
+                    await
+                        Response.WriteAsync(
+                            Jc.Serialize(new BasicResponse(ResponseCode.NoPermission, "no permission")));
+                    return;
+                }
+            }
+
+
+            User user = await _context.Users
+                .FirstOrDefaultAsync(p => p.Id == targetUser);
+
+            if (user == null)
+            {
+                await Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.NotFound, "user not found")));
+                return;
+            }
+
+            var allUserData = await _context.Users
+                    .Include(p => p.UserWithUserGroups)
+                    .ThenInclude(p => p.GroupRole)
+                    .Include(p => p.UserWithUserGroups)
+                    .ThenInclude(p => p.UserGroup)
+                    //--
+                    .Include(p => p.UserSettings.CodeEditorSetting)
+                    //--
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Description.AssetReferences)
+                    .ThenInclude(p => p.FileReferenceUserFileAsset)
+                    //--
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.MetaData)
+                    .ThenInclude(p => p.TagWithMetaDatas)
+                    .ThenInclude(p => p.Tag)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.CodeTemplates)
+                    .ThenInclude(p => p.TemplateFiles) //includes main file (no extra include)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.CodeTemplates)
+                    .ThenInclude(p => p.PLang)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.Description)
+                    .ThenInclude(p => p.AssetReferences)
+                    .ThenInclude(p => p.FileReferenceMarkdownAsset)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.Releases)
+                    .ThenInclude(p => p.PLang)
+                    //res
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.AssetReferences)
+                    .ThenInclude(p => p.FileReferenceTestAsset)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.TestSettings)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.TestType)
+                    //----
+                    .Include(p => p.Exercises)
+                    .ThenInclude(p => p.DefaultCustomTestSettings)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.PLang)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.SolutionFiles) //includes main file (no extra include)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.TestResults)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.TestType)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.AssetReferences)
+                    .ThenInclude(p => p.FileReferenceUserFileAsset)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.CustomProjectTestSettings)
+                    //----
+                    .Include(p => p.CustomProjects)
+                    .ThenInclude(p => p.Tests)
+                    .ThenInclude(p => p.TestResults)
+                    //--
+                    .Include(p => p.SystemRole)
+                    //--
+                    .Include(p => p.ExternalUser)
+                    //--
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.ExerciseRelease)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.CustomTests)
+                    .ThenInclude(p => p.AssetReferences)
+                    .ThenInclude(p => p.FileReferenceUserFileAsset)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.CustomTests)
+                    .ThenInclude(p => p.TestType)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.CustomTests)
+                    .ThenInclude(p => p.TestResultsNew)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.CustomTests)
+                    .ThenInclude(p => p.AfterTestResults)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.PLang)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.Assessment)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.SolutionFiles) //includes main file (no extra include)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.TestResults)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.CustomTestResults)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.AfterSolution)
+                    .ThenInclude(p => p.SolutionFiles) //includes main file (no extra include)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.AfterSolution)
+                    .ThenInclude(p => p.TestResults)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.Solutions)
+                    .ThenInclude(p => p.AfterSolution)
+                    .ThenInclude(p => p.CustomTestResults)
+                    //----
+                    .Include(p => p.ExerciseReleaseWithUserAsParticipations)
+                    .ThenInclude(p => p.LastEditedPLang)
+                    //--
+                    .FirstOrDefaultAsync(p => p.Id == targetUser)
+                ;
+
+            var jsonSerializerSettings = new JsonSerializerSettings
+            {
+                PreserveReferencesHandling = PreserveReferencesHandling.Objects
+            };
+
+
+            #region files
+
+
+            var exerciseTestAssetsBasePath = Files.GetUploadFilePath(UploadDirType.TestAssets);
+            var exerciseDescriptionAssetsBasePath = Files.GetUploadFilePath(UploadDirType.MarkdownAssets);
+            var userAssetsBasePath = Files.GetUploadFilePath(UploadDirType.UserAssets);
+
+
+            var userOwnExerciseTestIdsToRead = new List<IFileReference>();
+            var userOwnExerciseDescriptionIdsToRead = new List<IFileReference>();
+            var userAssetIdsToRead = new List<IFileReference>();
+
+            //get all asset ids from own exercises
+
+            foreach (var exercise in allUserData.Exercises)
+            {
+                userOwnExerciseDescriptionIdsToRead.AddRange(
+                    exercise.Description.AssetReferences.Select(p => p.FileReferenceMarkdownAsset));
+
+                foreach (var exerciseTest in exercise.Tests)
+                {
+                    userOwnExerciseTestIdsToRead.AddRange(
+                        exerciseTest.AssetReferences.Select(p => p.FileReferenceTestAsset));
+                }
+            }
+            
+            //get all asset ids from custom projects
+            foreach (var customProject in allUserData.CustomProjects)
+            {
+                foreach (var customProjectTest in customProject.Tests)
+                {
+                    userAssetIdsToRead.AddRange(
+                        customProjectTest.AssetReferences.Select(p => p.FileReferenceUserFileAsset));
+                }
+
+                userAssetIdsToRead.AddRange(
+                    customProject.Description.AssetReferences.Select(p => p.FileReferenceUserFileAsset));
+            }
+
+            //get all asset ids from participations
+            foreach (var releaseWithUserAsParticipation in allUserData.ExerciseReleaseWithUserAsParticipations)
+            {
+                foreach (var customTest in releaseWithUserAsParticipation.CustomTests)
+                {
+                    userAssetIdsToRead.AddRange(customTest.AssetReferences.Select(p => p.FileReferenceUserFileAsset));
+                }
+            }
+            
+
+            var exerciseDescriptionFileContentsDict =
+                await Files.ReadUploadedFilesAsArray(exerciseDescriptionAssetsBasePath,
+                    userOwnExerciseDescriptionIdsToRead.Select(p => p.Id).ToArray());
+            
+            var exerciseTestFileContentsDict =
+                await Files.ReadUploadedFilesAsArray(exerciseTestAssetsBasePath,
+                    userOwnExerciseTestIdsToRead.Select(p => p.Id).ToArray());
+
+            var userFileContentsDict =
+                await Files.ReadUploadedFilesAsArray(userAssetsBasePath,
+                    userAssetIdsToRead.Select(p => p.Id).ToArray());
+            
+
+            #endregion
+
+            //for schema give gitlab url to models folder...
+
+            try
+            {
+                var dateTime = DateTimeHelper.GetUtcNow();
+
+AppConfiguration.FileUploadDirRelativePath
+                string dirName =
+                    $"yapex_personalData_{dateTime.Year}_{dateTime.Month}_{dateTime.Day}__{dateTime.Hour}_{dateTime.Minute}_{dateTime.Second}";
+
+                var jsonResult = JsonConvert.SerializeObject(allUserData, Formatting.Indented, jsonSerializerSettings);
+
+                var memStream = new MemoryStream();
+
+                var zipArchive = new ZipArchive(memStream, ZipArchiveMode.Create, true,
+                    SubmissionController.UTF8WithoutBom);
+
+                var entryData = zipArchive.CreateEntry(dirName + "/data.json", CompressionLevel.Fastest);
+                using (var streamWriter = new StreamWriter(entryData.Open(), SubmissionController.UTF8WithoutBom))
+                {
+                    streamWriter.Write(jsonResult);
+                }
+
+                var ownExerciseDescriptionAssetDirName = AppConfiguration.MarkdownAssetFilesUploadDirName;
+                var ownExerciseTestAssetDirName = AppConfiguration.TestAssetFilesUploadDirName;
+                var userAssetDirName = AppConfiguration.UserAssetFilesUploadDirName;
+                
+                var entryInfo = zipArchive.CreateEntry(dirName + "/readme.txt", CompressionLevel.Fastest);
+                using (var streamWriter = new StreamWriter(entryInfo.Open(), SubmissionController.UTF8WithoutBom))
+                {
+                    streamWriter.WriteLine("Note that the .json file was serialized with references");
+                    streamWriter.WriteLine(
+                        "You can deserialize the data e.g. with Json.NET (https://www.newtonsoft.com/json | https://github.com/JamesNK/Newtonsoft.Json)");
+                    streamWriter.WriteLine(
+                        "The files are placed inside the corresponding directories (split between user files, exercise files, markdown files)");
+                    //even though frontend and backend don't allow spaces in file names... but legacy
+                    streamWriter.WriteLine("If a file name contains spaces then they are replaced by underscore(s)");
+                    streamWriter.WriteLine("All file names are prefixed with the file id followed by an underscore");
+                    streamWriter.WriteLine(
+                        "If then a file name contains some invalid characters the name is replaced with a unique guid");
+
+                    streamWriter.WriteLine();
+                    streamWriter.WriteLine($"Files for exercise descriptions can be found in /{ownExerciseDescriptionAssetDirName} (if any)");
+                    streamWriter.WriteLine($"Files for exercise tests can be found in /{ownExerciseTestAssetDirName} (if any)");
+                    streamWriter.WriteLine($"Files for your custom project description and tests can be found in /{userAssetDirName} (if any)");
+                    streamWriter.WriteLine();
+                    
+                    streamWriter.WriteLine(
+                        $"The structure/schema of the data can be found here: {Constants.SourceCodeUrlBase}, specifically {Constants.SourceCodeUrlBase}{Constants.SourceCodeModelsDir}");
+                }
+
+                #region (own) exercise description markdown files
+
+                foreach (var assetReference in userOwnExerciseDescriptionIdsToRead)
+                {
+                    var content = exerciseDescriptionFileContentsDict[assetReference.Id];
+                    var fileName = $"{assetReference.Id}_{assetReference.OriginalName}";
+
+                    if (Files.ValidFileName(fileName) == false)
+                    {
+                        fileName = fileName.Replace(' ', '_');
+
+                        if (Files.ValidFileName(fileName) == false)
+                        {
+                            fileName = $"{assetReference.Id}_{Guid.NewGuid()}";
+                        }
+                    }
+
+                    var entryFile = zipArchive.CreateEntry(dirName + "/" + ownExerciseDescriptionAssetDirName + "/" + fileName,
+                        CompressionLevel.Fastest);
+                    using (var streamWriter = new StreamWriter(entryFile.Open(), SubmissionController.UTF8WithoutBom))
+                    {
+                        streamWriter.BaseStream.Write(content, 0, content.Length);
+                    }
+                }
+                
+                #endregion
+                
+                #region (own) exercise test files
+
+                foreach (var assetReference in userOwnExerciseTestIdsToRead)
+                {
+                    var content = exerciseTestFileContentsDict[assetReference.Id];
+                    var fileName = $"{assetReference.Id}_{assetReference.OriginalName}";
+
+                    if (Files.ValidFileName(fileName) == false)
+                    {
+                        fileName = fileName.Replace(' ', '_');
+
+                        if (Files.ValidFileName(fileName) == false)
+                        {
+                            fileName = $"{assetReference.Id}_{Guid.NewGuid()}";
+                        }
+                    }
+
+                    var entryFile = zipArchive.CreateEntry(dirName + "/" + ownExerciseTestAssetDirName + "/" + fileName,
+                        CompressionLevel.Fastest);
+                    using (var streamWriter = new StreamWriter(entryFile.Open(), SubmissionController.UTF8WithoutBom))
+                    {
+                        streamWriter.BaseStream.Write(content, 0, content.Length);
+                    }
+                }
+                
+                #endregion
+                
+
+                #region custom user files
+
+                foreach (var assetReference in userAssetIdsToRead)
+                {
+                    var content = userFileContentsDict[assetReference.Id];
+                    var fileName = $"{assetReference.Id}_{assetReference.OriginalName}";
+
+                    if (Files.ValidFileName(fileName) == false)
+                    {
+                        fileName = fileName.Replace(' ', '_');
+
+                        if (Files.ValidFileName(fileName) == false)
+                        {
+                            fileName = $"{assetReference.Id}_{Guid.NewGuid()}";
+                        }
+                    }
+
+                    var entryFile = zipArchive.CreateEntry(dirName + "/" + userAssetDirName + "/" + fileName,
+                        CompressionLevel.Fastest);
+                    using (var streamWriter = new StreamWriter(entryFile.Open(), SubmissionController.UTF8WithoutBom))
+                    {
+                        streamWriter.BaseStream.Write(content, 0, content.Length);
+                    }
+                }
+                
+                #endregion
+                
+
+                zipArchive.Dispose();
+                var t = base.File(memStream.ToArray(), "application/zip", dirName + ".zip");
+
+                await t.ExecuteResultAsync(this.ControllerContext);
+
+                return;
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine($"[ERROR] downloading personal data for user id: {targetUser}, error: {e.Message}");
+
+                if (AppConfiguration.IsDebugMode)
+                {
+                    await
+                        Response.WriteAsync(
+                            Jc.Serialize(new BasicResponse(ResponseCode.ServerError,
+                                $"could not create personal data zip file, error: {e.Message}")));
+                }
+                else
+                {
+                    await
+                        Response.WriteAsync(
+                            Jc.Serialize(new BasicResponse(ResponseCode.ServerError,
+                                "could not create personal data zip file")));
+                }
+            }
+        }
+    }
+}
diff --git a/src/ClientServer/Controllers/Core/Users/SystemRolesController.cs b/src/ClientServer/Controllers/Core/Users/SystemRolesController.cs
index 325479b..9cce606 100644
--- a/src/ClientServer/Controllers/Core/Users/SystemRolesController.cs
+++ b/src/ClientServer/Controllers/Core/Users/SystemRolesController.cs
@@ -66,14 +66,15 @@ namespace ClientServer.Controllers.Core.Users
                         CanChangeUserData = p.SystemRolePermission.CanChangeUserData,
                         CanManageTags = p.SystemRolePermission.CanManageTags,
                         CanChangeSystemSettings = p.SystemRolePermission.CanChangeSystemSettings,
-                        CanViewDashboard = p.SystemRolePermission.CanViewDashboard
+                        CanViewDashboard = p.SystemRolePermission.CanViewDashboard,
+                        CanDownloadPersonalDataFromOthers = p.SystemRolePermission.CanDownloadPersonalDataFromOthers,
                     }).ToListAsync();
 
 
             await
                 Response.WriteAsync(
                     Jc.Serialize(new BasicResponseWithData<List<SystemRoleFullBase>>(ResponseCode.Ok,
-                        "system roles queried", displayRoles)));
+                        "", displayRoles)));
         }
 
         /// <summary>
@@ -110,7 +111,7 @@ namespace ClientServer.Controllers.Core.Users
             await
                 Response.WriteAsync(
                     Jc.Serialize(new BasicResponseWithData<List<int>>(ResponseCode.Ok,
-                        "system roles queried", new List<int>() {first.Id})));
+                        "", new List<int>() {first.Id})));
         }
 
         /// <summary>
@@ -150,7 +151,8 @@ namespace ClientServer.Controllers.Core.Users
                     CanChangeUserData = roleForBackend.CanChangeUserData,
                     CanChangeSystemSettings = roleForBackend.CanChangeSystemSettings,
                     CanManageTags = roleForBackend.CanManageTags,
-                    CanViewDashboard = roleForBackend.CanViewDashboard
+                    CanViewDashboard = roleForBackend.CanViewDashboard,
+                    CanDownloadPersonalDataFromOthers = roleForBackend.CanDownloadPersonalDataFromOthers,
                 }
             };
 
@@ -183,13 +185,14 @@ namespace ClientServer.Controllers.Core.Users
                 CanChangeUserData = roleForBackend.CanChangeUserData,
                 CanChangeSystemSettings = roleForBackend.CanChangeSystemSettings,
                 CanManageTags = roleForBackend.CanManageTags,
-                CanViewDashboard = roleForBackend.CanViewDashboard
+                CanViewDashboard = roleForBackend.CanViewDashboard,
+                CanDownloadPersonalDataFromOthers = roleForBackend.CanDownloadPersonalDataFromOthers,
             };
 
             await
                 Response.WriteAsync(
                     Jc.Serialize(new BasicResponseWithData<SystemRoleFullBase>(ResponseCode.Ok,
-                        "group independendt role created", newRoleForFrontend)));
+                        "", newRoleForFrontend)));
         }
 
         /// <summary>
@@ -250,6 +253,8 @@ namespace ClientServer.Controllers.Core.Users
             oldRole.SystemRolePermission.CanChangeSystemSettings = roleForBackend.CanChangeSystemSettings;
             oldRole.SystemRolePermission.CanManageTags = roleForBackend.CanManageTags;
             oldRole.SystemRolePermission.CanViewDashboard = roleForBackend.CanViewDashboard;
+            oldRole.SystemRolePermission.CanDownloadPersonalDataFromOthers =
+                roleForBackend.CanDownloadPersonalDataFromOthers;
 
             try
             {
@@ -278,13 +283,14 @@ namespace ClientServer.Controllers.Core.Users
                 CanChangeUserData = oldRole.SystemRolePermission.CanChangeUserData,
                 CanManageTags = oldRole.SystemRolePermission.CanManageTags,
                 CanChangeSystemSettings = oldRole.SystemRolePermission.CanChangeSystemSettings,
-                CanViewDashboard = oldRole.SystemRolePermission.CanViewDashboard
+                CanViewDashboard = oldRole.SystemRolePermission.CanViewDashboard,
+                CanDownloadPersonalDataFromOthers = oldRole.SystemRolePermission.CanDownloadPersonalDataFromOthers,
             };
 
 
             await
                 Response.WriteAsync(
-                    Jc.Serialize(new BasicResponseWithData<SystemRoleFullBase>(ResponseCode.Ok, "cahnged system role",
+                    Jc.Serialize(new BasicResponseWithData<SystemRoleFullBase>(ResponseCode.Ok, "",
                         roleForFrontend)));
         }
 
@@ -338,7 +344,7 @@ namespace ClientServer.Controllers.Core.Users
                 return;
             }
 
-            await Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.Ok, "system role deleted")));
+            await Response.WriteAsync(Jc.Serialize(new BasicResponse(ResponseCode.Ok, "")));
         }
     }
 
@@ -419,5 +425,10 @@ namespace ClientServer.Controllers.Core.Users
         /// true: can, false: not
         /// </summary>
         public bool CanViewDashboard { get; set; }
+
+        /// <summary>
+        /// true: can download personal data from other users, false: not
+        /// </summary>
+        public bool CanDownloadPersonalDataFromOthers { get; set; }
     }
 }
diff --git a/src/ClientServer/Controllers/Core/Users/UsersController.cs b/src/ClientServer/Controllers/Core/Users/UsersController.cs
index 2b19120..b8fc74f 100644
--- a/src/ClientServer/Controllers/Core/Users/UsersController.cs
+++ b/src/ClientServer/Controllers/Core/Users/UsersController.cs
@@ -85,7 +85,8 @@ namespace ClientServer.Controllers.Core.Users
                     CanChangeUserData = role.SystemRolePermission.CanChangeUserData,
                     CanManageTags = role.SystemRolePermission.CanManageTags,
                     CanChangeSystemSettings = role.SystemRolePermission.CanChangeSystemSettings,
-                    CanViewDashboard = role.SystemRolePermission.CanViewDashboard
+                    CanViewDashboard = role.SystemRolePermission.CanViewDashboard,
+                    CanDownloadPersonalDataFromOthers = role.SystemRolePermission.CanDownloadPersonalDataFromOthers,
                 };
             }
 
diff --git a/src/ClientServer/Helpers/Files.cs b/src/ClientServer/Helpers/Files.cs
index 0626930..fa3f0e4 100644
--- a/src/ClientServer/Helpers/Files.cs
+++ b/src/ClientServer/Helpers/Files.cs
@@ -28,6 +28,8 @@ namespace ClientServer.Helpers
         /// <summary>
         /// checks if the given name is a valid file name
         /// umlauts are NOT valid e.g. getting a file from windows with umlauts to a mac ... the mac can't display them (or vice versa?)
+        ///
+        /// no spaces because they are bad for paths...
         /// </summary>
         /// <param name="name">the file name</param>
         /// <returns>true: valid, false: not</returns>
@@ -339,7 +341,7 @@ namespace ClientServer.Helpers
         /// </summary>
         MarkdownAssets,
         /// <summary>
-        /// asset files for tests
+        /// asset files for tests (normal, submit)
         /// </summary>
         TestAssets,
         /// <summary>
diff --git a/src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.Designer.cs b/src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.Designer.cs
new file mode 100755
index 0000000..6ad929c
--- /dev/null
+++ b/src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.Designer.cs
@@ -0,0 +1,2164 @@
+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("20190914132004_SystemRolePermissionCanDownloadPersonalData")]
+    partial class SystemRolePermissionCanDownloadPersonalData
+    {
+        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>("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/20190914132004_SystemRolePermissionCanDownloadPersonalData.cs b/src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.cs
new file mode 100755
index 0000000..bc0b934
--- /dev/null
+++ b/src/ClientServer/Migrations/20190914132004_SystemRolePermissionCanDownloadPersonalData.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace ClientServer.Migrations
+{
+    public partial class SystemRolePermissionCanDownloadPersonalData : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<bool>(
+                name: "CanDownloadPersonalDataFromOthers",
+                table: "SystemRolePermissions",
+                nullable: false,
+                defaultValue: false);
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "CanDownloadPersonalDataFromOthers",
+                table: "SystemRolePermissions");
+        }
+    }
+}
diff --git a/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs b/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs
index aed1841..8b3d9b7 100755
--- a/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs
+++ b/src/ClientServer/Migrations/YapexDbContextModelSnapshot.cs
@@ -1496,6 +1496,8 @@ namespace ClientServer.Migrations
 
                     b.Property<bool>("CanDeleteRoles");
 
+                    b.Property<bool>("CanDownloadPersonalDataFromOthers");
+
                     b.Property<bool>("CanManageNewUsers");
 
                     b.Property<bool>("CanManageTags");
diff --git a/src/ClientServer/Models/CustomProjects/CustomProjectTestWithSolutionAsTestResult.cs b/src/ClientServer/Models/CustomProjects/CustomProjectTestWithSolutionAsTestResult.cs
index 7e4fa7a..e2a7b18 100644
--- a/src/ClientServer/Models/CustomProjects/CustomProjectTestWithSolutionAsTestResult.cs
+++ b/src/ClientServer/Models/CustomProjects/CustomProjectTestWithSolutionAsTestResult.cs
@@ -80,12 +80,12 @@ namespace ClientServer.Models.CustomProjects
         public string TestServerVersion { get; set; }
         
         /// <summary>
-        /// true: the limit <see cref="YapexDbContext.TestProtocolMaxStringLength"/> was exceeded and
+        /// true: the limit <see cref="YapexDbContext.TestProtocolMaxStringLength"/> was exceeded for the <see cref="Protocol"/> and
         /// the <see cref="Protocol"/> was cut
         /// </summary>
         public bool CharacterLimitExceeded { get; set; }
         /// <summary>
-        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/>
+        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/> for the <see cref="Protocol"/>
         /// can be null for old results
         /// </summary>
         public int? CharacterLimitUsed { get; set; }
diff --git a/src/ClientServer/Models/Exercises/AfterSolutions/AfterSolution.cs b/src/ClientServer/Models/Exercises/AfterSolutions/AfterSolution.cs
index 56f26b5..53bf732 100644
--- a/src/ClientServer/Models/Exercises/AfterSolutions/AfterSolution.cs
+++ b/src/ClientServer/Models/Exercises/AfterSolutions/AfterSolution.cs
@@ -1,6 +1,5 @@
 using System;
 using System.Collections.Generic;
-using ClientServer.Models.Exercises.Solution;
 
 namespace ClientServer.Models.Exercises.AfterSolutions
 {
diff --git a/src/ClientServer/Models/Exercises/AfterSolutions/CustomTestWithAfterSolutionAsTestResult.cs b/src/ClientServer/Models/Exercises/AfterSolutions/CustomTestWithAfterSolutionAsTestResult.cs
index aba9c74..a52384d 100644
--- a/src/ClientServer/Models/Exercises/AfterSolutions/CustomTestWithAfterSolutionAsTestResult.cs
+++ b/src/ClientServer/Models/Exercises/AfterSolutions/CustomTestWithAfterSolutionAsTestResult.cs
@@ -91,7 +91,7 @@ namespace ClientServer.Models.Exercises.AfterSolutions
         /// </summary>
         public bool CharacterLimitExceeded { get; set; }
         /// <summary>
-        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/>
+        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/> for <see cref="Protocol"/>
         /// can be null for old results
         /// </summary>
         public int? CharacterLimitUsed { get; set; }
diff --git a/src/ClientServer/Models/Exercises/AfterSolutions/TestWithAfterSolutionAsTestResult.cs b/src/ClientServer/Models/Exercises/AfterSolutions/TestWithAfterSolutionAsTestResult.cs
index fae4b2b..c0d1823 100644
--- a/src/ClientServer/Models/Exercises/AfterSolutions/TestWithAfterSolutionAsTestResult.cs
+++ b/src/ClientServer/Models/Exercises/AfterSolutions/TestWithAfterSolutionAsTestResult.cs
@@ -93,7 +93,7 @@ namespace ClientServer.Models.Exercises.AfterSolutions
         /// </summary>
         public bool CharacterLimitExceeded { get; set; }
         /// <summary>
-        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/>
+        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/> or <see cref="Protocol"/>
         /// can be null for old results
         /// </summary>
         public int? CharacterLimitUsed { get; set; }
diff --git a/src/ClientServer/Models/Exercises/Solution/CustomTestWithSingleSolutionAsTestResult.cs b/src/ClientServer/Models/Exercises/Solution/CustomTestWithSingleSolutionAsTestResult.cs
index 089f479..a9edc08 100644
--- a/src/ClientServer/Models/Exercises/Solution/CustomTestWithSingleSolutionAsTestResult.cs
+++ b/src/ClientServer/Models/Exercises/Solution/CustomTestWithSingleSolutionAsTestResult.cs
@@ -101,7 +101,7 @@ namespace ClientServer.Models.Exercises.Solution
         /// </summary>
         public bool CharacterLimitExceeded { get; set; }
         /// <summary>
-        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/>
+        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/> for <see cref="Protocol"/>
         /// can be null for old results
         /// </summary>
         public int? CharacterLimitUsed { get; set; }
diff --git a/src/ClientServer/Models/Exercises/Solution/ExerciseReleaseWithUserAsParticipation.cs b/src/ClientServer/Models/Exercises/Solution/ExerciseReleaseWithUserAsParticipation.cs
index d621a2f..353b381 100644
--- a/src/ClientServer/Models/Exercises/Solution/ExerciseReleaseWithUserAsParticipation.cs
+++ b/src/ClientServer/Models/Exercises/Solution/ExerciseReleaseWithUserAsParticipation.cs
@@ -7,8 +7,8 @@ using ClientServer.Models.Users;
 namespace ClientServer.Models.Exercises.Solution
 {
     /// <summary>
-    /// a class to store wich user has entered wich code (which user can access which exercise)
-    /// and stores the solution
+    /// a class to store which user has entered which code (which user can access which exercise)
+    /// and stores the solution for an exercise
     /// </summary>
     public class ExerciseReleaseWithUserAsParticipation
     {
diff --git a/src/ClientServer/Models/Exercises/Solution/Solution.cs b/src/ClientServer/Models/Exercises/Solution/Solution.cs
index 92a308b..7d482fa 100644
--- a/src/ClientServer/Models/Exercises/Solution/Solution.cs
+++ b/src/ClientServer/Models/Exercises/Solution/Solution.cs
@@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Threading.Tasks;
 using ClientServer.Db;
+using ClientServer.Helpers;
 using ClientServer.Models.Exercises.AfterSolutions;
 
 namespace ClientServer.Models.Exercises.Solution
@@ -96,7 +97,7 @@ namespace ClientServer.Models.Exercises.Solution
         /// the connected files for this solution version
         /// NOTE that these contain contain copies of readonly files from the exercise
         /// DO replace readonly files in this list with the version from the exercise (because this is always up to date)
-        /// USE the helper <see cref="UserSUserSolutionHelper"/> to replace the readonly files
+        /// USE the helper <see cref="UserSolutionHelper"/> to replace the readonly files
         /// </summary>
         public List<SolutionFile> SolutionFiles { get; set; }
         
diff --git a/src/ClientServer/Models/Exercises/Solution/TestWithSingleSolutionAsTestResult.cs b/src/ClientServer/Models/Exercises/Solution/TestWithSingleSolutionAsTestResult.cs
index 3e1d5c1..b8c436b 100644
--- a/src/ClientServer/Models/Exercises/Solution/TestWithSingleSolutionAsTestResult.cs
+++ b/src/ClientServer/Models/Exercises/Solution/TestWithSingleSolutionAsTestResult.cs
@@ -103,7 +103,7 @@ namespace ClientServer.Models.Exercises.Solution
         /// </summary>
         public bool CharacterLimitExceeded { get; set; }
         /// <summary>
-        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/>
+        /// the db will only store a max of characters <see cref="YapexDbContext.TestProtocolMaxStringLength"/> for <see cref="Protocol"/>
         /// can be null for old results
         /// </summary>
         public int? CharacterLimitUsed { get; set; }
diff --git a/src/ClientServer/Models/Exercises/Tests/Test.cs b/src/ClientServer/Models/Exercises/Tests/Test.cs
index 70a9ec9..4ed91b2 100644
--- a/src/ClientServer/Models/Exercises/Tests/Test.cs
+++ b/src/ClientServer/Models/Exercises/Tests/Test.cs
@@ -76,8 +76,14 @@ namespace ClientServer.Models.Exercises.Tests
         
         //---added
         
+        /// <summary>
+        /// all results for this test
+        /// </summary>
         public List<TestWithSingleSolutionAsTestResult> TestResultsNew { get; set; }
         
+        /// <summary>
+        /// all results for this after test (after release has finished...) 
+        /// </summary>
         public List<TestWithAfterSolutionAsTestResult> AfterTestResults { get; set; }
     }
 }
diff --git a/src/ClientServer/Models/Files/FileReferenceMarkdownAsset.cs b/src/ClientServer/Models/Files/FileReferenceMarkdownAsset.cs
index 575bd83..205097d 100644
--- a/src/ClientServer/Models/Files/FileReferenceMarkdownAsset.cs
+++ b/src/ClientServer/Models/Files/FileReferenceMarkdownAsset.cs
@@ -11,6 +11,8 @@ namespace ClientServer.Models.Files
     /// a reference to a file on the hard drive
     /// for exercise description (markdown) asset files
     /// files are stored without an extension because we don't need to know
+    ///
+    /// as file name we use the id in the db
     /// </summary>
     public class FileReferenceMarkdownAsset : IFileReference
     {
@@ -28,7 +30,7 @@ namespace ClientServer.Models.Files
         public string Hash { get; set; }
 
         /// <summary>
-        /// original name of the file (at the time the file was uploaded)
+        /// original name of the file (at the time the file was uploaded)  (might have no extension if uploaded without!)
         /// </summary>
         [MaxLength(YapexDbContext.DefaultMaxStringLength)]
         public string OriginalName { get; set; }
diff --git a/src/ClientServer/Models/Files/FileReferenceTestAsset.cs b/src/ClientServer/Models/Files/FileReferenceTestAsset.cs
index f728ece..f8964b3 100644
--- a/src/ClientServer/Models/Files/FileReferenceTestAsset.cs
+++ b/src/ClientServer/Models/Files/FileReferenceTestAsset.cs
@@ -9,8 +9,10 @@ namespace ClientServer.Models.Files
 {
     /// <summary>
     /// a reference to a file on the hard drive
-    /// for test asset files
+    /// for test asset files (normal, submit tests)
     /// files are stored without an extension because we don't need to know
+    ///
+    /// as file name we use the id in the db
     /// </summary>
     public class FileReferenceTestAsset : IFileReference
     {
@@ -28,7 +30,7 @@ namespace ClientServer.Models.Files
         public string Hash { get; set; }
         
         /// <summary>
-        /// original name of the file (at the time the file was uploaded)
+        /// original name of the file (at the time the file was uploaded)  (might have no extension if uploaded without!)
         /// </summary>
         [MaxLength(YapexDbContext.DefaultMaxStringLength)]
         public string OriginalName { get; set; }
diff --git a/src/ClientServer/Models/Files/FileReferenceUserFileAsset.cs b/src/ClientServer/Models/Files/FileReferenceUserFileAsset.cs
index dc2754c..0e1f6b0 100644
--- a/src/ClientServer/Models/Files/FileReferenceUserFileAsset.cs
+++ b/src/ClientServer/Models/Files/FileReferenceUserFileAsset.cs
@@ -12,6 +12,8 @@ namespace ClientServer.Models.Files
     /// a reference to a file on the hard drive
     /// for user files (may be a custom test file, custom project test file, custom project description markdown asset)
     /// files are stored without an extension because we don't need to know
+    ///
+    /// as file name we use the id in the db
     /// </summary>
     public class FileReferenceUserFileAsset : IFileReference
     {
@@ -31,7 +33,7 @@ namespace ClientServer.Models.Files
         public string Hash { get; set; }
         
         /// <summary>
-        /// original name of the file (at the time the file was uploaded)
+        /// original name of the file (at the time the file was uploaded)  (might have no extension if uploaded without!)
         /// </summary>
         [MaxLength(YapexDbContext.DefaultMaxStringLength)]
         public string OriginalName { get; set; }
diff --git a/src/ClientServer/Models/Files/IFileReference.cs b/src/ClientServer/Models/Files/IFileReference.cs
index 2da462f..960ee42 100644
--- a/src/ClientServer/Models/Files/IFileReference.cs
+++ b/src/ClientServer/Models/Files/IFileReference.cs
@@ -8,6 +8,9 @@ namespace ClientServer.Models.Files
         
         string Hash { get; set; }
         
+        /// <summary>
+        /// the original name when the file was uploaded (might have no extension if uploaded without!)
+        /// </summary>
         string OriginalName { get; set; }
         
         string MimeType { get; set; }
diff --git a/src/ClientServer/Models/Users/SystemRolePermission.cs b/src/ClientServer/Models/Users/SystemRolePermission.cs
index bee0b07..ced1fbc 100644
--- a/src/ClientServer/Models/Users/SystemRolePermission.cs
+++ b/src/ClientServer/Models/Users/SystemRolePermission.cs
@@ -77,6 +77,11 @@ namespace ClientServer.Models.Users
         /// </summary>
         public bool CanViewDashboard { get; set; }
 
+        /// <summary>
+        /// true: can download personal data from other users, false: not
+        /// </summary>
+        public bool CanDownloadPersonalDataFromOthers { get; set; }
+
         //fk
         public int SystemRoleId { get; set; }
         public SystemRole SystemRole { get; set; }
diff --git a/src/ClientServer/Models/Users/User.cs b/src/ClientServer/Models/Users/User.cs
index d42b7b0..597b8ca 100644
--- a/src/ClientServer/Models/Users/User.cs
+++ b/src/ClientServer/Models/Users/User.cs
@@ -95,7 +95,7 @@ namespace ClientServer.Models.Users
 
 
         /// <summary>
-        /// the <see cref="Exercise"/> s where this user was the creator
+        /// the <see cref="Exercise"/> s where this user was the creator / is the owner
         /// </summary>
         public List<Exercise> Exercises { get; set; }
 
-- 
GitLab