From 526dbc18826d67a6effa4c9cf90bae23ea0efb8a Mon Sep 17 00:00:00 2001 From: Max van Hattum <438466@student.fontys.nl> Date: Mon, 22 Mar 2021 15:41:24 +0100 Subject: [PATCH 01/66] Added route in project controller for retreiving autocompleted projects based on the title matching part of the query, added resource results for this and the whole service/repository chain --- API/Configuration/MappingProfile.cs | 3 ++ API/Controllers/ProjectController.cs | 15 ++++++++ API/Resources/AutocompleteProjectResource.cs | 27 ++++++++++++++ API/Resources/AutocompleteProjectsResource.cs | 18 ++++++++++ Repositories/ElasticSearch/Queries.cs | 2 ++ Repositories/ProjectRepository.cs | 35 +++++++++++++++++++ Services/Services/ProjectService.cs | 10 ++++++ 7 files changed, 110 insertions(+) create mode 100644 API/Resources/AutocompleteProjectResource.cs create mode 100644 API/Resources/AutocompleteProjectsResource.cs diff --git a/API/Configuration/MappingProfile.cs b/API/Configuration/MappingProfile.cs index 32484a08..ca35f35b 100644 --- a/API/Configuration/MappingProfile.cs +++ b/API/Configuration/MappingProfile.cs @@ -35,6 +35,9 @@ public class MappingProfile : Profile /// public MappingProfile() { + CreateMap(); + CreateMap(); + CreateMap() .ForMember(source => source.Id, option => option diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 49bc17e4..7e9dae3e 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -229,6 +229,21 @@ await projectService.GetProjectsTotalPages( return Ok(resultsResource); } + + [HttpGet("search/autocomplete")] + [ProducesResponseType(typeof(AutocompleteProjectsResource), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task GetAutoCompleteProjects([FromQuery(Name ="query")] string query) + { + List projects = await projectService.FindProjectsWhereTitleStartsWithQuery(query); + AutocompleteProjectsResource resultResource = new AutocompleteProjectsResource(); + List autocompleteProjectResources = mapper.Map, List>(projects); + resultResource.AutocompleteProjects = autocompleteProjectResources; + + return Ok(resultResource); + } + + /// /// This method is responsible for retrieving a single project. /// diff --git a/API/Resources/AutocompleteProjectResource.cs b/API/Resources/AutocompleteProjectResource.cs new file mode 100644 index 00000000..2a0dce03 --- /dev/null +++ b/API/Resources/AutocompleteProjectResource.cs @@ -0,0 +1,27 @@ +using Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace API.Resources +{ + /// + /// The view model of a single autocompleted search project. + /// + public class AutocompleteProjectResource + { + /// + /// Gets or sets the Id of the autocompleted search project. + /// + public string Id { get; set; } + /// + /// Gets or sets the title of the autocompleted search project. + /// + public string Name { get; set; } + /// + /// Gets or sets the Icon of the autocompleted search project. + /// + public File ProjectIcon { get; set; } + } +} diff --git a/API/Resources/AutocompleteProjectsResource.cs b/API/Resources/AutocompleteProjectsResource.cs new file mode 100644 index 00000000..b49ef399 --- /dev/null +++ b/API/Resources/AutocompleteProjectsResource.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace API.Resources +{ + /// + /// The viewmodel that is returned by the Autocomplete search route in the Project controller. + /// + public class AutocompleteProjectsResource + { + /// + /// The List of Autocompleted search projects. + /// + public List AutocompleteProjects { get; set; } + } +} diff --git a/Repositories/ElasticSearch/Queries.cs b/Repositories/ElasticSearch/Queries.cs index 7d42d6a7..d0a48fb4 100644 --- a/Repositories/ElasticSearch/Queries.cs +++ b/Repositories/ElasticSearch/Queries.cs @@ -23,9 +23,11 @@ public class Queries "\"text\",\"analyzer\":\"autocomplete\",\"search_analyzer\":\"autocomplete_search\"},\"Description\":" + "{ \"type\":\"text\",\"analyzer\":\"description_index\",\"search_analyzer\":\"description_search\"}," + "\"Likes\":{ \"type\":\"integer\"} } }}"; + private string searchProjectsByPartOfTitle = "{\"query\":{\"match\":{\"ProjectName\":{\"query\":\"ReplaceWithQuery\"}}}}"; public string ProjectRecommendations { get => projectRecommendations; } public string SimilarUsers { get => similarUsers; } public string IndexProjects { get => indexProjects; } + public string SearchProjectsByPartOfTitle { get => searchProjectsByPartOfTitle; set => searchProjectsByPartOfTitle = value; } } } diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 9ef5261b..3779a936 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -31,6 +31,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; +using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -111,6 +112,15 @@ Task> SearchAsync( void CreateProjectIndex(); void DeleteIndex(); void MigrateDatabase(List projectsToExport); + + /// + /// This method will call the ElasticSearch index to retreive projects where the title starts with the query. + /// + /// The string of characters with which the title must begin + /// + /// This method return a list of projects where the title, or part of the title matches the query. + /// + Task> FindProjectsWhereTitleStartsWithQuery(string query); } /// @@ -594,6 +604,10 @@ public async Task> GetLikedProjectsFromSimilarUser(int userId, int private void ParseJsonToESProjectFormatDTOList(IRestResponse restResponse, List esProjectFormats) { + if(restResponse.StatusCode != HttpStatusCode.OK) + { + return; + } JObject esProjects = JObject.Parse(restResponse.Content); JToken projects = esProjects.GetValue("hits")["hits"]; foreach(JToken project in projects) @@ -643,6 +657,27 @@ public async Task SyncProjectToES(Project project) } + + /// + /// This method will call the ElasticSearch index to retreive projects where the title starts with the query. + /// + /// The string of characters with which the title must begin + /// + /// This method return a list of projects where the title, or part of the title matches the query. + /// + public async Task> FindProjectsWhereTitleStartsWithQuery(string query) + { + RestRequest request = new RestRequest("_search", Method.POST); + string body = queries.SearchProjectsByPartOfTitle + .Replace("ReplaceWithQuery", query); + request.AddParameter("application/json", body, ParameterType.RequestBody); + IRestResponse restResponse = elasticSearchContext.Execute(request); + + List esProjectFormats = new List(); + + ParseJsonToESProjectFormatDTOList(restResponse, esProjectFormats); + return await ConvertProjects(esProjectFormats); + } } } diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index 47a1fad1..4c7d6d9d 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -76,6 +76,12 @@ public interface IProjectService : IService /// The project with users and collaborators Task> GetAllWithUserAndCollaboratorsAsync(); + /// + /// Get projects where the title begins with a certain string of characters. + /// + /// The string with which the title must begin + /// The projects where the title matches the query + Task> FindProjectsWhereTitleStartsWithQuery(string query); } /// @@ -230,6 +236,10 @@ private void CreateProjectIndexElastic() Repository.CreateProjectIndex(); } + public async Task> FindProjectsWhereTitleStartsWithQuery(string query) + { + return await Repository.FindProjectsWhereTitleStartsWithQuery(query); + } } } From cb91a04aef3ec1958528b13cbff89c5f80070bd7 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Wed, 14 Apr 2021 12:47:17 +0200 Subject: [PATCH 02/66] created Postman tests for autocomplete search project suggestions --- .../DeX_Elastic.postman_collection.json | 138 +++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index 7d61d76f..0e5aa9a9 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "bb63de1d-4b49-45dd-9517-ac44e49fc183", + "_postman_id": "8cc3f0c7-3271-4261-9950-950fefbc2d3d", "name": "DeX_Elastic", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -187,7 +187,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"Project 1\",\r\n \"description\": \"postmantest_ProjectToFollow\",\r\n \"shortDescription\": \"postmantest_ProjectToFollow\",\r\n \"uri\": \"postmantest_ProjectToFollow\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToFollow\",\r\n \"role\": \"postmantest_ProjectToFollow\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\"\r\n}", + "raw": "{\r\n \"name\": \"Testproject 1\",\r\n \"description\": \"postmantest_ProjectToFollow\",\r\n \"shortDescription\": \"postmantest_ProjectToFollow\",\r\n \"uri\": \"postmantest_ProjectToFollow\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToFollow\",\r\n \"role\": \"postmantest_ProjectToFollow\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\"\r\n}", "options": { "raw": { "language": "json" @@ -299,7 +299,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"Project 3\",\r\n \"description\": \"postmantest_ProjectToFollow\",\r\n \"shortDescription\": \"postmantest_ProjectToFollow\",\r\n \"uri\": \"postmantest_ProjectToFollow\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToFollow\",\r\n \"role\": \"postmantest_ProjectToFollow\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\"\r\n}", + "raw": "{\r\n \"name\": \"DeX Backend\",\r\n \"description\": \"postmantest_ProjectToFollow\",\r\n \"shortDescription\": \"postmantest_ProjectToFollow\",\r\n \"uri\": \"postmantest_ProjectToFollow\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToFollow\",\r\n \"role\": \"postmantest_ProjectToFollow\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\"\r\n}", "options": { "raw": { "language": "json" @@ -1523,6 +1523,138 @@ } }, "response": [] + }, + { + "name": "Get autocomplete test 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project1 = pm.environment.get(\"projectId1\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", + " pm.expect(foundProject1).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "\r\n\r\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=Test", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "Test" + } + ] + } + }, + "response": [] + }, + { + "name": "Get autocomplete test 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project1 = pm.environment.get(\"projectId3\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", + " pm.expect(foundProject1).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "\r\n\r\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=DeX", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "DeX" + } + ] + } + }, + "response": [] } ] }, From 8747f73fa17864c157c489538da6cd2fdf165abd Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Wed, 14 Apr 2021 13:08:40 +0200 Subject: [PATCH 03/66] removed unnecessary lines which broke the unit tests --- Repositories/ProjectRepository.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 31a89dd9..ce159ae1 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -629,10 +629,6 @@ public async Task> GetLikedProjectsFromSimilarUser(int userId, int private void ParseJsonToESProjectFormatDTOList(IRestResponse restResponse, List esProjectFormats) { - if(restResponse.StatusCode != HttpStatusCode.OK) - { - return; - } JObject esProjects = JObject.Parse(restResponse.Content); JToken projects = esProjects.GetValue("hits")["hits"]; foreach(JToken project in projects) From 199e43ff27a8285f19555d8698b498ff06289db0 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Wed, 14 Apr 2021 13:30:24 +0200 Subject: [PATCH 04/66] refactored Postman autocomplete tests --- Postman/ElasticSearch/DeX_Elastic.postman_collection.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index 0e5aa9a9..c7ccc5da 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -1537,6 +1537,7 @@ "function findProject(jsonData, id) {\r", " for (var i = 0; i < jsonData.length; i++) {\r", " if (jsonData[i].id == id) {\r", + " \r", " return i;\r", " }\r", " }\r", @@ -1598,7 +1599,7 @@ "script": { "exec": [ "var jsonData = pm.response.json();\r", - "var project1 = pm.environment.get(\"projectId3\");\r", + "var project3 = pm.environment.get(\"projectId3\");\r", "\r", "function findProject(jsonData, id) {\r", " for (var i = 0; i < jsonData.length; i++) {\r", @@ -1610,8 +1611,8 @@ "}\r", "\r", "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", - " pm.expect(foundProject1).to.not.eql(-1);\r", + " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", + " pm.expect(foundProject).to.not.eql(-1);\r", "});\r", "\r", "" From e43993dd3ca3f6dd3552efcdcc15f609e575c2c8 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Mon, 26 Apr 2021 14:31:55 +0200 Subject: [PATCH 05/66] merge develop --- .../DeX_Elastic.postman_collection.json | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index c7ccc5da..ad14d656 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "8cc3f0c7-3271-4261-9950-950fefbc2d3d", + "_postman_id": "35c11209-a7f9-4792-a704-efbef8cf5b4f", "name": "DeX_Elastic", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -8,6 +8,60 @@ { "name": "Warmup", "item": [ + { + "name": "Setup Elastic", + "item": [ + { + "name": "Migrate to Elastic", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "pm.environment.set(\"userId1\", jsonData.id)" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "IdentityId", + "value": "{{administratorUserIdentityId}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/export", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "export" + ] + } + }, + "response": [] + } + ] + }, { "name": "Register users", "item": [ @@ -1571,7 +1625,7 @@ } }, "url": { - "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=Test", + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=te", "host": [ "{{apiUrl}}" ], @@ -1584,7 +1638,7 @@ "query": [ { "key": "query", - "value": "Test" + "value": "te" } ] } @@ -2077,7 +2131,7 @@ "response": [] }, { - "name": "Delete-RegisteredUser2 Copy", + "name": "Delete-RegisteredUser3", "request": { "method": "DELETE", "header": [ From f3aca16d643e1d7e18d8184716eaaaef377a256a Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Mon, 26 Apr 2021 16:00:52 +0200 Subject: [PATCH 06/66] Added missing XML summaries to endpoint and removed unnecessary authorization for Postman requests which caused "error: Socket hang up". --- API/Controllers/ProjectController.cs | 7 +- .../DeX_Elastic.postman_collection.json | 277 +++++++++--------- 2 files changed, 150 insertions(+), 134 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 661b0d22..b45d5c06 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -239,7 +239,12 @@ await projectService.GetProjectsTotalPages( return Ok(resultsResource); } - + /// + /// This method returns suggestions while searching for projects + /// + /// + /// This method returns a list of autocomplete project resources. + /// This endpoint returns a list with suggested projects. [HttpGet("search/autocomplete")] [ProducesResponseType(typeof(AutocompleteProjectsResource), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index ad14d656..0b0eb3c4 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -545,6 +545,150 @@ } ] }, + { + "name": "Autocomplete tests", + "item": [ + { + "name": "Get autocomplete test 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project1 = pm.environment.get(\"projectId1\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " \r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", + " pm.expect(foundProject1).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=te", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "te" + } + ] + } + }, + "response": [] + }, + { + "name": "Get autocomplete test 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project3 = pm.environment.get(\"projectId3\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", + " pm.expect(foundProject).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=De", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "De" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "TestCase1", "item": [ @@ -1577,139 +1721,6 @@ } }, "response": [] - }, - { - "name": "Get autocomplete test 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "var jsonData = pm.response.json();\r", - "var project1 = pm.environment.get(\"projectId1\");\r", - "\r", - "function findProject(jsonData, id) {\r", - " for (var i = 0; i < jsonData.length; i++) {\r", - " if (jsonData[i].id == id) {\r", - " \r", - " return i;\r", - " }\r", - " }\r", - " return -1;\r", - "}\r", - "\r", - "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", - " pm.expect(foundProject1).to.not.eql(-1);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "\r\n\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=te", - "host": [ - "{{apiUrl}}" - ], - "path": [ - "api", - "Project", - "search", - "autocomplete" - ], - "query": [ - { - "key": "query", - "value": "te" - } - ] - } - }, - "response": [] - }, - { - "name": "Get autocomplete test 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "var jsonData = pm.response.json();\r", - "var project3 = pm.environment.get(\"projectId3\");\r", - "\r", - "function findProject(jsonData, id) {\r", - " for (var i = 0; i < jsonData.length; i++) {\r", - " if (jsonData[i].id == id) {\r", - " return i;\r", - " }\r", - " }\r", - " return -1;\r", - "}\r", - "\r", - "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", - " pm.expect(foundProject).to.not.eql(-1);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "\r\n\r\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=DeX", - "host": [ - "{{apiUrl}}" - ], - "path": [ - "api", - "Project", - "search", - "autocomplete" - ], - "query": [ - { - "key": "query", - "value": "DeX" - } - ] - } - }, - "response": [] } ] }, From 397d36a9cb88a8d2110ddc9572047a2398705e7b Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Wed, 28 Apr 2021 15:02:27 +0200 Subject: [PATCH 07/66] refactor order of Elastic tests --- .../DeX_Elastic.postman_collection.json | 288 +++++++++--------- 1 file changed, 144 insertions(+), 144 deletions(-) diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index 0b0eb3c4..4a598f1e 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -545,150 +545,6 @@ } ] }, - { - "name": "Autocomplete tests", - "item": [ - { - "name": "Get autocomplete test 1", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "var jsonData = pm.response.json();\r", - "var project1 = pm.environment.get(\"projectId1\");\r", - "\r", - "function findProject(jsonData, id) {\r", - " for (var i = 0; i < jsonData.length; i++) {\r", - " if (jsonData[i].id == id) {\r", - " \r", - " return i;\r", - " }\r", - " }\r", - " return -1;\r", - "}\r", - "\r", - "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", - " pm.expect(foundProject1).to.not.eql(-1);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=te", - "host": [ - "{{apiUrl}}" - ], - "path": [ - "api", - "Project", - "search", - "autocomplete" - ], - "query": [ - { - "key": "query", - "value": "te" - } - ] - } - }, - "response": [] - }, - { - "name": "Get autocomplete test 2", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "var jsonData = pm.response.json();\r", - "var project3 = pm.environment.get(\"projectId3\");\r", - "\r", - "function findProject(jsonData, id) {\r", - " for (var i = 0; i < jsonData.length; i++) {\r", - " if (jsonData[i].id == id) {\r", - " return i;\r", - " }\r", - " }\r", - " return -1;\r", - "}\r", - "\r", - "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", - " pm.expect(foundProject).to.not.eql(-1);\r", - "});\r", - "\r", - "" - ], - "type": "text/javascript" - } - } - ], - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "auth": { - "type": "noauth" - }, - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=De", - "host": [ - "{{apiUrl}}" - ], - "path": [ - "api", - "Project", - "search", - "autocomplete" - ], - "query": [ - { - "key": "query", - "value": "De" - } - ] - } - }, - "response": [] - } - ] - }, { "name": "TestCase1", "item": [ @@ -2067,6 +1923,150 @@ } ] }, + { + "name": "Autocomplete tests", + "item": [ + { + "name": "Get autocomplete test 1", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project1 = pm.environment.get(\"projectId1\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " \r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", + " pm.expect(foundProject1).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=te", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "te" + } + ] + } + }, + "response": [] + }, + { + "name": "Get autocomplete test 2", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var jsonData = pm.response.json();\r", + "var project3 = pm.environment.get(\"projectId3\");\r", + "\r", + "function findProject(jsonData, id) {\r", + " for (var i = 0; i < jsonData.length; i++) {\r", + " if (jsonData[i].id == id) {\r", + " return i;\r", + " }\r", + " }\r", + " return -1;\r", + "}\r", + "\r", + "pm.test(\"Expected projects are suggested: \", function() {\r", + " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", + " pm.expect(foundProject).to.not.eql(-1);\r", + "});\r", + "\r", + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{apiUrl}}/api/Project/search/autocomplete?query=De", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "Project", + "search", + "autocomplete" + ], + "query": [ + { + "key": "query", + "value": "De" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "Cleanup", "item": [ From 5b5693af8e324663e4c9d722753ff262dd2ed5d8 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Wed, 28 Apr 2021 15:16:24 +0200 Subject: [PATCH 08/66] fixed warning in ElasticSynchronizer background services --- ElasticSynchronizer/Workers/DeleteProjectWorker.cs | 4 ++-- ElasticSynchronizer/Workers/UpdateProjectWorker.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ElasticSynchronizer/Workers/DeleteProjectWorker.cs b/ElasticSynchronizer/Workers/DeleteProjectWorker.cs index afa41c54..a5e8e0f1 100644 --- a/ElasticSynchronizer/Workers/DeleteProjectWorker.cs +++ b/ElasticSynchronizer/Workers/DeleteProjectWorker.cs @@ -46,7 +46,7 @@ public DeleteProjectWorker(ILogger logger, Config config, R this.restClient = restClient; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + protected override Task ExecuteAsync(CancellationToken stoppingToken) { RabbitMQSubscriber subscriber = new RabbitMQSubscriber( new RabbitMQConnectionFactory(config.RabbitMQ.Hostname, @@ -59,8 +59,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) EventingBasicConsumer consumer = listener.CreateConsumer(documentDeleterService); listener.StartConsumer(consumer, subject); + return Task.CompletedTask; } - } } diff --git a/ElasticSynchronizer/Workers/UpdateProjectWorker.cs b/ElasticSynchronizer/Workers/UpdateProjectWorker.cs index 5ba1f213..a65d1a24 100644 --- a/ElasticSynchronizer/Workers/UpdateProjectWorker.cs +++ b/ElasticSynchronizer/Workers/UpdateProjectWorker.cs @@ -45,7 +45,7 @@ public UpdateProjectWorker(ILogger logger, Config config, R this.restClient = restClient; } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + protected override Task ExecuteAsync(CancellationToken stoppingToken) { RabbitMQSubscriber subscriber = new RabbitMQSubscriber( new RabbitMQConnectionFactory(config.RabbitMQ.Hostname, @@ -59,8 +59,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) EventingBasicConsumer consumer = listener.CreateConsumer(documentUpdaterService); listener.StartConsumer(consumer, subject); + return Task.CompletedTask; } - } } From 4394bff89845656baf51c0e952837264b914322e Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Thu, 29 Apr 2021 12:16:35 +0200 Subject: [PATCH 09/66] refactored requested changes --- API/Configuration/MappingProfile.cs | 4 ++-- API/Controllers/ProjectController.cs | 11 ++++------- ...cs => AutocompleteProjectResourceResult.cs} | 2 +- API/Resources/AutocompleteProjectsResource.cs | 18 ------------------ Repositories/ProjectRepository.cs | 4 ++-- 5 files changed, 9 insertions(+), 30 deletions(-) rename API/Resources/{AutocompleteProjectResource.cs => AutocompleteProjectResourceResult.cs} (93%) delete mode 100644 API/Resources/AutocompleteProjectsResource.cs diff --git a/API/Configuration/MappingProfile.cs b/API/Configuration/MappingProfile.cs index 5f75b0b3..0b4ba6cd 100644 --- a/API/Configuration/MappingProfile.cs +++ b/API/Configuration/MappingProfile.cs @@ -36,8 +36,8 @@ public class MappingProfile : Profile /// public MappingProfile() { - CreateMap(); - CreateMap(); + CreateMap(); + CreateMap(); CreateMap() .ForMember(source => source.Id, diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index b45d5c06..44781bca 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -246,16 +246,13 @@ await projectService.GetProjectsTotalPages( /// This method returns a list of autocomplete project resources. /// This endpoint returns a list with suggested projects. [HttpGet("search/autocomplete")] - [ProducesResponseType(typeof(AutocompleteProjectsResource), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] public async Task GetAutoCompleteProjects([FromQuery(Name ="query")] string query) { List projects = await projectService.FindProjectsWhereTitleStartsWithQuery(query); - AutocompleteProjectsResource resultResource = new AutocompleteProjectsResource(); - List autocompleteProjectResources = mapper.Map, List>(projects); - resultResource.AutocompleteProjects = autocompleteProjectResources; - - return Ok(resultResource); + List autocompleteProjectResourceResults = mapper.Map, List>(projects); + + return Ok(autocompleteProjectResourceResults); } diff --git a/API/Resources/AutocompleteProjectResource.cs b/API/Resources/AutocompleteProjectResourceResult.cs similarity index 93% rename from API/Resources/AutocompleteProjectResource.cs rename to API/Resources/AutocompleteProjectResourceResult.cs index 2a0dce03..44dc0295 100644 --- a/API/Resources/AutocompleteProjectResource.cs +++ b/API/Resources/AutocompleteProjectResourceResult.cs @@ -9,7 +9,7 @@ namespace API.Resources /// /// The view model of a single autocompleted search project. /// - public class AutocompleteProjectResource + public class AutocompleteProjectResourceResult { /// /// Gets or sets the Id of the autocompleted search project. diff --git a/API/Resources/AutocompleteProjectsResource.cs b/API/Resources/AutocompleteProjectsResource.cs deleted file mode 100644 index b49ef399..00000000 --- a/API/Resources/AutocompleteProjectsResource.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace API.Resources -{ - /// - /// The viewmodel that is returned by the Autocomplete search route in the Project controller. - /// - public class AutocompleteProjectsResource - { - /// - /// The List of Autocompleted search projects. - /// - public List AutocompleteProjects { get; set; } - } -} diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 8470cdaa..28a66187 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -124,7 +124,7 @@ Task> SearchAsync( void MigrateDatabase(List projectsToExport); /// - /// This method will call the ElasticSearch index to retreive projects where the title starts with the query. + /// This method will call the ElasticSearch index to retrieve projects where the title starts with the query. /// /// The string of characters with which the title must begin /// @@ -707,7 +707,7 @@ public async Task SyncProjectToES(Project project) } /// - /// This method will call the ElasticSearch index to retreive projects where the title starts with the query. + /// This method will call the ElasticSearch index to retrieve projects where the title starts with the query. /// /// The string of characters with which the title must begin /// From 71fae8c535fe11253c011fdeb9ab278cf5bfd388 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Thu, 29 Apr 2021 12:41:51 +0200 Subject: [PATCH 10/66] refactored Postman collection after refactoring the endpoint --- Postman/ElasticSearch/DeX_Elastic.postman_collection.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index 4a598f1e..f844ddc5 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -1947,7 +1947,7 @@ "}\r", "\r", "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject1 = findProject(jsonData.autocompleteProjects, project1);\r", + " foundProject1 = findProject(jsonData, project1);\r", " pm.expect(foundProject1).to.not.eql(-1);\r", "});\r", "\r", @@ -2016,7 +2016,7 @@ "}\r", "\r", "pm.test(\"Expected projects are suggested: \", function() {\r", - " foundProject = findProject(jsonData.autocompleteProjects, project3);\r", + " foundProject = findProject(jsonData, project3);\r", " pm.expect(foundProject).to.not.eql(-1);\r", "});\r", "\r", From c4967c0c0ba4f071878fa59c2e919814cb20fe13 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 30 Apr 2021 15:24:34 +0200 Subject: [PATCH 11/66] Resolved categories migration conflict --- ...0210430132046_AddedCategories.Designer.cs} | 111 +++++++++++++++--- ...s.cs => 20210430132046_AddedCategories.cs} | 33 +++++- .../ApplicationDbContextModelSnapshot.cs | 70 ++++++++--- 3 files changed, 174 insertions(+), 40 deletions(-) rename Data/Migrations/{20210224135630_AddCategories.Designer.cs => 20210430132046_AddedCategories.Designer.cs} (88%) rename Data/Migrations/{20210224135630_AddCategories.cs => 20210430132046_AddedCategories.cs} (54%) diff --git a/Data/Migrations/20210224135630_AddCategories.Designer.cs b/Data/Migrations/20210430132046_AddedCategories.Designer.cs similarity index 88% rename from Data/Migrations/20210224135630_AddCategories.Designer.cs rename to Data/Migrations/20210430132046_AddedCategories.Designer.cs index 25fdebc6..700f5be1 100644 --- a/Data/Migrations/20210224135630_AddCategories.Designer.cs +++ b/Data/Migrations/20210430132046_AddedCategories.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Data; using Microsoft.EntityFrameworkCore; @@ -10,8 +10,8 @@ namespace _4_Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210224135630_AddCategories")] - partial class AddCategories + [Migration("20210430132046_AddedCategories")] + partial class AddedCategories { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -61,6 +61,22 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("CallToActionOption"); }); + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + modelBuilder.Entity("Models.Collaborator", b => { b.Property("Id") @@ -285,6 +301,50 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectCategory"); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectInstitution"); + }); + modelBuilder.Entity("Models.ProjectLike", b => { b.Property("Id") @@ -345,21 +405,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); - modelBuilder.Entity("Models.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Category"); - }); - modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -578,6 +623,36 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.HasOne("Models.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("Categories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("LinkedInstitutions") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Models.ProjectLike", b => { b.HasOne("Models.Project", "LikedProject") diff --git a/Data/Migrations/20210224135630_AddCategories.cs b/Data/Migrations/20210430132046_AddedCategories.cs similarity index 54% rename from Data/Migrations/20210224135630_AddCategories.cs rename to Data/Migrations/20210430132046_AddedCategories.cs index 4dca7c85..34f8d1ae 100644 --- a/Data/Migrations/20210224135630_AddCategories.cs +++ b/Data/Migrations/20210430132046_AddedCategories.cs @@ -1,9 +1,8 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace _4_Data.Migrations { - public partial class AddCategories : Migration + public partial class AddedCategories : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -13,7 +12,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(nullable: true) + Name = table.Column(nullable: false) }, constraints: table => { @@ -32,16 +31,38 @@ protected override void Up(MigrationBuilder migrationBuilder) constraints: table => { table.PrimaryKey("PK_ProjectCategory", x => x.Id); + table.ForeignKey( + name: "FK_ProjectCategory_Category_CategoryId", + column: x => x.CategoryId, + principalTable: "Category", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectCategory_Project_ProjectId", + column: x => x.ProjectId, + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); }); + + migrationBuilder.CreateIndex( + name: "IX_ProjectCategory_CategoryId", + table: "ProjectCategory", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectCategory_ProjectId", + table: "ProjectCategory", + column: "ProjectId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Category"); + name: "ProjectCategory"); migrationBuilder.DropTable( - name: "ProjectCategory"); + name: "Category"); } } } diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 7f6abc55..ffde2a31 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Data; using Microsoft.EntityFrameworkCore; @@ -59,6 +59,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CallToActionOption"); }); + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + modelBuilder.Entity("Models.Collaborator", b => { b.Property("Id") @@ -283,6 +299,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectCategory"); + }); + modelBuilder.Entity("Models.ProjectInstitution", b => { b.Property("Id") @@ -365,21 +403,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); - modelBuilder.Entity("Models.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("Category"); - }); - modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -598,6 +621,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.HasOne("Models.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("Categories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Models.ProjectInstitution", b => { b.HasOne("Models.Institution", "Institution") From a971bd36ad925bf86d208f2b2d40c848eae0e694 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 30 Apr 2021 15:38:43 +0200 Subject: [PATCH 12/66] Revert "Resolved categories migration conflict" This reverts commit c4967c0c0ba4f071878fa59c2e919814cb20fe13. --- ... 20210224135630_AddCategories.Designer.cs} | 111 +++--------------- ...ies.cs => 20210224135630_AddCategories.cs} | 33 +----- .../ApplicationDbContextModelSnapshot.cs | 70 +++-------- 3 files changed, 40 insertions(+), 174 deletions(-) rename Data/Migrations/{20210430132046_AddedCategories.Designer.cs => 20210224135630_AddCategories.Designer.cs} (88%) rename Data/Migrations/{20210430132046_AddedCategories.cs => 20210224135630_AddCategories.cs} (54%) diff --git a/Data/Migrations/20210430132046_AddedCategories.Designer.cs b/Data/Migrations/20210224135630_AddCategories.Designer.cs similarity index 88% rename from Data/Migrations/20210430132046_AddedCategories.Designer.cs rename to Data/Migrations/20210224135630_AddCategories.Designer.cs index 700f5be1..25fdebc6 100644 --- a/Data/Migrations/20210430132046_AddedCategories.Designer.cs +++ b/Data/Migrations/20210224135630_AddCategories.Designer.cs @@ -1,4 +1,4 @@ -// +// using System; using Data; using Microsoft.EntityFrameworkCore; @@ -10,8 +10,8 @@ namespace _4_Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210430132046_AddedCategories")] - partial class AddedCategories + [Migration("20210224135630_AddCategories")] + partial class AddCategories { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -61,22 +61,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("CallToActionOption"); }); - modelBuilder.Entity("Models.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Category"); - }); - modelBuilder.Entity("Models.Collaborator", b => { b.Property("Id") @@ -301,50 +285,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); - modelBuilder.Entity("Models.ProjectCategory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CategoryId") - .HasColumnType("int"); - - b.Property("ProjectId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectCategory"); - }); - - modelBuilder.Entity("Models.ProjectInstitution", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("InstitutionId") - .HasColumnType("int"); - - b.Property("ProjectId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("InstitutionId"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectInstitution"); - }); - modelBuilder.Entity("Models.ProjectLike", b => { b.Property("Id") @@ -405,6 +345,21 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -623,36 +578,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired(); }); - modelBuilder.Entity("Models.ProjectCategory", b => - { - b.HasOne("Models.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Models.Project", "Project") - .WithMany("Categories") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Models.ProjectInstitution", b => - { - b.HasOne("Models.Institution", "Institution") - .WithMany() - .HasForeignKey("InstitutionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Models.Project", "Project") - .WithMany("LinkedInstitutions") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Models.ProjectLike", b => { b.HasOne("Models.Project", "LikedProject") diff --git a/Data/Migrations/20210430132046_AddedCategories.cs b/Data/Migrations/20210224135630_AddCategories.cs similarity index 54% rename from Data/Migrations/20210430132046_AddedCategories.cs rename to Data/Migrations/20210224135630_AddCategories.cs index 34f8d1ae..4dca7c85 100644 --- a/Data/Migrations/20210430132046_AddedCategories.cs +++ b/Data/Migrations/20210224135630_AddCategories.cs @@ -1,8 +1,9 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; namespace _4_Data.Migrations { - public partial class AddedCategories : Migration + public partial class AddCategories : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -12,7 +13,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(nullable: false) .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(nullable: false) + Name = table.Column(nullable: true) }, constraints: table => { @@ -31,38 +32,16 @@ protected override void Up(MigrationBuilder migrationBuilder) constraints: table => { table.PrimaryKey("PK_ProjectCategory", x => x.Id); - table.ForeignKey( - name: "FK_ProjectCategory_Category_CategoryId", - column: x => x.CategoryId, - principalTable: "Category", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_ProjectCategory_Project_ProjectId", - column: x => x.ProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); }); - - migrationBuilder.CreateIndex( - name: "IX_ProjectCategory_CategoryId", - table: "ProjectCategory", - column: "CategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_ProjectCategory_ProjectId", - table: "ProjectCategory", - column: "ProjectId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "ProjectCategory"); + name: "Category"); migrationBuilder.DropTable( - name: "Category"); + name: "ProjectCategory"); } } } diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index ffde2a31..7f6abc55 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Data; using Microsoft.EntityFrameworkCore; @@ -59,22 +59,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CallToActionOption"); }); - modelBuilder.Entity("Models.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Category"); - }); - modelBuilder.Entity("Models.Collaborator", b => { b.Property("Id") @@ -299,28 +283,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); - modelBuilder.Entity("Models.ProjectCategory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("CategoryId") - .HasColumnType("int"); - - b.Property("ProjectId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectCategory"); - }); - modelBuilder.Entity("Models.ProjectInstitution", b => { b.Property("Id") @@ -403,6 +365,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -621,21 +598,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); - modelBuilder.Entity("Models.ProjectCategory", b => - { - b.HasOne("Models.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Models.Project", "Project") - .WithMany("Categories") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Models.ProjectInstitution", b => { b.HasOne("Models.Institution", "Institution") From 4513c6c5223e1aac71ee66e78fcb3ba5a1a74fc4 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 30 Apr 2021 15:46:24 +0200 Subject: [PATCH 13/66] Resolved migration conflict --- ...0430134605_AddedHighlightImage.Designer.cs | 733 ++++++++++++++++++ .../20210430134605_AddedHighlightImage.cs | 43 + .../ApplicationDbContextModelSnapshot.cs | 79 +- Models/Highlight.cs | 2 + 4 files changed, 841 insertions(+), 16 deletions(-) create mode 100644 Data/Migrations/20210430134605_AddedHighlightImage.Designer.cs create mode 100644 Data/Migrations/20210430134605_AddedHighlightImage.cs diff --git a/Data/Migrations/20210430134605_AddedHighlightImage.Designer.cs b/Data/Migrations/20210430134605_AddedHighlightImage.Designer.cs new file mode 100644 index 00000000..e22dccf4 --- /dev/null +++ b/Data/Migrations/20210430134605_AddedHighlightImage.Designer.cs @@ -0,0 +1,733 @@ +// +using System; +using Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace _4_Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20210430134605_AddedHighlightImage")] + partial class AddedHighlightImage + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Collaborators"); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Guid") + .HasColumnType("nvarchar(max)"); + + b.Property("IconId") + .HasColumnType("int"); + + b.Property("IsVisible") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IconId"); + + b.ToTable("DataSource"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.HasKey("DataSourceId", "WizardPageId", "AuthFlow"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutePrivate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectCategory"); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectInstitution"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccountCreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedGraduationDate") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProfileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("RoleId"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProject"); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTask"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.File", "Image") + .WithMany() + .HasForeignKey("ImageId"); + + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.HasOne("Models.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("Categories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("LinkedInstitutions") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.HasOne("Models.User", "User") + .WithMany("UserTasks") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.HasOne("Models.User", "FollowedUser") + .WithMany("FollowedUsers") + .HasForeignKey("FollowedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20210430134605_AddedHighlightImage.cs b/Data/Migrations/20210430134605_AddedHighlightImage.cs new file mode 100644 index 00000000..ff4377b5 --- /dev/null +++ b/Data/Migrations/20210430134605_AddedHighlightImage.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class AddedHighlightImage : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImageId", + table: "Highlight", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Highlight_ImageId", + table: "Highlight", + column: "ImageId"); + + migrationBuilder.AddForeignKey( + name: "FK_Highlight_File_ImageId", + table: "Highlight", + column: "ImageId", + principalTable: "File", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Highlight_File_ImageId", + table: "Highlight"); + + migrationBuilder.DropIndex( + name: "IX_Highlight_ImageId", + table: "Highlight"); + + migrationBuilder.DropColumn( + name: "ImageId", + table: "Highlight"); + } + } +} diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 7f6abc55..10a11782 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,4 +1,4 @@ -// +// using System; using Data; using Microsoft.EntityFrameworkCore; @@ -59,6 +59,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CallToActionOption"); }); + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + modelBuilder.Entity("Models.Collaborator", b => { b.Property("Id") @@ -198,6 +214,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("EndDate") .HasColumnType("datetime2"); + b.Property("ImageId") + .HasColumnType("int"); + b.Property("ProjectId") .HasColumnType("int"); @@ -206,6 +225,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("ImageId"); + b.HasIndex("ProjectId"); b.ToTable("Highlight"); @@ -283,6 +304,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectCategory"); + }); + modelBuilder.Entity("Models.ProjectInstitution", b => { b.Property("Id") @@ -365,21 +408,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); - modelBuilder.Entity("Models.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(100)"); - - b.HasKey("Id"); - - b.ToTable("Category"); - }); - modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -574,6 +602,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Models.Highlight", b => { + b.HasOne("Models.File", "Image") + .WithMany() + .HasForeignKey("ImageId"); + b.HasOne("Models.Project", "Project") .WithMany() .HasForeignKey("ProjectId") @@ -598,6 +630,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.HasOne("Models.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("Categories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("Models.ProjectInstitution", b => { b.HasOne("Models.Institution", "Institution") diff --git a/Models/Highlight.cs b/Models/Highlight.cs index fd2f145d..e6b42048 100644 --- a/Models/Highlight.cs +++ b/Models/Highlight.cs @@ -39,6 +39,8 @@ public class Highlight public DateTime? EndDate { get; set; } + public File Image { get; set; } + } } From ccc061073ddacb2924864745e8c74fde908104dc Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 30 Apr 2021 15:47:57 +0200 Subject: [PATCH 14/66] Added highlight image and updated resources --- API/Resources/HighlightResource.cs | 6 ++++++ API/Resources/HighlightResourceResult.cs | 6 ++++++ Repositories/HighlightRepository.cs | 3 +++ 3 files changed, 15 insertions(+) diff --git a/API/Resources/HighlightResource.cs b/API/Resources/HighlightResource.cs index 99fcee71..b8258cbe 100644 --- a/API/Resources/HighlightResource.cs +++ b/API/Resources/HighlightResource.cs @@ -16,6 +16,7 @@ */ using System; +using System.IO; namespace API.Resources { @@ -46,6 +47,11 @@ public class HighlightResource /// public DateTime? EndDate { get; set; } + /// + /// This gets or sets the image id of the project that this highlight is associated with + /// + public int ImageId { get; set; } + } } diff --git a/API/Resources/HighlightResourceResult.cs b/API/Resources/HighlightResourceResult.cs index 084bed53..96344626 100644 --- a/API/Resources/HighlightResourceResult.cs +++ b/API/Resources/HighlightResourceResult.cs @@ -15,6 +15,7 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using Models; using System; namespace API.Resources @@ -56,6 +57,11 @@ public class HighlightResourceResult /// public DateTime? EndDate { get; set; } + /// + /// This gets or sets the image of the project that this highlight is associated with + /// + public FileResourceResult Image { get; set; } + } } diff --git a/Repositories/HighlightRepository.cs b/Repositories/HighlightRepository.cs index 6d9f2159..cca0b750 100644 --- a/Repositories/HighlightRepository.cs +++ b/Repositories/HighlightRepository.cs @@ -71,6 +71,7 @@ public override async Task FindAsync(int id) .Where(s => s.Id == id) .Include(p => p.Project) .Include(p => p.Project.ProjectIcon) + .Include(p => p.Image) .SingleOrDefaultAsync(); return RedactUser(project); @@ -87,6 +88,7 @@ public async Task> GetHighlightsAsync() .Where(s => s.EndDate >= DateTime.Now || s.EndDate == null) .Include(p => p.Project) .Include(p => p.Project.ProjectIcon) + .Include(p => p.Image) .ToListAsync(); return RedactUser(highlights); } @@ -102,6 +104,7 @@ public async Task> GetHighlightsByProjectIdAsync(int projectId) .Where(s => s.ProjectId == projectId) .Include(p => p.Project) .Include(p => p.Project.ProjectIcon) + .Include(p => p.Image) .ToListAsync(); } From 20ceeead21534399ea90263f68467a1960138589 Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 13:24:48 +0200 Subject: [PATCH 15/66] Removed project id form HighlightResourceResult and included it in ProjectHighlightResourceResult --- API/Resources/HighlightResourceResult.cs | 5 ----- API/Resources/ProjectHighlightResourceResult.cs | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/API/Resources/HighlightResourceResult.cs b/API/Resources/HighlightResourceResult.cs index 96344626..a743bbf4 100644 --- a/API/Resources/HighlightResourceResult.cs +++ b/API/Resources/HighlightResourceResult.cs @@ -32,11 +32,6 @@ public class HighlightResourceResult /// public int Id { get; set; } - /// - /// This gets or sets the id of the project that this highlight is associated with - /// - public int ProjectId { get; set; } - /// /// This gets or sets the description of the project that this highlight is associated with /// diff --git a/API/Resources/ProjectHighlightResourceResult.cs b/API/Resources/ProjectHighlightResourceResult.cs index b899e874..f6f9e8bc 100644 --- a/API/Resources/ProjectHighlightResourceResult.cs +++ b/API/Resources/ProjectHighlightResourceResult.cs @@ -25,6 +25,11 @@ namespace API.Resources public class ProjectHighlightResourceResult { + /// + /// This gets or sets the Id + /// + public int Id { get; set; } + /// /// This gets or sets the Name /// From e79b03fa658070863de1a79b1213a5abd8488132 Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 13:24:59 +0200 Subject: [PATCH 16/66] Added Image Id property in Highlight model --- Models/Highlight.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Models/Highlight.cs b/Models/Highlight.cs index e6b42048..5c866af2 100644 --- a/Models/Highlight.cs +++ b/Models/Highlight.cs @@ -41,6 +41,8 @@ public class Highlight public File Image { get; set; } + public int? ImageId { get; set; } + } } From d4492369e29434fe5109fb435836cc12e2961710 Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 13:25:25 +0200 Subject: [PATCH 17/66] Updated ImageId to nullable int in HighlightResource --- API/Resources/HighlightResource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/Resources/HighlightResource.cs b/API/Resources/HighlightResource.cs index b8258cbe..0c19aa90 100644 --- a/API/Resources/HighlightResource.cs +++ b/API/Resources/HighlightResource.cs @@ -50,7 +50,7 @@ public class HighlightResource /// /// This gets or sets the image id of the project that this highlight is associated with /// - public int ImageId { get; set; } + public int? ImageId { get; set; } } From 45e8db2ca64f973917cbbabaa517bf0908ffa37d Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 13:26:17 +0200 Subject: [PATCH 18/66] Added checks on existence of Project and File & returned whole object instead of id in Highlight create endpoint --- API/Controllers/HighlightController.cs | 42 ++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/API/Controllers/HighlightController.cs b/API/Controllers/HighlightController.cs index 7d102f6c..84eb049a 100644 --- a/API/Controllers/HighlightController.cs +++ b/API/Controllers/HighlightController.cs @@ -43,16 +43,22 @@ public class HighlightController : ControllerBase private readonly IHighlightService highlightService; private readonly IMapper mapper; + private readonly IFileService fileService; + private readonly IProjectService projectService; /// /// Initializes a new instance of the class /// /// The highlight service which is used to communicate with the logic layer. /// The mapper which is used to convert the resources to the models to resource results. - public HighlightController(IHighlightService highlightService, IMapper mapper) + /// The file service which is used to communicate with the logic layer. + /// The project service which is used to communicate with the logic layer. + public HighlightController(IHighlightService highlightService, IMapper mapper, IFileService fileService, IProjectService projectService) { this.highlightService = highlightService; this.mapper = mapper; + this.fileService = fileService; + this.projectService = projectService; } /// @@ -165,7 +171,7 @@ public async Task GetHighlightsByProjectId(int projectId) [Authorize(Policy = nameof(Defaults.Scopes.HighlightWrite))] [ProducesResponseType(typeof(HighlightResourceResult), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] - public IActionResult CreateHighlight(HighlightResource highlightResource) + public async Task CreateHighlight(HighlightResource highlightResource) { if(highlightResource == null) { @@ -179,6 +185,38 @@ public IActionResult CreateHighlight(HighlightResource highlightResource) } Highlight highlight = mapper.Map(highlightResource); + File file = null; + + if(highlightResource.ImageId != null) + { + file = await fileService.FindAsync(highlightResource.ImageId.Value); + if(file == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "File was not found.", + Detail = "The specified file was not found while creating highlight.", + Instance = "F4AF3D06-DD74-40E0-99BB-8E1183A6A734" + }; + return BadRequest(problem); + } + } + + Project project = await projectService.FindWithUserCollaboratorsAndInstitutionsAsync(highlightResource.ProjectId); + + if(project == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Project was not found.", + Detail = "The specified project was not found while creating highlight.", + Instance = "A6DBEE27-0363-479B-B099-A467D4B2CF00" + }; + return BadRequest(problem); + } + + highlight.Image = file; + highlight.Project = project; try { From 058c8c5abfa2e9e88f0a1c015e6d2d4d770b47be Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 13:41:46 +0200 Subject: [PATCH 19/66] Added extra existence checks for the update endpoint for highlights --- API/Controllers/HighlightController.cs | 60 +++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/API/Controllers/HighlightController.cs b/API/Controllers/HighlightController.cs index 84eb049a..9cf69fb4 100644 --- a/API/Controllers/HighlightController.cs +++ b/API/Controllers/HighlightController.cs @@ -24,6 +24,7 @@ using Models.Defaults; using Serilog; using Services.Services; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -171,6 +172,7 @@ public async Task GetHighlightsByProjectId(int projectId) [Authorize(Policy = nameof(Defaults.Scopes.HighlightWrite))] [ProducesResponseType(typeof(HighlightResourceResult), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task CreateHighlight(HighlightResource highlightResource) { if(highlightResource == null) @@ -198,7 +200,7 @@ public async Task CreateHighlight(HighlightResource highlightReso Detail = "The specified file was not found while creating highlight.", Instance = "F4AF3D06-DD74-40E0-99BB-8E1183A6A734" }; - return BadRequest(problem); + return NotFound(problem); } } @@ -212,7 +214,7 @@ public async Task CreateHighlight(HighlightResource highlightReso Detail = "The specified project was not found while creating highlight.", Instance = "A6DBEE27-0363-479B-B099-A467D4B2CF00" }; - return BadRequest(problem); + return NotFound(problem); } highlight.Image = file; @@ -252,6 +254,7 @@ public async Task CreateHighlight(HighlightResource highlightReso [Authorize(Policy = nameof(Defaults.Scopes.HighlightWrite))] [ProducesResponseType(typeof(HighlightResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] public async Task UpdateHighlight(int highlightId, [FromBody] HighlightResource highlightResource) { @@ -269,10 +272,57 @@ public async Task UpdateHighlight(int highlightId, mapper.Map(highlightResource, highlight); - highlightService.Update(highlight); - highlightService.Save(); + File file = null; - return Ok(mapper.Map(highlight)); + if(highlightResource.ImageId != null) + { + file = await fileService.FindAsync(highlightResource.ImageId.Value); + if(file == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "File was not found.", + Detail = "The specified file was not found while creating highlight.", + Instance = "412CC9A8-CB2A-4389-9F9C-F8494AB65AE0" + }; + return NotFound(problem); + } + } + + Project project = await projectService.FindWithUserCollaboratorsAndInstitutionsAsync(highlightResource.ProjectId); + + if(project == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Project was not found.", + Detail = "The specified project was not found while creating highlight.", + Instance = "B923484E-D562-4F03-A57E-88D02819EBD6" + }; + return NotFound(problem); + } + + highlight.Image = file; + highlight.Project = project; + + try + { + highlightService.Update(highlight); + highlightService.Save(); + + return Ok(mapper.Map(highlight)); + } catch(DbUpdateException e) + { + Log.Logger.Error(e, "Database exception"); + + ProblemDetails problem = new ProblemDetails + { + Title = "Failed updating highlight.", + Detail = "Failed updating the highlight to the database.", + Instance = "D9933508-F7B6-44B1-9266-5B040D2EA07D" + }; + return BadRequest(problem); + } } /// From c871790c49846f6f44165fd214f8e08e357e7f6d Mon Sep 17 00:00:00 2001 From: Ruben Fricke Date: Sat, 1 May 2021 13:46:29 +0200 Subject: [PATCH 20/66] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98385ce9..6ccf9937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added image to highlights - [#431](https://github.com/DigitalExcellence/dex-backend/issues/431) + ### Changed ### Deprecated From 00c9a443a6c2f491b8183dff8441e9cc9139d0b6 Mon Sep 17 00:00:00 2001 From: Ruben Date: Sat, 1 May 2021 16:46:57 +0200 Subject: [PATCH 21/66] Converted project id check to inner id in project check --- Postman/dex.postman_collection.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Postman/dex.postman_collection.json b/Postman/dex.postman_collection.json index 7fb710b7..4a7333ba 100644 --- a/Postman/dex.postman_collection.json +++ b/Postman/dex.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "42128aa1-c53f-4bf3-b9b7-7b440dc1bba8", + "_postman_id": "a73f56b6-95d9-4dc6-a648-7ff12a4457a5", "name": "DEV", "description": "Testing Digital Excellence API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -2886,7 +2886,7 @@ "function findHighlight(jsonData, idToFind){", " for(var i = 0; i< jsonData.length; i++)", " {", - " if(jsonData[i].projectId == idToFind) ", + " if(jsonData[i].project.id == idToFind) ", " { ", " return i; ", " }", @@ -3022,7 +3022,7 @@ "", "function findProjectId(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -12963,7 +12963,7 @@ "", "function findItem(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -18743,7 +18743,7 @@ "", "function findItem(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -23734,7 +23734,7 @@ "", "function findProjectId(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -24013,7 +24013,7 @@ "", "function findProjectId(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -24152,7 +24152,7 @@ "", "function findItem(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", @@ -28586,7 +28586,7 @@ "", "function findItem(jsonData, item) {", " for (var i = 0; i < jsonData.length; i++) {", - " if (jsonData[i].projectId == item) {", + " if (jsonData[i].project.id == item) {", " return i;", " }", " }", From 69b6f55261ef1e997f82aadee03610ddd4b551d8 Mon Sep 17 00:00:00 2001 From: Niray Mak <323492@student.fontys.nl> Date: Tue, 4 May 2021 19:07:44 +0200 Subject: [PATCH 22/66] Endpoint logic catches errors now --- API/Controllers/ProjectController.cs | 13 ++++++++++--- Repositories/ProjectRepository.cs | 5 ++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 44781bca..0f653100 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -249,10 +249,17 @@ await projectService.GetProjectsTotalPages( [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] public async Task GetAutoCompleteProjects([FromQuery(Name ="query")] string query) { - List projects = await projectService.FindProjectsWhereTitleStartsWithQuery(query); - List autocompleteProjectResourceResults = mapper.Map, List>(projects); + try + { + List projects = await projectService.FindProjectsWhereTitleStartsWithQuery(query); + List autocompleteProjectResourceResults = mapper.Map, List>(projects); + return Ok(autocompleteProjectResourceResults); + } + catch(Exception e) + { + return BadRequest(e.Message); + } - return Ok(autocompleteProjectResourceResults); } diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 28a66187..dfb4853d 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -720,7 +720,10 @@ public async Task> FindProjectsWhereTitleStartsWithQuery(string qu .Replace("ReplaceWithQuery", query); request.AddParameter("application/json", body, ParameterType.RequestBody); IRestResponse restResponse = elasticSearchContext.Execute(request); - + if(!restResponse.IsSuccessful) + { + throw new Exception(restResponse.ErrorMessage); + } List esProjectFormats = new List(); ParseJsonToESProjectFormatDTOList(restResponse, esProjectFormats); From b6b4560868a5ee4e08bfa298f9b29a96e23765f2 Mon Sep 17 00:00:00 2001 From: timsn Date: Thu, 6 May 2021 21:44:40 +0200 Subject: [PATCH 23/66] Made the get all projects function better --- Repositories/ProjectRepository.cs | 67 +++++++++++++------------------ 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 8d16eee0..82eac28d 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -97,7 +97,7 @@ Task> SearchAsync( Task SyncProjectToES(Project project); - + /// /// This method will retrieve a project with user and collaborators async. Project will be redacted if user @@ -139,7 +139,8 @@ public class ProjectRepository : Repository, IProjectRepository public ProjectRepository(DbContext dbContext) : base(dbContext) { } - public ProjectRepository(DbContext dbContext, IElasticSearchContext elasticSearchContext, ITaskPublisher taskPublisher, Queries queries) : base(dbContext) { + public ProjectRepository(DbContext dbContext, IElasticSearchContext elasticSearchContext, ITaskPublisher taskPublisher, Queries queries) : base(dbContext) + { this.taskPublisher = taskPublisher; this.elasticSearchContext = elasticSearchContext.CreateRestClientForElasticRequests(); this.queries = queries; @@ -194,12 +195,15 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut bool? highlighted = null ) { - IQueryable queryableProjects = GetDbSet().Include(u => u.User); - queryableProjects.Include(p => p.ProjectIcon).Load(); - queryableProjects.Include(p => p.CallToAction).Load(); - queryableProjects.Include(p => p.Collaborators).Load(); - queryableProjects.Include(p => p.Likes).Load(); - queryableProjects.Include(p => p.LinkedInstitutions).Load(); + IQueryable queryableProjects = GetDbSet() + .Include(u => u.User) + .Include(p => p.ProjectIcon) + .Include(p => p.CallToAction) + .Include(p => p.Collaborators) + .Include(p => p.Likes) + .Include(p => p.LinkedInstitutions) + .Include(p => p.Categories) + .ThenInclude(c => c.Category); queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted); @@ -209,22 +213,7 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut //Redact the user after fetching the collection from the project (no separate query needs to be executed) projectResults.ForEach(project => project.User = RedactUser(project.User)); - // This is needed because otherwise likes, collaborators and categories are missing! - foreach(Project project in queryableProjects) - { - project.Collaborators = await GetDbSet() - .Where(p => p.ProjectId == project.Id) - .ToListAsync(); - project.User = RedactUser(project.User); - project.Likes = await GetDbSet() - .Where(p => p.LikedProject.Id == project.Id) - .ToListAsync(); - project.Categories = await GetDbSet() - .Include(p => p.Category) - .Where(p => p.Project.Id == project.Id) - .ToListAsync(); - } - return await queryableProjects.ToListAsync(); + return projectResults; } /// @@ -343,20 +332,20 @@ public override void Update(Project entity) SetLikes(entity); ESProjectDTO projectToSync = ProjectConverter.ProjectToESProjectDTO(entity); taskPublisher.RegisterTask(Newtonsoft.Json.JsonConvert.SerializeObject(projectToSync), Subject.ELASTIC_CREATE_OR_UPDATE); - + } private static void SetLikes(Project entity) { - if (entity != null) + if(entity != null) { - if (entity.Likes != null) + if(entity.Likes != null) { _ = entity.Likes; } - - } + + } } /// @@ -389,7 +378,7 @@ public override void AddRange(IEnumerable entities) taskPublisher.RegisterTask(Newtonsoft.Json.JsonConvert.SerializeObject(projectToSync), Subject.ELASTIC_CREATE_OR_UPDATE); }); - + } /// @@ -401,7 +390,7 @@ public override void AddRange(IEnumerable entities) /// public override async Task AddAsync(Project entity) { - + await base.AddAsync(entity); base.Save(); Console.WriteLine("AddAsync method id: " + entity.Id); @@ -599,12 +588,12 @@ private async Task> GetProjectQueryable(string query) p.Id.ToString() .Equals(query) || p.User.Name.Contains(query)); - projectsToReturn.Include(p => p.ProjectIcon).Load(); - projectsToReturn.Include(p => p.CallToAction).Load(); - projectsToReturn.Include(p => p.Likes).Load(); - projectsToReturn.Include(p => p.Categories).Load(); + projectsToReturn.Include(p => p.ProjectIcon).Load(); + projectsToReturn.Include(p => p.CallToAction).Load(); + projectsToReturn.Include(p => p.Likes).Load(); + projectsToReturn.Include(p => p.Categories).Load(); - foreach (Project project in projectsToReturn) + foreach(Project project in projectsToReturn) { project.Collaborators = await GetDbSet() .Where(p => p.ProjectId == project.Id) @@ -686,14 +675,14 @@ public void MigrateDatabase(List projectsToExport) public async Task SyncProjectToES(Project project) { Project projectToSync = await FindAsync(project.Id); - if (projectToSync == null) + if(projectToSync == null) { throw new NotFoundException("Project to sync was not found"); } ESProjectDTO eSProjectDTO = ProjectConverter.ProjectToESProjectDTO(projectToSync); taskPublisher.RegisterTask(Newtonsoft.Json.JsonConvert.SerializeObject(eSProjectDTO), Subject.ELASTIC_CREATE_OR_UPDATE); - - + + } public Task ProjectExistsAsync(int id) { From b63ed6f9d06ebe10d702af796eb0f0b27bec5e2e Mon Sep 17 00:00:00 2001 From: timsn Date: Thu, 6 May 2021 21:49:33 +0200 Subject: [PATCH 24/66] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98385ce9..a60301db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed ### Fixed - +- Fixed the get all projects query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) ### Security From e837cb7181a0fef45ecf16a3c361f0c0f69a3471 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Sat, 15 May 2021 15:21:04 +0200 Subject: [PATCH 25/66] autocomplete now throws problemdetails which the frontend can show --- API/Controllers/ProjectController.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 0f653100..2aa20947 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -255,11 +255,16 @@ public async Task GetAutoCompleteProjects([FromQuery(Name ="query List autocompleteProjectResourceResults = mapper.Map, List>(projects); return Ok(autocompleteProjectResourceResults); } - catch(Exception e) + catch(Exception) { - return BadRequest(e.Message); + return BadRequest( + new ProblemDetails + { + Title = "Autocomplete results could not be retrieved.", + Detail = "Something went wrong.", + Instance = "26E7C55F-21DE-4A7B-804C-BC0B74597222" + }); } - } From 34f41a8e5046a3548c054bb0a532e59b404d9135 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Wed, 19 May 2021 08:58:14 +0200 Subject: [PATCH 26/66] updated changelog, controller now more specific error when ElasticSearch is unavailable --- API/Controllers/ProjectController.cs | 7 ++-- CHANGELOG.md | 2 ++ .../Exceptions/ElasticUnavailableException.cs | 34 +++++++++++++++++++ Repositories/ProjectRepository.cs | 4 +-- 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 Models/Exceptions/ElasticUnavailableException.cs diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 2aa20947..00804609 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -26,6 +26,7 @@ using Microsoft.EntityFrameworkCore; using Models; using Models.Defaults; +using Models.Exceptions; using Serilog; using Services.Services; using System; @@ -255,13 +256,13 @@ public async Task GetAutoCompleteProjects([FromQuery(Name ="query List autocompleteProjectResourceResults = mapper.Map, List>(projects); return Ok(autocompleteProjectResourceResults); } - catch(Exception) + catch(ElasticUnavailableException) { - return BadRequest( + return StatusCode(503, new ProblemDetails { Title = "Autocomplete results could not be retrieved.", - Detail = "Something went wrong.", + Detail = "ElasticSearch service unavailable.", Instance = "26E7C55F-21DE-4A7B-804C-BC0B74597222" }); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 98385ce9..5e5d6a96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added autocomplete suggestions for search results - [#361](https://github.com/DigitalExcellence/dex-backend/issues/361) + ### Changed ### Deprecated diff --git a/Models/Exceptions/ElasticUnavailableException.cs b/Models/Exceptions/ElasticUnavailableException.cs new file mode 100644 index 00000000..52d93d78 --- /dev/null +++ b/Models/Exceptions/ElasticUnavailableException.cs @@ -0,0 +1,34 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +using System; + +namespace Models.Exceptions +{ + /// + /// This exception will be thrown if Elastic is not available + /// + [Serializable] + public class ElasticUnavailableException : Exception + { + /// + /// Elastic Search unavailable exception constructor + /// + public ElasticUnavailableException() + : base($"ElasticSearch service is unavailable.") { } + } +} diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index dfb4853d..ca071b46 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -720,9 +720,9 @@ public async Task> FindProjectsWhereTitleStartsWithQuery(string qu .Replace("ReplaceWithQuery", query); request.AddParameter("application/json", body, ParameterType.RequestBody); IRestResponse restResponse = elasticSearchContext.Execute(request); - if(!restResponse.IsSuccessful) + if(!restResponse.IsSuccessful && restResponse.Content == "") { - throw new Exception(restResponse.ErrorMessage); + throw new ElasticUnavailableException(); } List esProjectFormats = new List(); From 109179d2c4f0508f345f43ecff0381600408ee61 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Wed, 19 May 2021 09:51:06 +0200 Subject: [PATCH 27/66] updated summaries for swagger documentation --- API/Controllers/ProjectController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 00804609..6e545b60 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -246,8 +246,10 @@ await projectService.GetProjectsTotalPages( /// /// This method returns a list of autocomplete project resources. /// This endpoint returns a list with suggested projects. + /// A 503 status code is returned when the Elastic Search service is unavailable. [HttpGet("search/autocomplete")] [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), 503)] public async Task GetAutoCompleteProjects([FromQuery(Name ="query")] string query) { try From 87d82c803a4fbdabc9a9599b3b1851d546a58856 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Wed, 19 May 2021 10:02:41 +0200 Subject: [PATCH 28/66] updated license from trial to basic --- elasticsearch/config/elasticsearch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticsearch/config/elasticsearch.yml b/elasticsearch/config/elasticsearch.yml index 86822dd0..b06c1d21 100644 --- a/elasticsearch/config/elasticsearch.yml +++ b/elasticsearch/config/elasticsearch.yml @@ -8,6 +8,6 @@ network.host: 0.0.0.0 ## X-Pack settings ## see https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-xpack.html # -xpack.license.self_generated.type: trial +xpack.license.self_generated.type: basic xpack.security.enabled: true xpack.monitoring.collection.enabled: true From b03e324d78bf6c7bbe9dec6eec87788508bbb034 Mon Sep 17 00:00:00 2001 From: timsn Date: Wed, 19 May 2021 10:50:49 +0200 Subject: [PATCH 29/66] Now doesn't get description anymore --- Repositories/ProjectRepository.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 82eac28d..6735ffc4 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -203,12 +203,33 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut .Include(p => p.Likes) .Include(p => p.LinkedInstitutions) .Include(p => p.Categories) - .ThenInclude(c => c.Category); + .ThenInclude(c => c.Category) + //Don't get the description for performance reasons. + .Select(p => new Project + { + UserId = p.UserId, + User = p.User, + Id = p.Id, + ProjectIconId = p.ProjectIconId, + ProjectIcon = p.ProjectIcon, + CallToAction = p.CallToAction, + Collaborators = p.Collaborators, + Likes = p.Likes, + LinkedInstitutions = p.LinkedInstitutions, + Categories = p.Categories, + Created = p.Created, + InstitutePrivate = p.InstitutePrivate, + Name = p.Name, + ShortDescription = p.ShortDescription, + Updated = p.Updated, + Uri = p.Uri + }); queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted); //Execute the IQueryable to get a collection of results - List projectResults = await queryableProjects.ToListAsync(); + List projectResults = await queryableProjects + .ToListAsync(); //Redact the user after fetching the collection from the project (no separate query needs to be executed) projectResults.ForEach(project => project.User = RedactUser(project.User)); From 31d2676c48f01f5f7ebc503338269bc3e85a5b16 Mon Sep 17 00:00:00 2001 From: timsn Date: Wed, 19 May 2021 10:53:31 +0200 Subject: [PATCH 30/66] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a60301db..bb69182f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the get all projects query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) +- Removed getting the description for the projects get all query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) ### Security From 0fb0e851afbfe54a88e208b97e31f9be3d56e00d Mon Sep 17 00:00:00 2001 From: Ruben Date: Thu, 20 May 2021 14:37:02 +0200 Subject: [PATCH 31/66] Added basic multiple image uploader for a project --- API/Controllers/ProjectController.cs | 34 + API/Resources/ProjectResource.cs | 2 + API/Resources/ProjectResourceResult.cs | 2 + ...20120425_AddedImagesToProjects.Designer.cs | 742 ++++++++++++++++++ .../20210520120425_AddedImagesToProjects.cs | 43 + .../ApplicationDbContextModelSnapshot.cs | 9 + Models/Project.cs | 3 + Repositories/ProjectRepository.cs | 4 + 8 files changed, 839 insertions(+) create mode 100644 Data/Migrations/20210520120425_AddedImagesToProjects.Designer.cs create mode 100644 Data/Migrations/20210520120425_AddedImagesToProjects.cs diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index be37a42d..5ccf19cb 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -363,6 +363,23 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } + foreach(int projectResourceImageId in projectResource.ImageIds) + { + Models.File image = await fileService.FindAsync(projectResourceImageId); + if(image == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Image was not found.", + Detail = "The specified image was not found while creating project.", + Instance = "BBE862F5-0F88-478A-92EF-6864860ACEC5" + }; + return BadRequest(problem); + } + + project.Images.Add(image); + } + project.ProjectIcon = file; project.User = await HttpContext.GetContextUser(userService) .ConfigureAwait(false); @@ -550,6 +567,23 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } } + foreach(int projectResourceImageId in projectResource.ImageIds) + { + Models.File image = await fileService.FindAsync(projectResourceImageId); + if(image == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Image was not found.", + Detail = "The specified image was not found while creating project.", + Instance = "BBE862F5-0F88-478A-92EF-6864860ACEC5" + }; + return BadRequest(problem); + } + + project.Images.Add(image); + } + await projectCategoryService.ClearProjectCategories(project); if(projectResource.Categories != null) { diff --git a/API/Resources/ProjectResource.cs b/API/Resources/ProjectResource.cs index e7eabb91..d865929c 100644 --- a/API/Resources/ProjectResource.cs +++ b/API/Resources/ProjectResource.cs @@ -71,6 +71,8 @@ public class ProjectResource /// public ICollection Categories { get; set; } + public IEnumerable ImageIds { get; set; } + } } diff --git a/API/Resources/ProjectResourceResult.cs b/API/Resources/ProjectResourceResult.cs index 3955d8e2..d35c559e 100644 --- a/API/Resources/ProjectResourceResult.cs +++ b/API/Resources/ProjectResourceResult.cs @@ -97,6 +97,8 @@ public class ProjectResourceResult /// public List Likes { get; set; } + public List Images { get; set; } + /// /// Sets or gets if project is visible to institute members only or not /// diff --git a/Data/Migrations/20210520120425_AddedImagesToProjects.Designer.cs b/Data/Migrations/20210520120425_AddedImagesToProjects.Designer.cs new file mode 100644 index 00000000..6e347847 --- /dev/null +++ b/Data/Migrations/20210520120425_AddedImagesToProjects.Designer.cs @@ -0,0 +1,742 @@ +// +using System; +using Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace _4_Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20210520120425_AddedImagesToProjects")] + partial class AddedImagesToProjects + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Category"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Collaborators"); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Guid") + .HasColumnType("nvarchar(max)"); + + b.Property("IconId") + .HasColumnType("int"); + + b.Property("IsVisible") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IconId"); + + b.ToTable("DataSource"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.HasKey("DataSourceId", "WizardPageId", "AuthFlow"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutePrivate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectCategory"); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectInstitution"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccountCreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedGraduationDate") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProfileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("RoleId"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProject"); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTask"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.Project", null) + .WithMany("Images") + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.File", "Image") + .WithMany() + .HasForeignKey("ImageId"); + + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectCategory", b => + { + b.HasOne("Models.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("Categories") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("LinkedInstitutions") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.HasOne("Models.User", "User") + .WithMany("UserTasks") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.HasOne("Models.User", "FollowedUser") + .WithMany("FollowedUsers") + .HasForeignKey("FollowedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20210520120425_AddedImagesToProjects.cs b/Data/Migrations/20210520120425_AddedImagesToProjects.cs new file mode 100644 index 00000000..049135a0 --- /dev/null +++ b/Data/Migrations/20210520120425_AddedImagesToProjects.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class AddedImagesToProjects : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ProjectId", + table: "File", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_File_ProjectId", + table: "File", + column: "ProjectId"); + + migrationBuilder.AddForeignKey( + name: "FK_File_Project_ProjectId", + table: "File", + column: "ProjectId", + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_File_Project_ProjectId", + table: "File"); + + migrationBuilder.DropIndex( + name: "IX_File_ProjectId", + table: "File"); + + migrationBuilder.DropColumn( + name: "ProjectId", + table: "File"); + } + } +} diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 10a11782..749ddce9 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -187,6 +187,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("ProjectId") + .HasColumnType("int"); + b.Property("UploadDateTime") .HasColumnType("datetime2"); @@ -195,6 +198,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("ProjectId"); + b.HasIndex("UploaderId"); b.ToTable("File"); @@ -593,6 +598,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Models.File", b => { + b.HasOne("Models.Project", null) + .WithMany("Images") + .HasForeignKey("ProjectId"); + b.HasOne("Models.User", "Uploader") .WithMany() .HasForeignKey("UploaderId") diff --git a/Models/Project.cs b/Models/Project.cs index be4c1dba..5a7d3cec 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -31,6 +31,7 @@ public Project() { Collaborators = new List(); LinkedInstitutions = new List(); + Images = new List(); } public int Id { get; set; } @@ -79,6 +80,8 @@ public Project() public List Categories { get; set; } + public List Images { get; set; } + /// /// Checks if the user can access the project based on diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 82eac28d..b5d4849b 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -159,6 +159,7 @@ public override async Task FindAsync(int id) .Where(s => s.Id == id) .Include(p => p.ProjectIcon) .Include(p => p.CallToAction) + .Include(p => p.Images) .SingleOrDefaultAsync(); if(project != null) @@ -202,6 +203,7 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut .Include(p => p.Collaborators) .Include(p => p.Likes) .Include(p => p.LinkedInstitutions) + .Include(p => p.Images) .Include(p => p.Categories) .ThenInclude(c => c.Category); @@ -279,6 +281,7 @@ public async Task FindWithUserCollaboratorsAndInstitutionsAsync(int id) .Include(p => p.User) .Include(p => p.ProjectIcon) .Include(p => p.CallToAction) + .Include(p => p.Images) .Where(p => p.Id == id) .FirstOrDefaultAsync(); if(project != null) @@ -421,6 +424,7 @@ public async Task> GetUserProjects(int userId) IEnumerable projects = await GetDbSet() .Include(p => p.Collaborators) .Include(p => p.ProjectIcon) + .Include(p => p.Images) .Where(p => p.UserId == userId) .ToListAsync(); From 838d848c4ff180c92d721f968339af2b41a0b8ae Mon Sep 17 00:00:00 2001 From: Ruben Fricke Date: Mon, 24 May 2021 15:02:34 +0200 Subject: [PATCH 32/66] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01023b2e..c3be504e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added image to highlights - [#431](https://github.com/DigitalExcellence/dex-backend/issues/431) +- Added functionality to add multiples images to a project - [#430](https://github.com/DigitalExcellence/dex-backend/issues/430) ### Changed From 7bb456943bb1655c871b79225ec8911f620fd1dc Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Thu, 27 May 2021 10:25:47 +0200 Subject: [PATCH 33/66] added summaries for readability --- API/Resources/ProjectResource.cs | 3 +++ API/Resources/ProjectResourceResult.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/API/Resources/ProjectResource.cs b/API/Resources/ProjectResource.cs index d865929c..e9f93bc2 100644 --- a/API/Resources/ProjectResource.cs +++ b/API/Resources/ProjectResource.cs @@ -71,6 +71,9 @@ public class ProjectResource /// public ICollection Categories { get; set; } + /// + /// This gets or sets the image ID's + /// public IEnumerable ImageIds { get; set; } } diff --git a/API/Resources/ProjectResourceResult.cs b/API/Resources/ProjectResourceResult.cs index d35c559e..09c51d47 100644 --- a/API/Resources/ProjectResourceResult.cs +++ b/API/Resources/ProjectResourceResult.cs @@ -97,6 +97,9 @@ public class ProjectResourceResult /// public List Likes { get; set; } + /// + /// This gets or sets the images in de project. + /// public List Images { get; set; } /// From ef0a544a763e7c9feff938c1c68c71afa8860d0b Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Fri, 28 May 2021 12:57:49 +0200 Subject: [PATCH 34/66] updated max_gram to 25 instead of 10. This makes it possible to autocomplete up to 25 character per word. --- Repositories/ElasticSearch/Queries.cs | 2 +- Repositories/ElasticSearch/Queries/IndexProjects.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Repositories/ElasticSearch/Queries.cs b/Repositories/ElasticSearch/Queries.cs index d0a48fb4..014caf8c 100644 --- a/Repositories/ElasticSearch/Queries.cs +++ b/Repositories/ElasticSearch/Queries.cs @@ -18,7 +18,7 @@ public class Queries "\"wordnet\",\"lenient\":true,\"synonyms_path\":\"analysis/wn_s.txt\"},\"english_stop\":" + "{ \"type\":\"stop\",\"stopwords\":\"_english_\"},\"english_stemmer\":{ \"type\":\"stemmer\"," + "\"language\":\"english\"} },\"tokenizer\":{ \"autocomplete\":{ \"type\":\"edge_ngram\"," + - "\"min_gram\":2,\"max_gram\":10,\"token_chars\":[\"letter\"]} }}},\"mappings\":{ \"properties\":" + + "\"min_gram\":2,\"max_gram\":25,\"token_chars\":[\"letter\"]} }}},\"mappings\":{ \"properties\":" + "{ \"Created\":{ \"type\":\"date\"},\"Id\":{ \"type\":\"integer\"},\"ProjectName\":{ \"type\":" + "\"text\",\"analyzer\":\"autocomplete\",\"search_analyzer\":\"autocomplete_search\"},\"Description\":" + "{ \"type\":\"text\",\"analyzer\":\"description_index\",\"search_analyzer\":\"description_search\"}," + diff --git a/Repositories/ElasticSearch/Queries/IndexProjects.json b/Repositories/ElasticSearch/Queries/IndexProjects.json index ff4d9cca..31f2c6d5 100644 --- a/Repositories/ElasticSearch/Queries/IndexProjects.json +++ b/Repositories/ElasticSearch/Queries/IndexProjects.json @@ -43,7 +43,7 @@ "autocomplete": { "type": "edge_ngram", "min_gram": 2, - "max_gram": 10, + "max_gram": 25, "token_chars": [ "letter" ] From 78279a149bd5d2ba04fe0eabf525f4cfceeb3bdc Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Fri, 28 May 2021 22:31:33 +0200 Subject: [PATCH 35/66] Update recourses and models --- API/Resources/ProjectCategoryResource.cs | 2 +- API/Resources/ProjectFilterParamsResource.cs | 9 ++++++++- Models/ProjectFilterParams.cs | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/API/Resources/ProjectCategoryResource.cs b/API/Resources/ProjectCategoryResource.cs index 9068a491..c93b71c0 100644 --- a/API/Resources/ProjectCategoryResource.cs +++ b/API/Resources/ProjectCategoryResource.cs @@ -27,7 +27,7 @@ public class ProjectCategoryResource /// /// Gets or sets the Id of the category. /// - public int CategoryId { get; set; } + public int Id { get; set; } } } diff --git a/API/Resources/ProjectFilterParamsResource.cs b/API/Resources/ProjectFilterParamsResource.cs index 451d3abc..61389d97 100644 --- a/API/Resources/ProjectFilterParamsResource.cs +++ b/API/Resources/ProjectFilterParamsResource.cs @@ -16,6 +16,7 @@ */ using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; namespace API.Resources { @@ -46,12 +47,18 @@ public class ProjectFilterParamsResource public string SortBy { get; set; } /// - /// Get or Set the direction of sorting by query parameter. + /// Get or Set the direction of sorting by query parameter.F /// Possible options are : asc, desc /// [FromQuery(Name = "sortDirection")] public string SortDirection { get; set; } + // + // Get or set the array of category id's + // + [FromQuery(Name = "categories")] + public ICollection Categories { get; set; } + /// /// This property filter the projects on the highlighted state /// Possible value: diff --git a/Models/ProjectFilterParams.cs b/Models/ProjectFilterParams.cs index 29746866..53bd556c 100644 --- a/Models/ProjectFilterParams.cs +++ b/Models/ProjectFilterParams.cs @@ -15,6 +15,8 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using System.Collections.Generic; + namespace Models { @@ -46,6 +48,11 @@ public class ProjectFilterParams /// public string SortDirection { get; set; } + // + // Get or set the array of category id's + // + public ICollection Categories { get; set; } + /// /// This property filter the projects on the highlighted state /// Possible value: From bf1b641fb9b2904b831cf56598a9921f29cc7601 Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Fri, 28 May 2021 22:32:08 +0200 Subject: [PATCH 36/66] Make GET category endpoints public --- API/Controllers/CategoryController.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/API/Controllers/CategoryController.cs b/API/Controllers/CategoryController.cs index fcf3b7e0..81f1d401 100644 --- a/API/Controllers/CategoryController.cs +++ b/API/Controllers/CategoryController.cs @@ -46,7 +46,6 @@ public CategoryController(ICategoryService categoryService, IProjectCategoryServ /// This method returns a list of category resource results. /// This endpoint returns a list of categories. [HttpGet] - [Authorize(Policy = nameof(Scopes.CategoryRead))] [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] public async Task GetAllCategories() { @@ -64,7 +63,6 @@ public async Task GetAllCategories() /// The 400 Bad Request status code is returned when the specified category id is invalid. /// The 404 Not Found status code is returned when no category is found with the specified category id. [HttpGet("{categoryId}")] - [Authorize(Policy = nameof(Scopes.CategoryRead))] [ProducesResponseType(typeof(CategoryResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] From 9d0c12265a7ee21aff083aa8da897522de79a57f Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Fri, 28 May 2021 22:32:31 +0200 Subject: [PATCH 37/66] Update getProject endpoints to include categories --- API/Controllers/ProjectController.cs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index be37a42d..63ba37f3 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -228,12 +228,9 @@ public async Task GetAllProjects( { Results = results.ToArray(), Count = results.Count(), - TotalCount = - await projectService.ProjectsCount(projectFilterParams), + TotalCount = await projectService.ProjectsCount(projectFilterParams), Page = projectFilterParams.Page, - TotalPages = - await projectService.GetProjectsTotalPages( - projectFilterParams) + TotalPages = await projectService.GetProjectsTotalPages(projectFilterParams) }; return Ok(resultsResource); @@ -340,8 +337,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( ProblemDetails problem = new ProblemDetails { Title = "Call to action value was not found.", - Detail = - "The specified call to action value was not found while creating the project.", + Detail = "The specified call to action value was not found while creating the project.", Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" }; return BadRequest(problem); @@ -373,10 +369,10 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( foreach(ProjectCategoryResource projectCategoryResource in projectCategoryResources) { - ProjectCategory alreadyExcProjectCategory = await projectCategoryService.GetProjectCategory(project.Id, projectCategoryResource.CategoryId); + ProjectCategory alreadyExcProjectCategory = await projectCategoryService.GetProjectCategory(project.Id, projectCategoryResource.Id); if(alreadyExcProjectCategory == null) { - Category category = await categoryService.FindAsync(projectCategoryResource.CategoryId); + Category category = await categoryService.FindAsync(projectCategoryResource.Id); if(category == null) { @@ -491,8 +487,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( ProblemDetails problem = new ProblemDetails { Title = "Call to action value was not found.", - Detail = - "The specified call to action value was not found while creating the project.", + Detail = "The specified call to action value was not found while creating the project.", Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" }; return BadRequest(problem); @@ -557,10 +552,10 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( foreach(ProjectCategoryResource projectCategoryResource in projectCategoryResources) { - ProjectCategory alreadyExcProjectCategory = await projectCategoryService.GetProjectCategory(project.Id, projectCategoryResource.CategoryId); + ProjectCategory alreadyExcProjectCategory = await projectCategoryService.GetProjectCategory(project.Id, projectCategoryResource.Id); if(alreadyExcProjectCategory == null) { - Category category = await categoryService.FindAsync(projectCategoryResource.CategoryId); + Category category = await categoryService.FindAsync(projectCategoryResource.Id); if(category == null) { From 2cb752deed10a6ad203cc20c0250ba8120c1a920 Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Fri, 28 May 2021 22:32:51 +0200 Subject: [PATCH 38/66] Update project service and repository to filter for categories --- Repositories/ProjectCategoryRepository.cs | 6 +-- Repositories/ProjectRepository.cs | 45 ++++++++++++++++------- Services.Tests/ProjectServiceTest.cs | 36 +++++++++++------- Services/Services/ProjectService.cs | 19 +++++----- 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/Repositories/ProjectCategoryRepository.cs b/Repositories/ProjectCategoryRepository.cs index 1aeb94c3..37db7ea4 100644 --- a/Repositories/ProjectCategoryRepository.cs +++ b/Repositories/ProjectCategoryRepository.cs @@ -41,7 +41,7 @@ public interface IProjectCategoryRepository : IRepository Task GetProjectCategory(int categoryId); /// - /// Gets project categories by given projectid + /// Gets project categories by given projectid /// Task> GetProjectCategories(int projectId); } @@ -54,7 +54,7 @@ public class ProjectCategoryRepository : Repository, { /// - /// This is the project category repository constructor + /// This is the project category repository constructor /// /// public ProjectCategoryRepository(DbContext dbContext) : @@ -79,7 +79,7 @@ public Task GetProjectCategory(int categoryId) } /// - /// Gets project categories by given projectid + /// Gets project categories by given projectid /// public Task> GetProjectCategories(int projectId) { diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 6735ffc4..5f52aaaf 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -52,21 +52,24 @@ public interface IProjectRepository : IRepository /// /// /// + /// /// List of projects Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( int? skip = null, int? take = null, Expression> orderBy = null, bool orderByAsc = true, - bool? highlighted = null + bool? highlighted = null, + ICollection categories = null ); /// /// This interface method counts the amount of projects matching the filters. /// /// + /// The categories parameter represents the categories the project needs to have /// number of projects found - Task CountAsync(bool? highlighted = null); + Task CountAsync(bool? highlighted = null, ICollection categories = null); /// /// This interface method searches the database for projects matching the search query and parameters. @@ -77,6 +80,7 @@ Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( /// The order by parameter represents the way how to order the projects. /// The order by asc parameters represents the order direction (True: asc, False: desc) /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns thee projects matching the search query and parameters. Task> SearchAsync( string query, @@ -84,7 +88,8 @@ Task> SearchAsync( int? take = null, Expression> orderBy = null, bool orderByAsc = true, - bool? highlighted = null + bool? highlighted = null, + ICollection categories = null ); /// @@ -92,8 +97,9 @@ Task> SearchAsync( /// /// The query parameters represents the search query used for filtering projects. /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns the amount of projects matching the filters. - Task SearchCountAsync(string query, bool? highlighted = null); + Task SearchCountAsync(string query, bool? highlighted = null, ICollection categories = null); Task SyncProjectToES(Project project); @@ -186,13 +192,15 @@ public override async Task FindAsync(int id) /// The order by parameter represents the way how to order the projects. /// The order by asc parameters represents the order direction (True: asc, False: desc) /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns a list of projects filtered by the specified parameters. public virtual async Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( int? skip = null, int? take = null, Expression> orderBy = null, bool orderByAsc = true, - bool? highlighted = null + bool? highlighted = null, + ICollection categories = null ) { IQueryable queryableProjects = GetDbSet() @@ -225,7 +233,7 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut Uri = p.Uri }); - queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted); + queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); //Execute the IQueryable to get a collection of results List projectResults = await queryableProjects @@ -241,10 +249,11 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut /// This method counts the amount of projects matching the filters. /// /// The highlighted parameter represents whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns the amount of projects matching the filters. - public virtual async Task CountAsync(bool? highlighted = null) + public virtual async Task CountAsync(bool? highlighted = null, ICollection categories = null) { - return await ApplyFilters(DbSet, null, null, null, true, highlighted) + return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories) .CountAsync(); } @@ -257,6 +266,7 @@ public virtual async Task CountAsync(bool? highlighted = null) /// The order by parameter represents the way how to order the projects. /// The order by asc parameters represents the order direction (True: asc, False: desc) /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns thee projects matching the search query and parameters. public virtual async Task> SearchAsync( string query, @@ -264,11 +274,12 @@ public virtual async Task> SearchAsync( int? take = null, Expression> orderBy = null, bool orderByAsc = true, - bool? highlighted = null + bool? highlighted = null, + ICollection categories = null ) { List result = - await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderByAsc, highlighted) + await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderByAsc, highlighted, categories) .ToListAsync(); return result.Where(p => ProjectContainsQuery(p, query)) .ToList(); @@ -279,10 +290,11 @@ await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderB /// /// The query parameters represents the search query used for filtering projects. /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// This method returns the amount of projects matching the filters. - public virtual async Task SearchCountAsync(string query, bool? highlighted = null) + public virtual async Task SearchCountAsync(string query, bool? highlighted = null, ICollection categories = null) { - return await ApplyFilters(await GetProjectQueryable(query), null, null, null, true, highlighted) + return await ApplyFilters(await GetProjectQueryable(query), null, null, null, true, highlighted, categories) .CountAsync(); } @@ -527,6 +539,7 @@ private List RedactUser(List projects) /// The order by parameter represents the way how to order the projects. /// The order by asc parameters represents the order direction (True: asc, False: desc) /// The highlighted parameter represents the whether to filter highlighted projects. + /// /// /// This method returns a IQueryable Projects collection based on the given filters. /// @@ -536,7 +549,8 @@ private IQueryable ApplyFilters( int? take, Expression> orderBy, bool orderByAsc, - bool? highlighted + bool? highlighted, + ICollection categories ) { if(highlighted.HasValue) @@ -552,6 +566,11 @@ private IQueryable ApplyFilters( queryable = queryable.Where(p => highlightedQueryable.Contains(p.Id) == highlighted.Value); } + if(categories != null && categories.Count > 0) + { + queryable = queryable.Where(p => p.Categories.Any(cat => categories.Contains(cat.Id))); + } + if(orderBy != null) { if(orderByAsc) diff --git a/Services.Tests/ProjectServiceTest.cs b/Services.Tests/ProjectServiceTest.cs index 7fb7fb89..db531c3f 100644 --- a/Services.Tests/ProjectServiceTest.cs +++ b/Services.Tests/ProjectServiceTest.cs @@ -1,16 +1,16 @@ /* * Digital Excellence Copyright (C) 2020 Brend Smits -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Lesser General Public License as published +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty -* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. -* -* You can find a copy of the GNU Lesser General Public License +* +* You can find a copy of the GNU Lesser General Public License * along with this program, in the LICENSE.md file in the root project directory. * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ @@ -49,6 +49,7 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List project.Updated, true, + null, null)) .Returns(Task.FromResult(projects)); @@ -69,6 +70,7 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List project.Updated, true, + null, null), Times.Once); }); @@ -91,6 +93,7 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour null, project => project.Created, true, + null, null)) .Returns(Task.FromResult(projects)); @@ -111,6 +114,7 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour null, project => project.Created, true, + null, null), Times.Once); }); @@ -133,6 +137,7 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource null, project => project.Name, false, + null, null)) .Returns(Task.FromResult(projects)); @@ -153,6 +158,7 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource null, project => project.Name, false, + null, null), Times.Once); }); @@ -175,7 +181,8 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

project.Updated, true, - true)) + true, + null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams @@ -195,7 +202,8 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

project.Updated, true, - true), + true, + null), Times.Once); }); @@ -217,7 +225,8 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List null, project => project.Updated, true, - false)) + false, + null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams @@ -237,7 +246,8 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List null, project => project.Updated, true, - false), + false, + null), Times.Once); }); diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index a52b7539..e0bed5ec 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -78,10 +78,9 @@ public interface IProjectService : IService void MigrateDatabase(List projectsToExport); ///

- /// Get project by id with users and collaborators + /// Get all projects in the database with collaborators and institutions /// - /// The parameter is the id of the project. - /// The project with users and collaborators + /// Returns all projects in the database with collaborators and institutions Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(); } @@ -165,7 +164,8 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj take, orderBy, orderByDirection, - projectFilterParams.Highlighted); + projectFilterParams.Highlighted, + projectFilterParams.Categories); } /// @@ -175,7 +175,7 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj /// The number of projects public virtual async Task ProjectsCount(ProjectFilterParams projectFilterParams) { - return await Repository.CountAsync(projectFilterParams.Highlighted); + return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories); } /// @@ -208,10 +208,9 @@ public async Task ProjectExistsAsync(int id) } /// - /// Get project by id with users and collaborators + /// Get all projects in the database with collaborators and institutions /// - /// The parameter is the id of the project. - /// The project with users and collaborators + /// Returns all projects in the database with collaborators and institutions public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync() { return Repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync(); @@ -224,7 +223,7 @@ public void MigrateDatabase(List projectsToExport) CreateProjectIndexElastic(); // Migrates the data from MSSQL to Elastic. Repository.MigrateDatabase(projectsToExport); - + } /// @@ -243,7 +242,7 @@ private void CreateProjectIndexElastic() Repository.CreateProjectIndex(); } - + public Task> GetUserProjects(int userId) { return Repository.GetUserProjects(userId); From 2bfd593d2db7b475bb0257766e584b9976653b37 Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Mon, 31 May 2021 09:52:54 +0200 Subject: [PATCH 39/66] Add extra null check on skipCount --- Repositories/ProjectRepository.cs | 3 ++- Services/Services/ProjectService.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 5f52aaaf..1197eb09 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -568,6 +568,7 @@ ICollection categories if(categories != null && categories.Count > 0) { + // Check for every project if there is any project-category that matches the selected categories queryable = queryable.Where(p => p.Categories.Any(cat => categories.Contains(cat.Id))); } @@ -582,7 +583,7 @@ ICollection categories } } - if(skip.HasValue) queryable = queryable.Skip(skip.Value); + if(skip.HasValue && skip.Value > 0) queryable = queryable.Skip(skip.Value); if(take.HasValue) queryable = queryable.Take(take.Value); return queryable; } diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index e0bed5ec..e060ff2b 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -139,7 +139,7 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj int? skip = null; int? take = null; - if(projectFilterParams.Page.HasValue) + if(projectFilterParams.Page.HasValue && projectFilterParams.Page.Value > 1) { skip = projectFilterParams.AmountOnPage * (projectFilterParams.Page - 1); take = projectFilterParams.AmountOnPage; From fc0217865ca245b0ffee71fb65584c8462205551 Mon Sep 17 00:00:00 2001 From: Spacinggolem Date: Mon, 31 May 2021 10:03:05 +0200 Subject: [PATCH 40/66] Fix tests --- Services.Tests/SearchServiceTest.cs | 62 +++++++++++++++++------------ 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/Services.Tests/SearchServiceTest.cs b/Services.Tests/SearchServiceTest.cs index a3b76237..10026dda 100644 --- a/Services.Tests/SearchServiceTest.cs +++ b/Services.Tests/SearchServiceTest.cs @@ -1,16 +1,16 @@ /* * Digital Excellence Copyright (C) 2020 Brend Smits -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Lesser General Public License as published +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty -* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. -* -* You can find a copy of the GNU Lesser General Public License +* +* You can find a copy of the GNU Lesser General Public License * along with this program, in the LICENSE.md file in the root project directory. * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ @@ -69,7 +69,8 @@ public async Task SearchInternalProjects_goodflow_sort_direction_true( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -90,6 +91,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_true( null, project => project.Updated, true, + null, null), Times.Once); }); @@ -112,7 +114,8 @@ public async Task SearchInternalProjects_goodflow_sort_direction_false( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -133,6 +136,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_false( null, project => project.Updated, false, + null, null), Times.Once); }); @@ -154,7 +158,8 @@ public async Task SearchInternalProjects_goodflow_sortBy_name( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -175,6 +180,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_name( null, project => project.Name, false, + null, null), Times.Once); }); @@ -196,7 +202,8 @@ public async Task SearchInternalProjects_goodflow_sortBy_Created( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -217,6 +224,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_Created( null, project => project.Created, false, + null, null), Times.Once); }); @@ -238,7 +246,8 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -259,6 +268,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page( 20, project => project.Updated, true, + null, null), Times.Once); }); @@ -281,7 +291,8 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page_custom( It.IsAny(), It.IsAny>>(), It.IsAny(), - It.IsAny())) + It.IsAny(), + It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); IEnumerable resultedProjects = await Service.SearchInternalProjects("", @@ -302,6 +313,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page_custom( 17, project => project.Updated, true, + null, null), Times.Once); }); @@ -319,7 +331,7 @@ public async Task SearchInternalProjectsCount_highlighted_null( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -333,7 +345,7 @@ public async Task SearchInternalProjectsCount_highlighted_null( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", null, null), Times.Once); }); @@ -350,7 +362,7 @@ public async Task SearchInternalProjectsCount_highlighted_true( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -364,7 +376,7 @@ public async Task SearchInternalProjectsCount_highlighted_true( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", true), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", true, null), Times.Once); }); @@ -381,7 +393,7 @@ public async Task SearchInternalProjectsCount_highlighted_false( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -395,7 +407,7 @@ public async Task SearchInternalProjectsCount_highlighted_false( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), Times.Once); }); @@ -411,7 +423,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_custom_amountOnPage( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalPages = await Service.SearchInternalProjectsTotalPages("", @@ -425,7 +437,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_custom_amountOnPage( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), Times.Once); }); @@ -442,7 +454,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_default_amountOnPage [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalPages = await Service.SearchInternalProjectsTotalPages("", @@ -456,7 +468,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_default_amountOnPage }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), Times.Once); }); From f3839040bfcd33e67aafce8ad8f231e744729919 Mon Sep 17 00:00:00 2001 From: Walter Sajtos Date: Tue, 1 Jun 2021 09:50:25 +0200 Subject: [PATCH 41/66] Update seed --- Data/Helpers/Seed.cs | 8 ++++---- Services/Services/ProjectService.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Data/Helpers/Seed.cs b/Data/Helpers/Seed.cs index 15a9a523..f1a2570e 100644 --- a/Data/Helpers/Seed.cs +++ b/Data/Helpers/Seed.cs @@ -189,19 +189,19 @@ public static List SeedCategories() categories.AddRange(new[]{ new Category { - Name = "Some Category 1" + Name = "UX/UI" }, new Category { - Name = "Some Category 2" + Name = "Video" }, new Category { - Name = "Some Category 3" + Name = "Code" }, new Category { - Name = "Some Category 4" + Name = "Research" } }); diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index e060ff2b..403e1517 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -138,7 +138,7 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj projectFilterParams.AmountOnPage = 20; int? skip = null; - int? take = null; + int? take = projectFilterParams.AmountOnPage; if(projectFilterParams.Page.HasValue && projectFilterParams.Page.Value > 1) { skip = projectFilterParams.AmountOnPage * (projectFilterParams.Page - 1); From b26321a9caf974b6ef1bd3546fd09c02cdc28515 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Tue, 1 Jun 2021 10:34:15 +0200 Subject: [PATCH 42/66] added empty list to Project Ids --- API/Resources/ProjectResource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/Resources/ProjectResource.cs b/API/Resources/ProjectResource.cs index e9f93bc2..92f543c7 100644 --- a/API/Resources/ProjectResource.cs +++ b/API/Resources/ProjectResource.cs @@ -74,8 +74,8 @@ public class ProjectResource /// /// This gets or sets the image ID's /// - public IEnumerable ImageIds { get; set; } - + public IEnumerable ImageIds { get; set; } = new List(); + } } From f971e0cb3571904dbd40b45843ddef1a7048f387 Mon Sep 17 00:00:00 2001 From: Walter Sajtos Date: Tue, 1 Jun 2021 10:42:55 +0200 Subject: [PATCH 43/66] Fix entity framework select --- Repositories/ProjectRepository.cs | 63 ++++++++++++++++--------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 1197eb09..50ca3fff 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -204,39 +204,42 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut ) { IQueryable queryableProjects = GetDbSet() - .Include(u => u.User) - .Include(p => p.ProjectIcon) - .Include(p => p.CallToAction) - .Include(p => p.Collaborators) - .Include(p => p.Likes) - .Include(p => p.LinkedInstitutions) - .Include(p => p.Categories) - .ThenInclude(c => c.Category) + .Include(u => u.User) + .Include(p => p.ProjectIcon) + .Include(p => p.CallToAction) + .Include(p => p.Collaborators) + .Include(p => p.Likes) + .Include(p => p.LinkedInstitutions) + .Include(p => p.Categories) + .ThenInclude(c => c.Category); //Don't get the description for performance reasons. - .Select(p => new Project - { - UserId = p.UserId, - User = p.User, - Id = p.Id, - ProjectIconId = p.ProjectIconId, - ProjectIcon = p.ProjectIcon, - CallToAction = p.CallToAction, - Collaborators = p.Collaborators, - Likes = p.Likes, - LinkedInstitutions = p.LinkedInstitutions, - Categories = p.Categories, - Created = p.Created, - InstitutePrivate = p.InstitutePrivate, - Name = p.Name, - ShortDescription = p.ShortDescription, - Updated = p.Updated, - Uri = p.Uri - }); - - queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); + + queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); //Execute the IQueryable to get a collection of results - List projectResults = await queryableProjects + List projectResults = await queryableProjects.Select(p => new Project + { + UserId = p.UserId, + User = p.User, + Id = p.Id, + ProjectIconId = p.ProjectIconId, + ProjectIcon = p.ProjectIcon, + CallToAction = p.CallToAction, + Collaborators = p.Collaborators, + Likes = p.Likes, + LinkedInstitutions = p.LinkedInstitutions, + Categories = p.Categories.Select(c => new ProjectCategory() + { + Category = c.Category, + Id = c.Id + }).ToList(), + Created = p.Created, + InstitutePrivate = p.InstitutePrivate, + Name = p.Name, + ShortDescription = p.ShortDescription, + Updated = p.Updated, + Uri = p.Uri + }) .ToListAsync(); //Redact the user after fetching the collection from the project (no separate query needs to be executed) From b120d170a00c72e8dfd87a8e13989f61602f8105 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Tue, 1 Jun 2021 10:56:43 +0200 Subject: [PATCH 44/66] Added unique GUIDs --- API/Controllers/ProjectController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 5ccf19cb..c430bab7 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -372,7 +372,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( { Title = "Image was not found.", Detail = "The specified image was not found while creating project.", - Instance = "BBE862F5-0F88-478A-92EF-6864860ACEC5" + Instance = "B040FAAD-FD22-4C77-822E-C498DFA1A9CB" }; return BadRequest(problem); } @@ -576,8 +576,8 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( { Title = "Image was not found.", Detail = "The specified image was not found while creating project.", - Instance = "BBE862F5-0F88-478A-92EF-6864860ACEC5" - }; + Instance = "FC816E40-31A6-4187-BEBA-D22F06019F8F" + }; return BadRequest(problem); } From 4dc2515c8e0ae61445d855810d8a61ba8a3bf3a0 Mon Sep 17 00:00:00 2001 From: Walter Sajtos Date: Tue, 1 Jun 2021 11:06:48 +0200 Subject: [PATCH 45/66] Update seeding --- Data/Helpers/Seed.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Data/Helpers/Seed.cs b/Data/Helpers/Seed.cs index f1a2570e..a1eb4d14 100644 --- a/Data/Helpers/Seed.cs +++ b/Data/Helpers/Seed.cs @@ -202,6 +202,10 @@ public static List SeedCategories() new Category { Name = "Research" + }, + new Category + { + Name = "Assignment" } }); From 06b8d15d9ffdf90ac40930932ae257bb9791ce7b Mon Sep 17 00:00:00 2001 From: Walter Sajtos Date: Tue, 1 Jun 2021 11:06:58 +0200 Subject: [PATCH 46/66] Fix category Id selection in linq query --- Repositories/ProjectRepository.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 50ca3fff..ec941080 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -212,11 +212,11 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut .Include(p => p.LinkedInstitutions) .Include(p => p.Categories) .ThenInclude(c => c.Category); - //Don't get the description for performance reasons. queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); //Execute the IQueryable to get a collection of results + //Don't get the description for performance reasons. List projectResults = await queryableProjects.Select(p => new Project { UserId = p.UserId, @@ -572,7 +572,8 @@ ICollection categories if(categories != null && categories.Count > 0) { // Check for every project if there is any project-category that matches the selected categories - queryable = queryable.Where(p => p.Categories.Any(cat => categories.Contains(cat.Id))); + // queryable = queryable.Where(p => p.Categories.All(cat => categories.Contains(cat.Category.Id))); + queryable = queryable.Where(p => p.Categories.Any(cat => categories.Contains(cat.Category.Id))); } if(orderBy != null) From f1460fa95266432711a33d8a9bb3696383895baa Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Tue, 1 Jun 2021 11:10:58 +0200 Subject: [PATCH 47/66] When no similar users are found, an empty array is now returned instead of ProblemDetails as we don't want the frontend to show this as an error. In the frontend we can catch the empty array. --- API/Controllers/UserController.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/API/Controllers/UserController.cs b/API/Controllers/UserController.cs index a78f5485..13111085 100644 --- a/API/Controllers/UserController.cs +++ b/API/Controllers/UserController.cs @@ -21,6 +21,7 @@ using API.Resources; using AutoMapper; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.VisualStudio.Web.CodeGeneration; @@ -604,6 +605,7 @@ public async Task SetUserGraduationDate([FromBody] UserResource u /// Ok [HttpGet("projectrecommendations/{amount}")] [ProducesResponseType(typeof(ProblemDetails), (int)HttpStatusCode.NotFound)] + [ProducesResponseType(StatusCodes.Status200OK)] [Authorize] public async Task GetRecommendedProjects(int amount) { @@ -625,15 +627,9 @@ public async Task GetRecommendedProjects(int amount) List projectRecommendations = await userService.GetRecommendedProjects(user.Id, amount); return Ok(mapper.Map, List>(projectRecommendations)); - } catch(RecommendationNotFoundException e) + } catch(RecommendationNotFoundException) { - ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the recommendations", - Detail = e.Message, - Instance = "948319D2-1A19-4E00-AF50-DB5D096AFD39" - }; - return NotFound(problem); + return Ok(new List()); } } From cb9f1835fd5876a6382ad4dc99666654533fb221 Mon Sep 17 00:00:00 2001 From: Walter Sajtos Date: Tue, 1 Jun 2021 11:18:05 +0200 Subject: [PATCH 48/66] Consistent model naming --- API/Controllers/ProjectController.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 5ccf19cb..f8546861 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -34,7 +34,6 @@ using System.Linq; using System.Net; using System.Threading.Tasks; -using File = Models.File; namespace API.Controllers { @@ -349,7 +348,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } Project project = mapper.Map(projectResource); - File file = await fileService.FindAsync(projectResource.FileId); + Models.File file = await fileService.FindAsync(projectResource.FileId); if(projectResource.FileId != 0 && file == null) @@ -528,7 +527,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } // Upload the new file if there is one - File file = null; + Models.File file = null; if(projectResource.FileId != 0) { if(project.ProjectIconId != 0 && @@ -536,7 +535,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( { if(project.ProjectIconId != projectResource.FileId) { - File fileToDelete = await fileService.FindAsync(project.ProjectIconId.Value); + Models.File fileToDelete = await fileService.FindAsync(project.ProjectIconId.Value); // Remove the file from the filesystem fileUploader.DeleteFileFromDirectory(fileToDelete); @@ -673,7 +672,7 @@ public async Task DeleteProject(int projectId) if(project.ProjectIconId.HasValue) { // We need to delete the old file. - File fileToDelete = await fileService.FindAsync(project.ProjectIconId.Value); + Models.File fileToDelete = await fileService.FindAsync(project.ProjectIconId.Value); try { // Remove the file from the database From 15057f288c14ec8829924d9f2c02fcfe835a74d3 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Tue, 1 Jun 2021 11:43:39 +0200 Subject: [PATCH 49/66] Edited ProducesResponseType annotation. Empty array of ProjectResourceResult is now returned instead of an empty array of projects. --- API/Controllers/UserController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/API/Controllers/UserController.cs b/API/Controllers/UserController.cs index 13111085..14bab8d8 100644 --- a/API/Controllers/UserController.cs +++ b/API/Controllers/UserController.cs @@ -603,9 +603,11 @@ public async Task SetUserGraduationDate([FromBody] UserResource u /// Get recommended projects for the user who is logged in. /// /// Ok + /// This endpoint returns status code 200. An empty array is returned when no recommendations can be found. + /// The 404 Not Found status code is returned when the user is not found. [HttpGet("projectrecommendations/{amount}")] [ProducesResponseType(typeof(ProblemDetails), (int)HttpStatusCode.NotFound)] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [Authorize] public async Task GetRecommendedProjects(int amount) { @@ -629,7 +631,7 @@ public async Task GetRecommendedProjects(int amount) } catch(RecommendationNotFoundException) { - return Ok(new List()); + return Ok(new List()); } } From d66f34f68e24bdabbb3e9e1328b7d300b4bf8088 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Wed, 2 Jun 2021 11:17:59 +0200 Subject: [PATCH 50/66] Changed gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index fda7eec6..a3e6418d 100644 --- a/.gitignore +++ b/.gitignore @@ -339,3 +339,5 @@ IdentityServer/tempkey.rsa ### rabbitmq/data/ + +/API/appsettingsapi.Development.json \ No newline at end of file From aad2f74c1508cba545be17e2accba256c47765e7 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Wed, 2 Jun 2021 11:21:06 +0200 Subject: [PATCH 51/66] Gitignore --- .gitignore | 4 +--- API/appsettingsapi.Development.json | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index a3e6418d..c0cc8cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -338,6 +338,4 @@ IdentityServer/tempkey.rsa !/API/Uploads/Images/.gitkeep ### -rabbitmq/data/ - -/API/appsettingsapi.Development.json \ No newline at end of file +rabbitmq/data/ \ No newline at end of file diff --git a/API/appsettingsapi.Development.json b/API/appsettingsapi.Development.json index 83aaf5ed..0710892b 100644 --- a/API/appsettingsapi.Development.json +++ b/API/appsettingsapi.Development.json @@ -37,8 +37,8 @@ }, "RabbitMQ": { "Hostname": "localhost", - "Username": "test", - "Password": "test" + "Username": "guest", + "Password": "guest" } }, "Logging": { From 5394e88e4deacc2e62b4b71da5b8439936fd84cf Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Wed, 2 Jun 2021 11:24:45 +0200 Subject: [PATCH 52/66] Updated appsettings --- API/appsettingsapi.Development.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/appsettingsapi.Development.json b/API/appsettingsapi.Development.json index 0710892b..83aaf5ed 100644 --- a/API/appsettingsapi.Development.json +++ b/API/appsettingsapi.Development.json @@ -37,8 +37,8 @@ }, "RabbitMQ": { "Hostname": "localhost", - "Username": "guest", - "Password": "guest" + "Username": "test", + "Password": "test" } }, "Logging": { From ba84d26d2b761024c80adb91b34f5da4dbc69bc6 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Wed, 2 Jun 2021 11:28:06 +0200 Subject: [PATCH 53/66] Updated git ignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c0cc8cf0..cc63b6bc 100644 --- a/.gitignore +++ b/.gitignore @@ -337,5 +337,4 @@ IdentityServer/tempkey.rsa /API/Uploads/Images/* !/API/Uploads/Images/.gitkeep -### -rabbitmq/data/ \ No newline at end of file +### \ No newline at end of file From 523844422131f77b0a985f011a56f862ad4509b5 Mon Sep 17 00:00:00 2001 From: macfleury-2000 Date: Wed, 2 Jun 2021 11:29:25 +0200 Subject: [PATCH 54/66] Updated git ignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index cc63b6bc..c0cc8cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -337,4 +337,5 @@ IdentityServer/tempkey.rsa /API/Uploads/Images/* !/API/Uploads/Images/.gitkeep -### \ No newline at end of file +### +rabbitmq/data/ \ No newline at end of file From 3405ea7a50fad29f473d3732261b357eceb460fc Mon Sep 17 00:00:00 2001 From: Joep Verhoeven <45588540+BluRRayS@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:38:00 +0200 Subject: [PATCH 55/66] Performance testing workflow --- .github/workflows/PerformanceTests.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/PerformanceTests.yml diff --git a/.github/workflows/PerformanceTests.yml b/.github/workflows/PerformanceTests.yml new file mode 100644 index 00000000..107d8527 --- /dev/null +++ b/.github/workflows/PerformanceTests.yml @@ -0,0 +1,17 @@ + +name: Execute Performance tests + +on: + push: + branches: [ develop, release/* ] + +jobs: + build: + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v2 + - name: Navigate to performance test directory. + run: cd PerformanceTests + - name: Run docker compose up + run: docker-compose -f docker-compose.load-tests.yml up From fe278d097a202ca0c5d85ff641d47b4b7be0069c Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:58:02 +0200 Subject: [PATCH 56/66] updated Mock method so it will return mocked data with any amount of projects asked --- Services.Tests/ProjectServiceTest.cs | 35 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Services.Tests/ProjectServiceTest.cs b/Services.Tests/ProjectServiceTest.cs index db531c3f..860a9b07 100644 --- a/Services.Tests/ProjectServiceTest.cs +++ b/Services.Tests/ProjectServiceTest.cs @@ -46,7 +46,7 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, null, @@ -54,20 +54,21 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams - { - Page = null, - AmountOnPage = null, - Highlighted = null, - SortBy = null, - SortDirection = "asc" - }); + { + Page = null, + AmountOnPage = null, + Highlighted = null, + SortBy = null, + SortDirection = "asc", + Categories = null + }); ; Assert.DoesNotThrow(() => { RepositoryMock.Verify(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, null, @@ -90,7 +91,7 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour RepositoryMock.Setup(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Created, true, null, @@ -111,7 +112,7 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour RepositoryMock.Verify(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Created, true, null, @@ -134,7 +135,7 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource RepositoryMock.Setup(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Name, false, null, @@ -155,7 +156,7 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource RepositoryMock.Verify(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Name, false, null, @@ -178,7 +179,7 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, true, @@ -199,7 +200,7 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, true, @@ -222,7 +223,7 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List RepositoryMock.Setup(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, false, @@ -243,7 +244,7 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List RepositoryMock.Verify(repository => repository.GetAllWithUsersCollaboratorsAndInstitutionsAsync( null, - null, + It.IsAny(), project => project.Updated, true, false, From e412370004dd371dac89b2954be276b6098dea08 Mon Sep 17 00:00:00 2001 From: Ruben Fricke Date: Wed, 2 Jun 2021 15:16:16 +0200 Subject: [PATCH 57/66] Update API/Resources/ProjectFilterParamsResource.cs Co-authored-by: Niray Mak <44868678+niraymak@users.noreply.github.com> --- API/Resources/ProjectFilterParamsResource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/Resources/ProjectFilterParamsResource.cs b/API/Resources/ProjectFilterParamsResource.cs index 61389d97..3899b0b6 100644 --- a/API/Resources/ProjectFilterParamsResource.cs +++ b/API/Resources/ProjectFilterParamsResource.cs @@ -47,7 +47,7 @@ public class ProjectFilterParamsResource public string SortBy { get; set; } ///

- /// Get or Set the direction of sorting by query parameter.F + /// Get or Set the direction of sorting by query parameter. /// Possible options are : asc, desc /// [FromQuery(Name = "sortDirection")] From d02b3f8ee2b87d712adf75f27d4dcc697f6520cf Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:02:55 +0200 Subject: [PATCH 58/66] added summaries to ProjectFilterParamsResource --- API/Resources/ProjectFilterParamsResource.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/API/Resources/ProjectFilterParamsResource.cs b/API/Resources/ProjectFilterParamsResource.cs index 61389d97..127d577d 100644 --- a/API/Resources/ProjectFilterParamsResource.cs +++ b/API/Resources/ProjectFilterParamsResource.cs @@ -53,9 +53,10 @@ public class ProjectFilterParamsResource [FromQuery(Name = "sortDirection")] public string SortDirection { get; set; } - // - // Get or set the array of category id's - // + /// + /// Get or set the array of category id's + /// + /// [FromQuery(Name = "categories")] public ICollection Categories { get; set; } From c2dba68e4594ba8608f7a529734be1bae40f95ae Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:16:52 +0200 Subject: [PATCH 59/66] fixed merge error --- Repositories/ProjectRepository.cs | 97 ++++++++++++------------------- 1 file changed, 37 insertions(+), 60 deletions(-) diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 6d1399db..4390326d 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -206,77 +206,54 @@ public override async Task FindAsync(int id) /// The categories parameter represents the categories the project needs to have /// This method returns a list of projects filtered by the specified parameters. public virtual async Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( - int? skip = null, - int? take = null, - Expression> orderBy = null, - bool orderByAsc = true, - bool? highlighted = null, - ICollection categories = null - ) + int? skip = null, + int? take = null, + Expression> orderBy = null, + bool orderByAsc = true, + bool? highlighted = null, + ICollection categories = null + ) { IQueryable queryableProjects = GetDbSet() - .Include(u => u.User) - .Include(p => p.ProjectIcon) - .Include(p => p.CallToAction) - .Include(p => p.Collaborators) - .Include(p => p.Likes) - .Include(p => p.LinkedInstitutions) - .Include(p => p.Images) - .Include(p => p.Categories) - .ThenInclude(c => c.Category) - //Don't get the description for performance reasons. - .Select(p => new Project - { - UserId = p.UserId, - User = p.User, - Id = p.Id, - ProjectIconId = p.ProjectIconId, - ProjectIcon = p.ProjectIcon, - CallToAction = p.CallToAction, - Collaborators = p.Collaborators, - Likes = p.Likes, - LinkedInstitutions = p.LinkedInstitutions, - Categories = p.Categories, - Created = p.Created, - InstitutePrivate = p.InstitutePrivate, - Name = p.Name, - ShortDescription = p.ShortDescription, - Updated = p.Updated, - Uri = p.Uri - }); + .Include(u => u.User) + .Include(p => p.ProjectIcon) + .Include(p => p.CallToAction) + .Include(p => p.Collaborators) + .Include(p => p.Likes) + .Include(p => p.LinkedInstitutions) + .Include(p => p.Categories) + .ThenInclude(c => c.Category); queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); //Execute the IQueryable to get a collection of results //Don't get the description for performance reasons. List projectResults = await queryableProjects.Select(p => new Project - { - UserId = p.UserId, - User = p.User, - Id = p.Id, - ProjectIconId = p.ProjectIconId, - ProjectIcon = p.ProjectIcon, - CallToAction = p.CallToAction, - Collaborators = p.Collaborators, - Likes = p.Likes, - LinkedInstitutions = p.LinkedInstitutions, - Categories = p.Categories.Select(c => new ProjectCategory() - { - Category = c.Category, - Id = c.Id - }).ToList(), - Created = p.Created, - InstitutePrivate = p.InstitutePrivate, - Name = p.Name, - ShortDescription = p.ShortDescription, - Updated = p.Updated, - Uri = p.Uri - }) + { + UserId = p.UserId, + User = p.User, + Id = p.Id, + ProjectIconId = p.ProjectIconId, + ProjectIcon = p.ProjectIcon, + CallToAction = p.CallToAction, + Collaborators = p.Collaborators, + Likes = p.Likes, + LinkedInstitutions = p.LinkedInstitutions, + Categories = p.Categories.Select(c => new ProjectCategory() + { + Category = c.Category, + Id = c.Id + }).ToList(), + Created = p.Created, + InstitutePrivate = p.InstitutePrivate, + Name = p.Name, + ShortDescription = p.ShortDescription, + Updated = p.Updated, + Uri = p.Uri + }) .ToListAsync(); - //Redact the user after fetching the collection from the project (no separate query needs to be executed) projectResults.ForEach(project => project.User = RedactUser(project.User)); - return projectResults; } From 69a442984880cba4cedc1036ac0c669b9c9dc365 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:50:38 +0200 Subject: [PATCH 60/66] update Postman tests after permissions and field name changed --- Postman/dex.postman_collection.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Postman/dex.postman_collection.json b/Postman/dex.postman_collection.json index 4a7333ba..dd4b90ba 100644 --- a/Postman/dex.postman_collection.json +++ b/Postman/dex.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "a73f56b6-95d9-4dc6-a648-7ff12a4457a5", + "_postman_id": "466dd8bf-e391-45ed-9f50-e958d0b5fa5c", "name": "DEV", "description": "Testing Digital Excellence API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -681,7 +681,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"name\": \"{{projectName}}\",\r\n \"description\": \"postmantest_ProjectToTag\",\r\n \"shortDescription\": \"postmantest_ProjectToTag\",\r\n \"uri\": \"postmantest_ProjectToTag\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToTag\",\r\n \"role\": \"postmantest_ProjectToTag\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\",\r\n \"categories\": [\r\n {\r\n \"categoryId\": {{categoryIdToBeCategorized}}\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"name\": \"{{projectName}}\",\r\n \"description\": \"postmantest_ProjectToTag\",\r\n \"shortDescription\": \"postmantest_ProjectToTag\",\r\n \"uri\": \"postmantest_ProjectToTag\",\r\n \"collaborators\": [\r\n {\r\n \"fullName\": \"postmantest_ProjectToTag\",\r\n \"role\": \"postmantest_ProjectToTag\"\r\n }\r\n ],\r\n \"created\": \"2020-04-20T08:24:26.693Z\",\r\n \"updated\": \"2020-04-20T08:24:26.693Z\",\r\n \"categories\": [\r\n {\r\n \"Id\": {{categoryIdToBeCategorized}}\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -6757,12 +6757,12 @@ "", "eval(pm.environment.get(\"commonTests\"))();", "", - "pm.test(\"Status code is 401\", function () {", - " pm.response.to.have.status(401);", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", "});", "", "pm.test(\"Response must be valid and have a json body\", function () {", - " pm.response.to.be.unauthorized;", + " pm.response.to.be.ok;", " pm.response.to.be.withBody;", " pm.response.to.be.json;", "});", @@ -6803,12 +6803,12 @@ "", "eval(pm.environment.get(\"commonTests\"))();", "", - "pm.test(\"Status code is 401\", function () {", - " pm.response.to.have.status(401);", + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", "});", "", "pm.test(\"Response must be valid and have a json body\", function () {", - " pm.response.to.be.unauthorized;", + " pm.response.to.be.ok;", " pm.response.to.be.withBody;", " pm.response.to.be.json;", "});", From 73564eb1aad6cc8576c2a982ce3a2bd1a14a3649 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 4 Jun 2021 09:18:12 +0200 Subject: [PATCH 61/66] Seed categories in production --- API/Startup.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/API/Startup.cs b/API/Startup.cs index a3bd762d..44bc3759 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -473,12 +473,6 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment if(!env.IsProduction()) { - if(!context.Category.Any()) - { - context.Category.AddRange(Seed.SeedCategories()); - context.SaveChanges(); - } - if(!context.Institution.Any()) { // Seed institutions @@ -536,6 +530,12 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment } } + if(!context.Category.Any()) + { + context.Category.AddRange(Seed.SeedCategories()); + context.SaveChanges(); + } + if(!context.WizardPage.Any()) { context.WizardPage.AddRange(Seed.SeedWizardPages()); From ae8babda511080fcb0abb9a8b3a8962cdf1311a6 Mon Sep 17 00:00:00 2001 From: Ruben Date: Fri, 4 Jun 2021 09:18:50 +0200 Subject: [PATCH 62/66] Added concept category --- Data/Helpers/Seed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Data/Helpers/Seed.cs b/Data/Helpers/Seed.cs index a1eb4d14..dd7b52e2 100644 --- a/Data/Helpers/Seed.cs +++ b/Data/Helpers/Seed.cs @@ -205,7 +205,7 @@ public static List SeedCategories() }, new Category { - Name = "Assignment" + Name = "Concept" } }); From dc7755593c461ea2c8aad4045434959009d57f9c Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Fri, 4 Jun 2021 10:01:51 +0200 Subject: [PATCH 63/66] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d680b7de..7006b881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added autocomplete suggestions for search results - [#361](https://github.com/DigitalExcellence/dex-backend/issues/361) - Added image to highlights - [#431](https://github.com/DigitalExcellence/dex-backend/issues/431) - Added functionality to add multiples images to a project - [#430](https://github.com/DigitalExcellence/dex-backend/issues/430) +- Filtering through categories - [#444](https://github.com/DigitalExcellence/dex-backend/issues/444) ### Changed From 8e3c72b3ce6f796a546141d1ba7c11a28a403de9 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Mon, 7 Jun 2021 10:28:58 +0200 Subject: [PATCH 64/66] updated version numbers for release. updated changelog for release. --- API/01_API.csproj | 2 +- CHANGELOG.md | 22 ++++++++++++------- Data/06_Data.csproj | 2 +- .../14_ElasticSynchronizer.csproj | 2 +- IdentityServer/08_IdentityServer.csproj | 2 +- JobScheduler/13_JobScheduler.csproj | 2 +- .../10_MessageBrokerPublisher.Tests.csproj | 2 +- .../09_MessageBrokerPublisher.csproj | 2 +- Models/07_Models.csproj | 2 +- .../12_NotificationSystem.Tests.csproj | 2 +- .../11_NotificationSystem.csproj | 2 +- .../05_Repositories.Tests.csproj | 2 +- Repositories/04_Repositories.csproj | 2 +- Services.Tests/03_Services.Tests.csproj | 2 +- Services/02_Services.csproj | 2 +- 15 files changed, 28 insertions(+), 22 deletions(-) diff --git a/API/01_API.csproj b/API/01_API.csproj index e31533df..183fb26b 100644 --- a/API/01_API.csproj +++ b/API/01_API.csproj @@ -6,7 +6,7 @@ .\API.xml Digital Excellence Fontys 8 - 1.3.0-beta + 1.4.0-beta diff --git a/CHANGELOG.md b/CHANGELOG.md index 7006b881..08f92456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added autocomplete suggestions for search results - [#361](https://github.com/DigitalExcellence/dex-backend/issues/361) -- Added image to highlights - [#431](https://github.com/DigitalExcellence/dex-backend/issues/431) -- Added functionality to add multiples images to a project - [#430](https://github.com/DigitalExcellence/dex-backend/issues/430) -- Filtering through categories - [#444](https://github.com/DigitalExcellence/dex-backend/issues/444) - ### Changed ### Deprecated @@ -22,12 +17,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Fixed the get all projects query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) -- Removed getting the description for the projects get all query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) - ### Security +## Release v.1.4.0-beta - 07-06-2021 + +### Added + +- Added autocomplete suggestions for search results - [#361](https://github.com/DigitalExcellence/dex-backend/issues/361) +- Added image to highlights - [#431](https://github.com/DigitalExcellence/dex-backend/issues/431) +- Added functionality to add multiples images to a project - [#430](https://github.com/DigitalExcellence/dex-backend/issues/430) +- Filtering through categories - [#444](https://github.com/DigitalExcellence/dex-backend/issues/444) + +### Fixed + +- Improved performance of the get all projects query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) +- Removed getting the description for the projects get all query - [#436](https://github.com/DigitalExcellence/dex-backend/issues/436) + ## Release v.1.2.0-beta - 15-04-2021 ### Added diff --git a/Data/06_Data.csproj b/Data/06_Data.csproj index b87ed4e6..9f7a30eb 100644 --- a/Data/06_Data.csproj +++ b/Data/06_Data.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 8 - 1.3.0-beta + 1.4.0-beta diff --git a/ElasticSynchronizer/14_ElasticSynchronizer.csproj b/ElasticSynchronizer/14_ElasticSynchronizer.csproj index f1b7499b..4d8220ad 100644 --- a/ElasticSynchronizer/14_ElasticSynchronizer.csproj +++ b/ElasticSynchronizer/14_ElasticSynchronizer.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 dotnet-ElasticSynchronizer-03111C2E-844A-4534-8446-C60B5B11289B - 1.3.0-beta + 1.4.0-beta diff --git a/IdentityServer/08_IdentityServer.csproj b/IdentityServer/08_IdentityServer.csproj index 59a841fa..d8693410 100644 --- a/IdentityServer/08_IdentityServer.csproj +++ b/IdentityServer/08_IdentityServer.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 8 - 1.3.0-beta + 1.4.0-beta diff --git a/JobScheduler/13_JobScheduler.csproj b/JobScheduler/13_JobScheduler.csproj index b0b22746..39883670 100644 --- a/JobScheduler/13_JobScheduler.csproj +++ b/JobScheduler/13_JobScheduler.csproj @@ -5,7 +5,7 @@ dotnet-JobScheduler-3CE46EAB-4F12-4C7D-A37B-BFA136C798C1 13_JobScheduler 13_JobScheduler - 1.3.0-beta + 1.4.0-beta diff --git a/MessageBrokerPublisher.Tests/10_MessageBrokerPublisher.Tests.csproj b/MessageBrokerPublisher.Tests/10_MessageBrokerPublisher.Tests.csproj index 0346c8a2..2ae4993a 100644 --- a/MessageBrokerPublisher.Tests/10_MessageBrokerPublisher.Tests.csproj +++ b/MessageBrokerPublisher.Tests/10_MessageBrokerPublisher.Tests.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 false - 1.3.0-beta + 1.4.0-beta diff --git a/MessageBrokerPublisher/09_MessageBrokerPublisher.csproj b/MessageBrokerPublisher/09_MessageBrokerPublisher.csproj index 214f3303..52fcf6c2 100644 --- a/MessageBrokerPublisher/09_MessageBrokerPublisher.csproj +++ b/MessageBrokerPublisher/09_MessageBrokerPublisher.csproj @@ -2,7 +2,7 @@ netcoreapp3.1 - 1.3.0-beta + 1.4.0-beta diff --git a/Models/07_Models.csproj b/Models/07_Models.csproj index b551f92f..402bd570 100644 --- a/Models/07_Models.csproj +++ b/Models/07_Models.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 8 - 1.3.0-beta + 1.4.0-beta diff --git a/NotificationSystem.Tests/12_NotificationSystem.Tests.csproj b/NotificationSystem.Tests/12_NotificationSystem.Tests.csproj index c6c6dc8f..4afd0aea 100644 --- a/NotificationSystem.Tests/12_NotificationSystem.Tests.csproj +++ b/NotificationSystem.Tests/12_NotificationSystem.Tests.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 false - 1.3.0-beta + 1.4.0-beta diff --git a/NotificationSystem/11_NotificationSystem.csproj b/NotificationSystem/11_NotificationSystem.csproj index e333db5b..8ad416eb 100644 --- a/NotificationSystem/11_NotificationSystem.csproj +++ b/NotificationSystem/11_NotificationSystem.csproj @@ -3,7 +3,7 @@ Exe netcoreapp3.1 - 1.3.0-beta + 1.4.0-beta diff --git a/Repositories.Tests/05_Repositories.Tests.csproj b/Repositories.Tests/05_Repositories.Tests.csproj index b25869e2..1f4943c9 100644 --- a/Repositories.Tests/05_Repositories.Tests.csproj +++ b/Repositories.Tests/05_Repositories.Tests.csproj @@ -5,7 +5,7 @@ false - 1.3.0-beta + 1.4.0-beta diff --git a/Repositories/04_Repositories.csproj b/Repositories/04_Repositories.csproj index ea60823a..a654cbf1 100644 --- a/Repositories/04_Repositories.csproj +++ b/Repositories/04_Repositories.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 8 - 1.3.0-beta + 1.4.0-beta diff --git a/Services.Tests/03_Services.Tests.csproj b/Services.Tests/03_Services.Tests.csproj index f93cd9dd..ade93a3d 100644 --- a/Services.Tests/03_Services.Tests.csproj +++ b/Services.Tests/03_Services.Tests.csproj @@ -5,7 +5,7 @@ false - 1.3.0-beta + 1.4.0-beta diff --git a/Services/02_Services.csproj b/Services/02_Services.csproj index 461feb9f..0dc4869d 100644 --- a/Services/02_Services.csproj +++ b/Services/02_Services.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 8 - 1.3.0-beta + 1.4.0-beta From 9067baabb0da98cb2b243227aaf973a02ad120c3 Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Mon, 7 Jun 2021 10:31:48 +0200 Subject: [PATCH 65/66] removed unsucceeding performance testing workflow --- .github/workflows/PerformanceTests.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/workflows/PerformanceTests.yml diff --git a/.github/workflows/PerformanceTests.yml b/.github/workflows/PerformanceTests.yml deleted file mode 100644 index 107d8527..00000000 --- a/.github/workflows/PerformanceTests.yml +++ /dev/null @@ -1,17 +0,0 @@ - -name: Execute Performance tests - -on: - push: - branches: [ develop, release/* ] - -jobs: - build: - runs-on: ubuntu-18.04 - - steps: - - uses: actions/checkout@v2 - - name: Navigate to performance test directory. - run: cd PerformanceTests - - name: Run docker compose up - run: docker-compose -f docker-compose.load-tests.yml up From 249dc0f6d34d702e80b41edffe91b71e5ba492eb Mon Sep 17 00:00:00 2001 From: Niray Mak <44868678+niraymak@users.noreply.github.com> Date: Mon, 7 Jun 2021 12:34:07 +0200 Subject: [PATCH 66/66] updated production and staging docker-compose to fix elasticsearch config --- docker-compose-production.yml | 3 +++ docker-compose-staging.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/docker-compose-production.yml b/docker-compose-production.yml index e709edc2..23ababa2 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -194,6 +194,9 @@ services: ports: - "9200:9200" - "9300:9300" + volumes: + - elasticsearch:/usr/share/elasticsearch/data + - /home/ubuntu/docker_compose/elasticonfig/wn_s.txt:/usr/share/elasticsearch/config/analysis/wn_s.txt environment: ES_JAVA_OPTS: "-Xmx256m -Xms256m" ELASTIC_PASSWORD: ${App__ElasticSearch__Password} diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index cdb218db..def20cbf 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -196,6 +196,9 @@ services: ports: - "9200:9200" - "9300:9300" + volumes: + - elasticsearch:/usr/share/elasticsearch/data + - /home/ubuntu/docker_compose/elasticonfig/wn_s.txt:/usr/share/elasticsearch/config/analysis/wn_s.txt environment: ES_JAVA_OPTS: "-Xmx256m -Xms256m" ELASTIC_PASSWORD: ${App__ElasticSearch__Password}