diff --git a/API.Tests/Controllers/ProjectControllerTests.cs b/API.Tests/Controllers/ProjectControllerTests.cs index 026296f6..a5f6046f 100644 --- a/API.Tests/Controllers/ProjectControllerTests.cs +++ b/API.Tests/Controllers/ProjectControllerTests.cs @@ -8,6 +8,9 @@ using Xunit; using API.Tests.Enums; using API.Tests.Helpers; +using API.Resources; +using API.InputOutput.Tag; +using System.Collections.Generic; namespace API.Tests.Controllers { @@ -112,5 +115,37 @@ public async Task CategorizeProject_Returns_Expected_Result_For_All_Roles(UserRo // Assert response.StatusCode.Should().Be(expectedResult); } + + [Theory] + [InlineData(UserRole.Admin, HttpStatusCode.OK)] + [InlineData(UserRole.DataOfficer, HttpStatusCode.OK)] + [InlineData(UserRole.PrUser, HttpStatusCode.OK)] + [InlineData(UserRole.RegisteredUser, HttpStatusCode.OK)] + public async Task Update_Tag_Returns_Expected_Result_For_All_Roles(UserRole role, HttpStatusCode expectedResult) + { + // Arrange + await AuthenticateAs(role); + ProjectInput projectInput = SeedUtility.RandomProjectInput(); + projectInput.Tags = new List() { + new TagInput() { Name = "java" } + }; + HttpResponseMessage postProjectResponse = await TestClient.PostAsJsonAsync("project", projectInput); + string responseContent = await postProjectResponse.Content.ReadAsStringAsync(); + + int projectId = JsonConvert.DeserializeObject(responseContent).Id; + + ProjectInput project = JsonConvert.DeserializeObject(responseContent); + + project.Tags = new List() { + new TagInput() { Name = "csharp" }, + new TagInput() { Name = "java" } + }; + + // Act + HttpResponseMessage response = await TestClient.PutAsJsonAsync("project/" + projectId, projectInput); + + // Assert + response.StatusCode.Should().Be(expectedResult); + } } } diff --git a/API.Tests/Helpers/SeedUtility.cs b/API.Tests/Helpers/SeedUtility.cs index 1ccf8ac6..8b077432 100644 --- a/API.Tests/Helpers/SeedUtility.cs +++ b/API.Tests/Helpers/SeedUtility.cs @@ -6,6 +6,7 @@ using Models; using System.Collections.Generic; using System.Linq; +using API.Resources; namespace API.Tests.Helpers { @@ -14,11 +15,11 @@ public class SeedUtility public static Project RandomProject() { Faker projectToFake = new Faker() - .RuleFor(p => p.UserId, 1) - .RuleFor(p => p.Uri, f => f.Internet.Url()) - .RuleFor(p => p.Name, f => f.Commerce.ProductName()) - .RuleFor(p => p.Description, f => f.Lorem.Sentences(10)) - .RuleFor(p => p.ShortDescription, f => f.Lorem.Sentences(1)); + .RuleFor(p => p.UserId, 1) + .RuleFor(p => p.Uri, f => f.Internet.Url()) + .RuleFor(p => p.Name, f => f.Commerce.ProductName()) + .RuleFor(p => p.Description, f => f.Lorem.Sentences(10)) + .RuleFor(p => p.ShortDescription, f => f.Lorem.Sentences(1)); Project project = projectToFake.Generate(); project.Created = DateTime.Now.AddDays(-2); project.Updated = DateTime.Now; @@ -26,6 +27,18 @@ public static Project RandomProject() return project; } + public static ProjectInput RandomProjectInput() + { + Faker projectToFake = new Faker() + .RuleFor(p => p.Uri, f => f.Internet.Url()) + .RuleFor(p => p.Name, f => f.Commerce.ProductName()) + .RuleFor(p => p.Description, f => f.Lorem.Sentences(10)) + .RuleFor(p => p.ShortDescription, f => f.Lorem.Sentences(1)); + ProjectInput project = projectToFake.Generate(); + + return project; + } + public static Category RandomCategory() { Faker categoryToFake = new Faker() @@ -35,6 +48,22 @@ public static Category RandomCategory() return category; } + public static List RandomTags() + { + List tags = new List(); + for(int i = 0; i < 3; i++) + { + Faker tagToFake = new Faker() + .RuleFor(t => t.Name, f => f.Hacker.Adjective()); + + Tag tag = tagToFake.Generate(); + + tags.Add(tag); + } + + return tags; + } + public static Highlight RandomHighlight() { Faker highlightToFake = new Faker() diff --git a/API/Configuration/MappingProfile.cs b/API/Configuration/MappingProfile.cs index f82ed145..aeac100b 100644 --- a/API/Configuration/MappingProfile.cs +++ b/API/Configuration/MappingProfile.cs @@ -15,6 +15,7 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using API.InputOutput.Tag; using API.Resources; using AutoMapper; using Models; @@ -175,6 +176,13 @@ public MappingProfile() .ForMember(dest => dest.InstititutionName, opt => opt.MapFrom(src => src.Institution.Name)) .ForMember(dest => dest.ProjectName, opt => opt.MapFrom(src => src.Project.Name)); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap(); + CreateMap() + .ForMember(q => q.Id, opt => opt.MapFrom(q => q.Tag.Id)) + .ForMember(q => q.Name, opt => opt.MapFrom(q => q.Tag.Name)); CreateExternalSourceMappingProfiles(); } diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 8b8d9a8f..ba617493 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -19,6 +19,7 @@ using API.Extensions; using API.HelperClasses; using API.InputOutput.ProjectTransferRequest; +using API.InputOutput.Tag; using API.Resources; using AutoMapper; using Microsoft.AspNetCore.Authorization; @@ -31,6 +32,7 @@ using Models.Exceptions; using SendGrid; using Serilog; +using Services; using Services.Services; using System; using System.Collections.Generic; @@ -64,6 +66,8 @@ public class ProjectController : ControllerBase private readonly IProjectInstitutionService projectInstitutionService; private readonly IInstitutionService institutionService; private readonly IProjectTransferService projectTransferService; + private readonly ITagService tagService; + private readonly IProjectTagService projectTagService; /// /// Initializes a new instance of the class /// @@ -90,6 +94,8 @@ public class ProjectController : ControllerBase /// The projectinstitution service is responsible for link projects and institutions. /// The institution service which is used to communicate with the logic layer /// /// The projectTransferservice which is used to communicate with the logic layer + /// + /// public ProjectController(IProjectService projectService, IUserService userService, IMapper mapper, @@ -102,7 +108,10 @@ public ProjectController(IProjectService projectService, IInstitutionService institutionService, ICallToActionOptionService callToActionOptionService, ICategoryService categoryService, - IProjectCategoryService projectCategoryService, IProjectTransferService projectTransferService) + IProjectCategoryService projectCategoryService, + IProjectTransferService projectTransferService, + ITagService tagService, + IProjectTagService projectTagService) { this.projectService = projectService; this.userService = userService; @@ -118,6 +127,8 @@ public ProjectController(IProjectService projectService, this.projectInstitutionService = projectInstitutionService; this.institutionService = institutionService; this.projectTransferService = projectTransferService; + this.tagService = tagService; + this.projectTagService = projectTagService; } @@ -252,15 +263,14 @@ public async Task GetAllProjects( [HttpGet("search/autocomplete")] [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), 503)] - public async Task GetAutoCompleteProjects([FromQuery(Name ="query")] string query) + public async Task GetAutoCompleteProjects([FromQuery(Name = "query")] string query) { try { List projects = await projectService.FindProjectsWhereTitleStartsWithQuery(query); List autocompleteProjectResourceResults = mapper.Map, List>(projects); return Ok(autocompleteProjectResourceResults); - } - catch(ElasticUnavailableException) + } catch(ElasticUnavailableException) { return StatusCode(503, new ProblemDetails @@ -294,8 +304,7 @@ public async Task GetProject(int projectId) ProblemDetails problem = new ProblemDetails { Title = "Failed getting project.", - Detail = - "The Id is smaller then 0 and therefore it could never be a valid project id.", + Detail = "The Id is smaller then 0 and therefore it could never be a valid project id.", Instance = "D590A4FE-FDBA-4AE5-B184-BC7395C45D4E" }; return BadRequest(problem); @@ -351,9 +360,9 @@ public async Task GetProject(int projectId) [ProducesResponseType(typeof(ProjectOutput), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] - public async Task CreateProjectAsync([FromBody] ProjectInput projectResource) + public async Task CreateProjectAsync([FromBody] ProjectInput projectInput) { - if(projectResource == null) + if(projectInput == null) { ProblemDetails problem = new ProblemDetails { @@ -364,7 +373,7 @@ public async Task CreateProjectAsync([FromBody] ProjectInput proj return BadRequest(problem); } - if(projectResource.Name.Count() > 75) + if(projectInput.Name.Count() > 75) { ProblemDetails problem = new ProblemDetails { @@ -375,9 +384,9 @@ public async Task CreateProjectAsync([FromBody] ProjectInput proj return BadRequest(problem); } - if(projectResource.CallToActions != null) + if(projectInput.CallToActions != null) { - if(projectResource.CallToActions.GroupBy(cta => cta.OptionValue).Any(cta => cta.Count() > 1)) + if(projectInput.CallToActions.GroupBy(cta => cta.OptionValue).Any(cta => cta.Count() > 1)) { ProblemDetails problem = new ProblemDetails { @@ -388,17 +397,17 @@ public async Task CreateProjectAsync([FromBody] ProjectInput proj return BadRequest(problem); } - if(projectResource.CallToActions.Count > projectResource.MaximumCallToActions) + if(projectInput.CallToActions.Count > projectInput.MaximumCallToActions) { ProblemDetails problem = new ProblemDetails { - Title = $"Maximum amount of {projectResource.MaximumCallToActions} call to actions exceeded.", - Detail = $"It is not possible to create a project with more than {projectResource.MaximumCallToActions} call to actions.", + Title = $"Maximum amount of {projectInput.MaximumCallToActions} call to actions exceeded.", + Detail = $"It is not possible to create a project with more than {projectInput.MaximumCallToActions} call to actions.", Instance = "E780005D-BBEB-423E-BA01-58145D3DBDF5" }; return BadRequest(problem); } - foreach(CallToActionInput callToAction in projectResource.CallToActions) + foreach(CallToActionInput callToAction in projectInput.CallToActions) { IEnumerable callToActionOptions = await callToActionOptionService.GetCallToActionOptionFromValueAsync( @@ -414,13 +423,13 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } } - + } - Project project = mapper.Map(projectResource); - Models.File file = await fileService.FindAsync(projectResource.IconId); + Project project = mapper.Map(projectInput); + Models.File file = await fileService.FindAsync(projectInput.IconId); - if(projectResource.IconId != 0 && + if(projectInput.IconId != 0 && file == null) { ProblemDetails problem = new ProblemDetails @@ -432,7 +441,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } - if(projectResource.ImageIds.Count() > 10) + if(projectInput.ImageIds.Count() > 10) { ProblemDetails problem = new ProblemDetails { @@ -443,16 +452,16 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } - foreach(int projectResourceImageId in projectResource.ImageIds) + foreach(int projectResourceImageId in projectInput.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 = "B040FAAD-FD22-4C77-822E-C498DFA1A9CB" + { + Title = "Image was not found.", + Detail = "The specified image was not found while creating project.", + Instance = "B040FAAD-FD22-4C77-822E-C498DFA1A9CB" }; return BadRequest(problem); } @@ -464,9 +473,9 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( project.User = await HttpContext.GetContextUser(userService) .ConfigureAwait(false); - if(projectResource.Categories != null) + if(projectInput.Categories != null) { - ICollection projectCategoryResources = projectResource.Categories; + ICollection projectCategoryResources = projectInput.Categories; foreach(ProjectCategoryInput projectCategoryResource in projectCategoryResources) { @@ -509,13 +518,51 @@ await projectCategoryService.AddAsync(projectCategory) project.LinkedInstitutions.Add(new ProjectInstitution { Project = project, Institution = project.User.Institution }); } + if(projectInput.Tags != null) + { + // replaces duplicate tag names + List projectTagInputs = projectInput.Tags.GroupBy(item => item.Name, StringComparer.OrdinalIgnoreCase) + .Where(g => g.Count() >= 1) + .Select(g => g.Key) + .ToList(); + + //empties the tag list for new inserts + project.Tags = new List(); + + foreach(string projectTagInput in projectTagInputs) + { + Tag tag = await tagService.FindByNameAsync(projectTagInput); + + if(tag == null) + { + if(projectTagInput.Count() > 30) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Maximum amount of tag characters exceeded.", + Detail = "It is not possible to create a tag with more than 30 characters.", + Instance = "c91b56fb-1066-4c5b-95f3-40b29280be87" + }; + return BadRequest(problem); + } + + Tag newTag = new Tag {Name = projectTagInput }; + await tagService.AddAsync(newTag) + .ConfigureAwait(false); + tagService.Save(); + } + + tag = tagService.FindByName(projectTagInput); + ProjectTag projectTag = new ProjectTag(tag, project); + project.Tags.Add(projectTag); + } + } + try { projectService.Add(project); projectService.Save(); - projectCategoryService.Save(); - return Created(nameof(CreateProjectAsync), mapper.Map(project)); } catch(DbUpdateException e) { @@ -532,11 +579,14 @@ await projectCategoryService.AddAsync(projectCategory) } } + + + /// /// This method is responsible for updating the project with the specified identifier. /// /// The project identifier which is used for searching the project. - /// The project resource which is used for updating the project. + /// The project resource which is used for updating the project. /// This method returns the project resource result. /// This endpoint returns the updated project. /// The 401 Unauthorized status code is return when the user has not the correct permission to update. @@ -548,7 +598,7 @@ await projectCategoryService.AddAsync(projectCategory) [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] - public async Task UpdateProject(int projectId, [FromBody] ProjectInput projectResource) + public async Task UpdateProject(int projectId, [FromBody] ProjectInput projectInput) { Project project = await projectService.FindAsync(projectId) .ConfigureAwait(false); @@ -578,7 +628,7 @@ public async Task UpdateProject(int projectId, [FromBody] Project return Unauthorized(problem); } - if(projectResource.Name.Count() > 75) + if(projectInput.Name.Count() > 75) { ProblemDetails problem = new ProblemDetails { @@ -589,9 +639,9 @@ public async Task UpdateProject(int projectId, [FromBody] Project return BadRequest(problem); } - if(projectResource.CallToActions != null) + if(projectInput.CallToActions != null) { - if(projectResource.CallToActions.GroupBy(cta => cta.OptionValue).Any(cta => cta.Count() > 1)) + if(projectInput.CallToActions.GroupBy(cta => cta.OptionValue).Any(cta => cta.Count() > 1)) { ProblemDetails problem = new ProblemDetails { @@ -602,19 +652,19 @@ public async Task UpdateProject(int projectId, [FromBody] Project return BadRequest(problem); } - if(projectResource.CallToActions.Count > projectResource.MaximumCallToActions) + if(projectInput.CallToActions.Count > projectInput.MaximumCallToActions) { ProblemDetails problem = new ProblemDetails { - Title = $"Maximum amount of {projectResource.MaximumCallToActions} call to actions exceeded.", + Title = $"Maximum amount of {projectInput.MaximumCallToActions} call to actions exceeded.", Detail = - $"It is not possible to create a project with more than {projectResource.MaximumCallToActions} call to actions.", + $"It is not possible to create a project with more than {projectInput.MaximumCallToActions} call to actions.", Instance = "E780005D-BBEB-423E-BA01-58145D3DBDF5" }; return BadRequest(problem); } - foreach(CallToActionInput callToAction in projectResource.CallToActions) + foreach(CallToActionInput callToAction in projectInput.CallToActions) { IEnumerable callToActionOptions = await callToActionOptionService.GetCallToActionOptionFromValueAsync( @@ -632,10 +682,10 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } } - + } - if (projectResource.InstitutePrivate != project.InstitutePrivate) + if(projectInput.InstitutePrivate != project.InstitutePrivate) { ProblemDetails problem = new ProblemDetails { @@ -646,9 +696,9 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( return BadRequest(problem); } - if(projectResource.IconId != 0) + if(projectInput.IconId != 0) { - Models.File file = await fileService.FindAsync(projectResource.IconId); + Models.File file = await fileService.FindAsync(projectInput.IconId); if(file != null) { project.ProjectIcon = file; @@ -667,7 +717,7 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( project.ProjectIcon = null; } - if(projectResource.ImageIds.Count() > 10) + if(projectInput.ImageIds.Count() > 10) { ProblemDetails problem = new ProblemDetails { @@ -679,16 +729,16 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } project.Images.Clear(); - foreach(int projectResourceImageId in projectResource.ImageIds) + foreach(int projectResourceImageId in projectInput.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 updating project.", - Instance = "FC816E40-31A6-4187-BEBA-D22F06019F8F" + { + Title = "Image was not found.", + Detail = "The specified image was not found while updating project.", + Instance = "FC816E40-31A6-4187-BEBA-D22F06019F8F" }; return BadRequest(problem); } @@ -697,9 +747,9 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } await projectCategoryService.ClearProjectCategories(project); - if(projectResource.Categories != null) + if(projectInput.Categories != null) { - ICollection projectCategoryResources = projectResource.Categories; + ICollection projectCategoryResources = projectInput.Categories; foreach(ProjectCategoryInput projectCategoryResource in projectCategoryResources) { @@ -725,13 +775,69 @@ await projectCategoryService.AddAsync(projectCategory) } } } + mapper.Map(projectInput, project); + //Same like categories, remove all and then add/create every tag again + projectTagService.RemoveRange(project.Tags); + if(projectInput.Tags != null) + { + // replaces duplicate tag names + List projectTagInputs = projectInput.Tags.GroupBy(item => item.Name, StringComparer.OrdinalIgnoreCase) + .Where(g => g.Count() >= 1) + .Select(g => g.Key) + .ToList(); - mapper.Map(projectResource, project); - projectService.Update(project); - projectService.Save(); + //empties the tag list for new inserts + project.Tags = new List(); + foreach(string projectTagInput in projectTagInputs) + { + Tag tag = await tagService.FindByNameAsync(projectTagInput); - return Ok(mapper.Map(project)); + if(tag == null) + { + if(projectTagInput.Count() > 30) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Maximum amount of tag characters exceeded.", + Detail = "It is not possible to create a tag with more than 30 characters.", + Instance = "bdc17517-0b26-4bef-ad54-86c22cb107ed" + }; + return BadRequest(problem); + } + Tag newTag = new Tag { Name = projectTagInput }; + await tagService.AddAsync(newTag) + .ConfigureAwait(false); + tagService.Save(); + } + + tag = tagService.FindByName(projectTagInput); + ProjectTag projectTag = new ProjectTag(tag, project); + project.Tags.Add(projectTag); + } + } + + + try + { + projectService.Update(project); + projectService.Save(); + + return Ok(mapper.Map(project)); + } catch(DbUpdateException e) + { + Log.Logger.Error(e, "Database exception"); + + + ProblemDetails problem = new ProblemDetails + { + Title = "Failed to update new project.", + Detail = "There was a problem while saving the project to the database.", + Instance = "9FEEF001-F91F-44E9-8090-6106703AB033" + }; + return BadRequest(problem); + } + } /// @@ -810,6 +916,8 @@ await fileService.RemoveAsync(fileToDelete.Id) await projectCategoryService.ClearProjectCategories(project); + await projectTagService.ClearProjectTags(project); + await projectService.RemoveAsync(projectId) .ConfigureAwait(false); projectService.Save(); diff --git a/API/Controllers/TagController.cs b/API/Controllers/TagController.cs new file mode 100644 index 00000000..64eddc33 --- /dev/null +++ b/API/Controllers/TagController.cs @@ -0,0 +1,220 @@ +using API.InputOutput.Tag; +using API.Resources; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Models; +using Serilog; +using Services.Services; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using static Models.Defaults.Defaults; + +namespace API.Controllers +{ + + /// + /// This class is responsible for handling HTTP requests that are related + /// to the roles, for example creating, retrieving, updating or deleting. + /// + [Route("api/[controller]")] + [ApiController] + public class TagController : ControllerBase + { + + private readonly IMapper mapper; + private readonly ITagService tagService; + + /// + /// Initializes a new instance of the class + /// + /// The tag service which is used to communicate with the logic layer. + /// The mapper which is used to convert the resources to the model to the resource result. + public TagController(ITagService tagService, IMapper mapper) + { + this.tagService = tagService; + this.mapper = mapper; + } + + /// + /// This method is responsible for retrieving all roles. + /// + /// This method returns a list of tag resource results. + /// This endpoint returns a list of tags. + + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] + public async Task GetAllTags() + { + IEnumerable tags = await tagService.GetAll() + .ConfigureAwait(false); + IEnumerable tagsOutput = mapper.Map, IEnumerable>(tags); + return Ok(tagsOutput); + } + + /// + /// This method is responsible for retrieving a single tag. + /// + /// This method return the tag resource result. + /// This endpoint returns the tag with the specified id. + /// The 400 Bad Request status code is returned when the specified tag id is invalid. + /// The 404 Not Found status code is returned when no tag is found with the specified tag id. + [HttpGet("id/{id}")] + [ProducesResponseType(typeof(TagOutput), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task GetTagById(int id) + { + if(id < 0) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting tag.", + Detail = "The Id is smaller then 0 and therefore it could never be a valid tag id.", + Instance = "745FFB49-5968-4F8F-AE73-D621D781FCA0" + }; + return BadRequest(problem); + } + + Tag tag = await tagService.FindAsync(id) + .ConfigureAwait(false); + if(tag == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting tag.", + Detail = "The tag could not be found in the database.", + Instance = "2BBB4058-FCD1-47A7-BB2C-FF8E6CB439DD" + }; + return NotFound(problem); + } + + return Ok(mapper.Map(tag)); + } + + /// + /// This method is responsible for retrieving a single tag. + /// + /// This method return the tag resource result. + /// This endpoint returns the tag with the specified name. + /// The 400 Bad Request status code is returned when the specified tag name is invalid. + /// The 404 Not Found status code is returned when no tag is found with the specified tag name. + [HttpGet("name/{name}")] + [ProducesResponseType(typeof(TagOutput), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task GetTagByName(string name) + { + Tag tag = await tagService.FindByNameAsync(name) + .ConfigureAwait(false); + if(tag == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting tag.", + Detail = "The tag could not be found in the database.", + Instance = "FD169337-D328-4E5B-977F-7B2E73995D33" + }; + return NotFound(problem); + } + + return Ok(mapper.Map(tag)); + } + + /// + /// This method is responsible for creating the tag. + /// + /// The tag resource which is used to create a tag. + /// This method returns the created tag resource result. + /// This endpoint returns the created tag. + /// The 400 Bad Request status code is returned when unable to create tag. + [HttpPost] + [ProducesResponseType(typeof(TagOutput), (int) HttpStatusCode.Created)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + public async Task CreateTagAsync([FromBody] TagInput tagResource) + { + if(tagResource == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed to create a new tag.", + Detail = "The specified tag resource was null", + Instance = "5ABFDFE4-7151-46AE-BD2C-32804882E796" + }; + return BadRequest(problem); + } + + if(tagResource.Name.Length > 30) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Maximum amount of tag characters exceeded.", + Detail = "It is not possible to create a tag with more than 30 characters.", + Instance = "072e9a65-04cf-4ec3-ad0a-8d42c1495068" + }; + return BadRequest(problem); + } + + Tag tag = mapper.Map(tagResource); + + try + { + await tagService.AddAsync(tag) + .ConfigureAwait(false); + tagService.Save(); + return Created(nameof(CreateTagAsync), mapper.Map(tag)); + } catch(DbUpdateException e) + { + Log.Logger.Error(e, "Database exception"); + + ProblemDetails problem = new ProblemDetails + { + Title = "Failed to save the new tag.", + Detail = "There was a problem while saving the tag to the database.", + Instance = "4370AD4D-A33B-47B8-9A1B-D11F7A137C89" + }; + return BadRequest(problem); + } + } + + /// + /// This method is responsible for deleting the tag. + /// + /// The tag identifier which is used for searching the tag. + /// This method returns status code 200. + /// This endpoint returns status code 200. Tag is deleted. + /// The 400 Bad Request status code is returned when the tag is assigned to a user. + /// The 401 Unauthorized status code is returned when the user is not allowed to delete tags. + /// The 404 Not Found status code is returned when the tag with the specified id could not be found. + [HttpDelete("{id}")] + [Authorize(Policy = nameof(Scopes.AdminProjectWrite))] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.Unauthorized)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task DeleteTag(int id) + { + Tag tag = await tagService.FindAsync(id) + .ConfigureAwait(false); + if(tag == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed to delete the tag.", + Detail = "The tag could not be found in the database.", + Instance = "0C2FA6FC-62F0-434D-B3F1-3251D60AF02C" + }; + return NotFound(problem); + } + + await tagService.RemoveAsync(tag.Id) + .ConfigureAwait(false); + tagService.Save(); + return Ok(); + } + + } + +} diff --git a/API/Extensions/DependencyInjectionExtensions.cs b/API/Extensions/DependencyInjectionExtensions.cs index 902bf405..f02e8a69 100644 --- a/API/Extensions/DependencyInjectionExtensions.cs +++ b/API/Extensions/DependencyInjectionExtensions.cs @@ -128,6 +128,12 @@ public static IServiceCollection AddServicesAndRepositories(this IServiceCollect services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddExternalDataSources(); return services; diff --git a/API/InputOutput/Project/ProjectFilterParamsInput.cs b/API/InputOutput/Project/ProjectFilterParamsInput.cs index 19fdfb80..f804d1d3 100644 --- a/API/InputOutput/Project/ProjectFilterParamsInput.cs +++ b/API/InputOutput/Project/ProjectFilterParamsInput.cs @@ -70,6 +70,12 @@ public class ProjectFilterParamsInput [FromQuery(Name = "highlighted")] public bool? Highlighted { get; set; } + /// + /// Get or set the array of tag id's + /// + [FromQuery(Name = "tags")] + public ICollection Tags { get; set; } + } } diff --git a/API/InputOutput/Project/ProjectInput.cs b/API/InputOutput/Project/ProjectInput.cs index 5541cf58..51b421c8 100644 --- a/API/InputOutput/Project/ProjectInput.cs +++ b/API/InputOutput/Project/ProjectInput.cs @@ -15,6 +15,8 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using API.InputOutput.Tag; +using Models; using System.Collections.Generic; namespace API.Resources @@ -80,7 +82,11 @@ public class ProjectInput /// This gets or sets the image ID's /// public IEnumerable ImageIds { get; set; } = new List(); - + + /// + /// This gets or sets the tags + /// + public IList Tags { get; set; } } } diff --git a/API/InputOutput/Project/ProjectOutput.cs b/API/InputOutput/Project/ProjectOutput.cs index 51b2fad1..6b6502d8 100644 --- a/API/InputOutput/Project/ProjectOutput.cs +++ b/API/InputOutput/Project/ProjectOutput.cs @@ -15,6 +15,7 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using API.InputOutput.Tag; using System; using System.Collections.Generic; @@ -112,6 +113,11 @@ public class ProjectOutput /// public List Categories { get; set; } + /// + /// This gets or sets the tags belonging to a project. + /// + public List Tags { get; set; } + } } diff --git a/API/InputOutput/Project/ProjectResultInput.cs b/API/InputOutput/Project/ProjectResultInput.cs index fe5f0222..ad1fb3b8 100644 --- a/API/InputOutput/Project/ProjectResultInput.cs +++ b/API/InputOutput/Project/ProjectResultInput.cs @@ -15,6 +15,7 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using API.InputOutput.Tag; using System; using System.Collections.Generic; @@ -82,6 +83,10 @@ public class ProjectResultInput /// public List Categories { get; set; } + /// + /// This gets or sets the tags of the project. + /// + public List Tags { get; set; } } } diff --git a/API/InputOutput/ProjectTag/ProjectTagInput.cs b/API/InputOutput/ProjectTag/ProjectTagInput.cs new file mode 100644 index 00000000..a557d1c7 --- /dev/null +++ b/API/InputOutput/ProjectTag/ProjectTagInput.cs @@ -0,0 +1,16 @@ +namespace API.Resources +{ + + /// + /// Object to retrieve from tthe frontend with the ProjectTag + /// + public class ProjectTagInput + { + + /// + /// Gets or sets the Id of the Tag. + /// + public int Id { get; set; } + } + +} diff --git a/API/InputOutput/ProjectTag/ProjectTagOutput.cs b/API/InputOutput/ProjectTag/ProjectTagOutput.cs new file mode 100644 index 00000000..178db0b9 --- /dev/null +++ b/API/InputOutput/ProjectTag/ProjectTagOutput.cs @@ -0,0 +1,22 @@ +namespace API.Resources +{ + + /// + /// Object to return to frontend with the ProjectTag + /// + public class ProjectTagOutput + { + + /// + /// Gets or sets the Id of the project tag. + /// + public int Id { get; set; } + + /// + /// Gets or sets the Name of the project tag. + /// + public string Name { get; set; } + + } + +} diff --git a/API/InputOutput/Tag/TagInput.cs b/API/InputOutput/Tag/TagInput.cs new file mode 100644 index 00000000..9be0d8fa --- /dev/null +++ b/API/InputOutput/Tag/TagInput.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace API.InputOutput.Tag +{ + public class TagInput + { + public string Name { get; set; } + } +} diff --git a/API/InputOutput/Tag/TagOutput.cs b/API/InputOutput/Tag/TagOutput.cs new file mode 100644 index 00000000..57571ec1 --- /dev/null +++ b/API/InputOutput/Tag/TagOutput.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace API.InputOutput.Tag +{ + public class TagOutput + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/API/Startup.cs b/API/Startup.cs index f0a78b03..febb0586 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -368,7 +368,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseRouting(); app.UseCors(c => { - c.WithOrigins(Config.Frontend.FrontendUrl); + c.WithOrigins(Config.Frontend.FrontendUrl, "http://plex-tool.xyz", "https://plex-tool.xyz"); c.SetIsOriginAllowedToAllowWildcardSubdomains(); c.AllowAnyHeader(); c.AllowAnyMethod(); @@ -495,7 +495,6 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment .CreateScope(); using ApplicationDbContext context = serviceScope.ServiceProvider.GetService(); - //Only apply migrations when db is running via MSSQL instead of IN Memory if(!context.Database.IsInMemory()) { @@ -528,7 +527,13 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment context.User.Add(Seed.SeedDataOfficerUser(roles)); context.SaveChanges(); } - + //Seed Tags + if(!context.Tag.Any()) + { + List tags = context.Tag.ToList(); + context.Tag.AddRange(Seed.SeedTags(tags)); + context.SaveChanges(); + } if(!context.Project.Any()) { //Seed projects diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 948046d5..1a797c5e 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -186,6 +186,16 @@ public ApplicationDbContext(DbContextOptions options) : ba /// public DbSet ProjectTransferRequest { get; set; } + /// + /// Gets or sets the Tag + /// + public DbSet Tag { get; set; } + + /// + /// Gets or sets the ProjectTags + /// + public DbSet ProjectTag {get; set;} + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/Data/Helpers/Seed.cs b/Data/Helpers/Seed.cs index dd7b52e2..e747485b 100644 --- a/Data/Helpers/Seed.cs +++ b/Data/Helpers/Seed.cs @@ -92,6 +92,25 @@ public static List SeedUsers(List roles) return users; } + /// + /// Seed random tags into the database using fake date from Bogus + /// + public static List SeedTags(List tags) + { + List result = new List(); + for(int i = 0; i < 30; i++) + { + Faker tagToFake = new Faker() + .RuleFor(t => t.Name, f => f.Hacker.Adjective()); + + Tag tag = tagToFake.Generate(); + + tags.Add(tag); + } + + return tags; + } + /// /// Seeds the roles. /// @@ -344,6 +363,7 @@ public static List SeedProjects(List users) for(int i = 0; i < 30; i++) { User user = users[r.Next(0, users.Count - 1)]; + //ProjectTag projectTag = new ProjectTag() { Id = i, Project = i, Tag = } Faker projectToFake = new Faker() .RuleFor(s => s.UserId, user.Id) .RuleFor(s => s.Uri, f => f.Internet.Url()) diff --git a/Data/Migrations/20211209095920_AddedTags.Designer.cs b/Data/Migrations/20211209095920_AddedTags.Designer.cs new file mode 100644 index 00000000..8d94a232 --- /dev/null +++ b/Data/Migrations/20211209095920_AddedTags.Designer.cs @@ -0,0 +1,821 @@ +// +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("20211209095920_AddedTags")] + partial class AddedTags + { + 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("ProjectId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + 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("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("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.ProjectTransferRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CurrentOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransferGuid") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PotentialNewOwnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectTransferRequest"); + }); + + 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.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Tag"); + }); + + 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.CallToAction", b => + { + b.HasOne("Models.Project", null) + .WithMany("CallToActions") + .HasForeignKey("ProjectId"); + }); + + 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.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.ProjectTransferRequest", b => + { + b.HasOne("Models.User", "PotentialNewOwner") + .WithMany() + .HasForeignKey("PotentialNewOwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .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.Tag", b => + { + b.HasOne("Models.Project", null) + .WithMany("Tags") + .HasForeignKey("ProjectId"); + }); + + 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/20211209095920_AddedTags.cs b/Data/Migrations/20211209095920_AddedTags.cs new file mode 100644 index 00000000..f94c9dd2 --- /dev/null +++ b/Data/Migrations/20211209095920_AddedTags.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class AddedTags : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Tag", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: true), + ProjectId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Tag", x => x.Id); + table.ForeignKey( + name: "FK_Tag_Project_ProjectId", + column: x => x.ProjectId, + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Tag_ProjectId", + table: "Tag", + column: "ProjectId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Tag"); + } + } +} diff --git a/Data/Migrations/20211209105731_UpdateProjectTags.Designer.cs b/Data/Migrations/20211209105731_UpdateProjectTags.Designer.cs new file mode 100644 index 00000000..a0c38f75 --- /dev/null +++ b/Data/Migrations/20211209105731_UpdateProjectTags.Designer.cs @@ -0,0 +1,842 @@ +// +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("20211209105731_UpdateProjectTags")] + partial class UpdateProjectTags + { + 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("ProjectId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + 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("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("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.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTag"); + }); + + modelBuilder.Entity("Models.ProjectTransferRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CurrentOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransferGuid") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PotentialNewOwnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectTransferRequest"); + }); + + 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.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tag"); + }); + + 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.CallToAction", b => + { + b.HasOne("Models.Project", null) + .WithMany("CallToActions") + .HasForeignKey("ProjectId"); + }); + + 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.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.ProjectTag", b => + { + b.HasOne("Models.Project", "Project") + .WithMany("ProjectTags") + .HasForeignKey("ProjectId"); + + b.HasOne("Models.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId"); + }); + + modelBuilder.Entity("Models.ProjectTransferRequest", b => + { + b.HasOne("Models.User", "PotentialNewOwner") + .WithMany() + .HasForeignKey("PotentialNewOwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .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/20211209105731_UpdateProjectTags.cs b/Data/Migrations/20211209105731_UpdateProjectTags.cs new file mode 100644 index 00000000..634a2fc7 --- /dev/null +++ b/Data/Migrations/20211209105731_UpdateProjectTags.cs @@ -0,0 +1,83 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class UpdateProjectTags : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Tag_Project_ProjectId", + table: "Tag"); + + migrationBuilder.DropIndex( + name: "IX_Tag_ProjectId", + table: "Tag"); + + migrationBuilder.DropColumn( + name: "ProjectId", + table: "Tag"); + + migrationBuilder.CreateTable( + name: "ProjectTag", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + TagId = table.Column(nullable: true), + ProjectId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectTag", x => x.Id); + table.ForeignKey( + name: "FK_ProjectTag_Project_ProjectId", + column: x => x.ProjectId, + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_ProjectTag_Tag_TagId", + column: x => x.TagId, + principalTable: "Tag", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProjectTag_ProjectId", + table: "ProjectTag", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectTag_TagId", + table: "ProjectTag", + column: "TagId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ProjectTag"); + + migrationBuilder.AddColumn( + name: "ProjectId", + table: "Tag", + type: "int", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Tag_ProjectId", + table: "Tag", + column: "ProjectId"); + + migrationBuilder.AddForeignKey( + name: "FK_Tag_Project_ProjectId", + table: "Tag", + column: "ProjectId", + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/Data/Migrations/20211216122156_cascadeondelete.Designer.cs b/Data/Migrations/20211216122156_cascadeondelete.Designer.cs new file mode 100644 index 00000000..384da176 --- /dev/null +++ b/Data/Migrations/20211216122156_cascadeondelete.Designer.cs @@ -0,0 +1,842 @@ +// +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("20211216122156_cascadeondelete")] + partial class cascadeondelete + { + 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("ProjectId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + 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("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("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.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTag"); + }); + + modelBuilder.Entity("Models.ProjectTransferRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CurrentOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerAcceptedRequest") + .HasColumnType("bit"); + + b.Property("PotentialNewOwnerId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransferGuid") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("PotentialNewOwnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectTransferRequest"); + }); + + 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.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tag"); + }); + + 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.CallToAction", b => + { + b.HasOne("Models.Project", null) + .WithMany("CallToActions") + .HasForeignKey("ProjectId"); + }); + + 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.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.ProjectTag", b => + { + b.HasOne("Models.Project", "Project") + .WithMany("Tags") + .HasForeignKey("ProjectId"); + + b.HasOne("Models.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId"); + }); + + modelBuilder.Entity("Models.ProjectTransferRequest", b => + { + b.HasOne("Models.User", "PotentialNewOwner") + .WithMany() + .HasForeignKey("PotentialNewOwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .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/20211216122156_cascadeondelete.cs b/Data/Migrations/20211216122156_cascadeondelete.cs new file mode 100644 index 00000000..785abbc6 --- /dev/null +++ b/Data/Migrations/20211216122156_cascadeondelete.cs @@ -0,0 +1,61 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class cascadeondelete : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ProjectTag_Project_ProjectId", + table: "ProjectTag"); + + migrationBuilder.DropForeignKey( + name: "FK_ProjectTag_Tag_TagId", + table: "ProjectTag"); + + migrationBuilder.AddForeignKey( + name: "FK_ProjectTag_Project_ProjectId", + table: "ProjectTag", + column: "ProjectId", + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ProjectTag_Tag_TagId", + table: "ProjectTag", + column: "TagId", + principalTable: "Tag", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ProjectTag_Project_ProjectId", + table: "ProjectTag"); + + migrationBuilder.DropForeignKey( + name: "FK_ProjectTag_Tag_TagId", + table: "ProjectTag"); + + migrationBuilder.AddForeignKey( + name: "FK_ProjectTag_Project_ProjectId", + table: "ProjectTag", + column: "ProjectId", + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ProjectTag_Tag_TagId", + table: "ProjectTag", + column: "TagId", + principalTable: "Tag", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 7187b44e..380a0d32 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -378,6 +378,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("ProjectLike"); }); + modelBuilder.Entity("Models.ProjectTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("TagId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("TagId"); + + b.ToTable("ProjectTag"); + }); + modelBuilder.Entity("Models.ProjectTransferRequest", b => { b.Property("Id") @@ -447,6 +469,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RoleScope"); }); + modelBuilder.Entity("Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tag"); + }); + modelBuilder.Entity("Models.User", b => { b.Property("Id") @@ -719,6 +756,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("Models.ProjectTag", b => + { + b.HasOne("Models.Project", "Project") + .WithMany("Tags") + .HasForeignKey("ProjectId"); + + b.HasOne("Models.Tag", "Tag") + .WithMany("ProjectTags") + .HasForeignKey("TagId"); + }); + modelBuilder.Entity("Models.ProjectTransferRequest", b => { b.HasOne("Models.User", "PotentialNewOwner") diff --git a/Models/Project.cs b/Models/Project.cs index 87878628..45771562 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -32,6 +32,7 @@ public Project() Collaborators = new List(); LinkedInstitutions = new List(); Images = new List(); + Tags = new List(); } public int Id { get; set; } @@ -82,6 +83,9 @@ public Project() public List Images { get; set; } + public List Tags { get; set; } + + /// /// Checks if the user can access the project based on diff --git a/Models/ProjectFilterParams.cs b/Models/ProjectFilterParams.cs index 89deeb88..abb4c109 100644 --- a/Models/ProjectFilterParams.cs +++ b/Models/ProjectFilterParams.cs @@ -62,6 +62,12 @@ public class ProjectFilterParams /// public bool? Highlighted { get; set; } + // + // Get or set the array of tag id's + // + public ICollection Tags { get; set; } + + } } diff --git a/Models/Tag.cs b/Models/Tag.cs new file mode 100644 index 00000000..51a213b7 --- /dev/null +++ b/Models/Tag.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Models +{ + public class Tag + { + public int Id { get; set; } + public string Name { get; set; } + public List ProjectTags { get; set; } + } + + public class ProjectTag + { + public ProjectTag(Tag tag, Project project) + { + this.Tag = tag; + this.Project = project; + } + + public ProjectTag(){} + + public int Id { get; set; } + public Tag Tag { get; set; } + public Project Project { get; set; } + } +} diff --git a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json index f844ddc5..de0310c3 100644 --- a/Postman/ElasticSearch/DeX_Elastic.postman_collection.json +++ b/Postman/ElasticSearch/DeX_Elastic.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "35c11209-a7f9-4792-a704-efbef8cf5b4f", + "_postman_id": "cc08c559-2f57-4721-ac7b-e79c52c4b334", "name": "DeX_Elastic", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -747,78 +747,7 @@ }, { "name": "Tests", - "item": [ - { - "name": "Get Project Recommendations", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "var jsonData = pm.response.json();\r", - "var project1 = pm.environment.get(\"projectId1\");\r", - "var project2 = pm.environment.get(\"projectId2\");\r", - "var foundProject1;\r", - "var foundProject2;\r", - "\r", - "function findProject(jsonData, id) {\r", - " for (var i = 0; i < jsonData.length; i++) {\r", - " if (jsonData[i].id == id) {\r", - " console.log(jsonData[i].id);\r", - " console.log(id);\r", - " return i;\r", - " }\r", - " }\r", - " return -1;\r", - "}\r", - "\r", - "pm.test(\"Expected projects are recommended: \", function() {\r", - " foundProject1 = findProject(jsonData, project1);\r", - " foundProject2 = findProject(jsonData, project2);\r", - " console.log(foundProject1);\r", - " console.log(foundProject2);\r", - " pm.expect(foundProject1).to.not.eql(-1);\r", - " pm.expect(foundProject2).to.not.eql(-1);\r", - "});" - ], - "type": "text/javascript" - } - }, - { - "listen": "prerequest", - "script": { - "exec": [ - "setTimeout(function(){}, [2500]);" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "IdentityId", - "value": "{{registeredUserIdentityId2}}", - "type": "text" - } - ], - "url": { - "raw": "{{apiUrl}}/api/User/projectrecommendations/2", - "host": [ - "{{apiUrl}}" - ], - "path": [ - "api", - "User", - "projectrecommendations", - "2" - ] - } - }, - "response": [] - } - ] + "item": [] }, { "name": "Cleanup", diff --git a/Repositories.Tests/DataGenerators/TagDataGenerator.cs b/Repositories.Tests/DataGenerators/TagDataGenerator.cs new file mode 100644 index 00000000..97491cd5 --- /dev/null +++ b/Repositories.Tests/DataGenerators/TagDataGenerator.cs @@ -0,0 +1,27 @@ +using Bogus; +using Models; +using Repositories.Tests.DataGenerators.Base; +using System.Collections.Generic; + +namespace Repositories.Tests.DataGenerators +{ + + /// + /// FakeDataGenerator for the tags + /// + public class TagDataGenerator : FakeDataGenerator + { + + /// + /// Initializes the TagDataGenerator + /// and define dataGenerator options + /// + public TagDataGenerator() + { + Faker = new Faker() + .RuleFor(tag => tag.Name, faker => faker.Hacker.Adjective()); + } + + } + +} diff --git a/Repositories.Tests/DataSources/TagDataSourceAttribute .cs b/Repositories.Tests/DataSources/TagDataSourceAttribute .cs new file mode 100644 index 00000000..ee968bd7 --- /dev/null +++ b/Repositories.Tests/DataSources/TagDataSourceAttribute .cs @@ -0,0 +1,58 @@ +using Models; +using NUnit.Framework.Interfaces; +using Repositories.Tests.DataGenerators; +using Repositories.Tests.DataGenerators.Base; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Repositories.Tests.DataSources +{ + + /// + /// Attribute to generate tags + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class TagDataSourceAttribute : Attribute, IParameterDataSource + { + + private readonly int amountToGenerate; + private readonly IFakeDataGenerator fakeDataGenerator; + + /// + /// Initializes tagDataSourceAttribute + /// + public TagDataSourceAttribute() + { + fakeDataGenerator = new TagDataGenerator(); + } + + /// + /// Initializes tagyDataSourceAttribute + /// and setting the amount of tags to be generated + /// + public TagDataSourceAttribute(int amount) : this() + { + amountToGenerate = amount; + } + + /// + /// Generate the data and return it + /// + /// Extra parameters given in the attribute, not in use but required due to inheritance + /// The generated data + public IEnumerable GetData(IParameterInfo parameter) + { + if(amountToGenerate <= 1) + { + return new[] {fakeDataGenerator.Generate()}; + } + List tags = fakeDataGenerator.GenerateRange(amountToGenerate) + .ToList(); + return new[] {tags}; + } + + } + +} diff --git a/Repositories.Tests/TagRepositoryTest.cs b/Repositories.Tests/TagRepositoryTest.cs new file mode 100644 index 00000000..51741023 --- /dev/null +++ b/Repositories.Tests/TagRepositoryTest.cs @@ -0,0 +1,144 @@ +using Models; +using NUnit.Framework; +using Repositories.Tests.Base; +using Repositories.Tests.DataSources; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Repositories.Tests +{ + + [TestFixture] + public class TagRepositoryTest : RepositoryTest + { + + protected new ITagRepository Repository => base.Repository; + + /// + /// + /// + /// The tag which is used as data to test. + [Test] + public async Task FindByName_one([TagDataSource] Tag tag) + { + DbContext.Add(tag); + await DbContext.SaveChangesAsync(); + + Tag retrievedTag = Repository.FindByName(tag.Name); + Assert.AreEqual(tag, + retrievedTag); + } + + [Test] + public async Task FindByNameAsync_one([TagDataSource] Tag tag) + { + DbContext.Add(tag); + await DbContext.SaveChangesAsync(); + + Tag retrievedTag = await Repository.FindByNameAsync(tag.Name); + Assert.AreEqual(tag, + retrievedTag); + } + + /// + [Test] + public override Task AddAsyncTest_GoodFlow([TagDataSource] Tag entity) + { + return base.AddAsyncTest_GoodFlow(entity); + } + + /// + [Test] + public override void AddRangeTest_BadFlow_EmptyList() + { + base.AddRangeTest_BadFlow_EmptyList(); + } + + /// + [Test] + public override void AddRangeTest_BadFlow_Null() + { + base.AddRangeTest_BadFlow_Null(); + } + + /// + [Test] + public override Task AddRangeTest_GoodFlow([TagDataSource(10)] List entities) + { + return base.AddRangeTest_GoodFlow(entities); + } + + /// + [Test] + public override void AddTest_BadFlow_Null() + { + base.AddTest_BadFlow_Null(); + } + + /// + [Test] + public override Task FindAsyncTest_BadFlow_NotExists([TagDataSource] Tag entity) + { + return base.FindAsyncTest_BadFlow_NotExists(entity); + } + + /// + [Test] + public override Task FindAsyncTest_GoodFlow([TagDataSource] Tag entity) + { + return base.FindAsyncTest_GoodFlow(entity); + } + + /// + [Test] + public override Task GetAllAsyncTest_Badflow_Empty() + { + return base.GetAllAsyncTest_Badflow_Empty(); + } + + /// + [Test] + public override Task GetAllAsyncTest_GoodFlow([TagDataSource(10)] List entities) + { + return base.GetAllAsyncTest_GoodFlow(entities); + } + + /// + [Test] + public override Task RemoveAsyncTest_BadFlow_NotExists([TagDataSource] Tag entity) + { + return base.RemoveAsyncTest_BadFlow_NotExists(entity); + } + + /// + [Test] + public override Task RemoveAsyncTest_GoodFlow([TagDataSource] Tag entity) + { + return base.RemoveAsyncTest_GoodFlow(entity); + } + + /// + [Test] + public override Task UpdateTest_BadFlow_NotExists([TagDataSource] Tag entity, + [TagDataSource] Tag updateEntity) + { + return base.UpdateTest_BadFlow_NotExists(entity, updateEntity); + } + + /// + [Test] + public override Task UpdateTest_BadFlow_Null([TagDataSource] Tag entity) + { + return base.UpdateTest_BadFlow_Null(entity); + } + + /// + [Test] + public override Task UpdateTest_GoodFlow([TagDataSource] Tag entity, [TagDataSource] Tag updateEntity) + { + return base.UpdateTest_GoodFlow(entity, updateEntity); + } + + } + +} diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 398cea09..2999c8bf 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -54,6 +54,7 @@ public interface IProjectRepository : IRepository /// /// /// + /// /// List of projects Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( int? skip = null, @@ -61,7 +62,8 @@ Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null + ICollection categories = null, + ICollection tags = null ); /// @@ -77,7 +79,7 @@ Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( /// /// The categories parameter represents the categories the project needs to have /// number of projects found - Task CountAsync(bool? highlighted = null, ICollection categories = null, int? userId = null); + Task CountAsync(bool? highlighted = null, ICollection categories = null, ICollection tags = null, int? userId = null); /// /// This interface method searches the database for projects matching the search query and parameters. @@ -97,7 +99,8 @@ Task> SearchAsync( Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null + ICollection categories = null, + ICollection tags = null ); /// @@ -107,7 +110,7 @@ Task> SearchAsync( /// 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, ICollection categories = null); + Task SearchCountAsync(string query, bool? highlighted = null, ICollection categories = null, ICollection tags = null); Task SyncProjectToES(Project project); @@ -136,7 +139,8 @@ Task> GetUserProjects(int userId, Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null); + ICollection categories = null, + ICollection tags = null); Task> GetLikedProjectsFromSimilarUser(int userId, int similarUserId); void CreateProjectIndex(); void DeleteIndex(); @@ -217,6 +221,7 @@ public override async Task FindAsync(int id) /// 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 + /// The tags parameter represents the tags 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, @@ -224,7 +229,8 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null + ICollection categories = null, + ICollection tags = null ) { IQueryable queryableProjects = GetDbSet() @@ -235,9 +241,11 @@ 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) + .Include(p => p.Tags) + .ThenInclude(t => t.Tag); - queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories); + queryableProjects = ApplyFilters(queryableProjects, skip, take, orderBy, orderByAsc, highlighted, categories, tags); //Execute the IQueryable to get a collection of results //Don't get the description for performance reasons. @@ -257,6 +265,11 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut Category = c.Category, Id = c.Id }).ToList(), + Tags = p.Tags.Select(t => new ProjectTag() + { + Tag = t.Tag, + Id = t.Id + }).ToList(), Created = p.Created, InstitutePrivate = p.InstitutePrivate, Name = p.Name, @@ -276,15 +289,15 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut /// 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, ICollection categories = null, int? userId = null) + public virtual async Task CountAsync(bool? highlighted = null, ICollection categories = null, ICollection tags = null, int? userId = null) { if(userId.HasValue) { - return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories) + return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories, tags) .Where(p => p.UserId == userId) .CountAsync(); } - return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories) + return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories, tags) .CountAsync(); } @@ -306,11 +319,12 @@ public virtual async Task> SearchAsync( Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null + ICollection categories = null, + ICollection tags = null ) { List result = - await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderByAsc, highlighted, categories) + await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderByAsc, highlighted, categories, tags) .ToListAsync(); return result.Where(p => ProjectContainsQuery(p, query)) .ToList(); @@ -323,9 +337,9 @@ await ApplyFilters(await GetProjectQueryable(query), skip, take, orderBy, orderB /// 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, ICollection categories = null) + public virtual async Task SearchCountAsync(string query, bool? highlighted = null, ICollection categories = null, ICollection tags = null) { - return await ApplyFilters(await GetProjectQueryable(query), null, null, null, true, highlighted, categories) + return await ApplyFilters(await GetProjectQueryable(query), null, null, null, true, highlighted, categories, tags) .CountAsync(); } @@ -358,6 +372,10 @@ public async Task FindWithUserCollaboratorsAndInstitutionsAsync(int id) .Include(p => p.Category) .Where(p => p.Project.Id == project.Id) .ToListAsync(); + project.Tags = await GetDbSet() + .Include(p => p.Tag) + .Where(p => p.Project.Id == project.Id) + .ToListAsync(); project.LinkedInstitutions = await GetDbSet() .Include(p => p.Institution) @@ -494,7 +512,7 @@ public async Task> GetUserProjects( Expression> orderBy = null, bool orderByAsc = true, bool? highlighted = null, - ICollection categories = null) + ICollection categories = null, ICollection tags = null) { IQueryable projects = GetDbSet() .Include(p => p.Collaborators) @@ -502,33 +520,35 @@ public async Task> GetUserProjects( .Include(p => p.Images) .Include(p => p.Categories) .ThenInclude(c => c.Category) + .Include(p => p.Tags) + .ThenInclude(t => t.Tag) .Where(p => p.UserId == userId); - projects = ApplyFilters(projects, skip, take, orderBy, orderByAsc, highlighted, categories); + projects = ApplyFilters(projects, skip, take, orderBy, orderByAsc, highlighted, categories, tags); List projectResults = await projects.Select(p => new Project - { - UserId = p.UserId, - User = p.User, - Id = p.Id, - ProjectIconId = p.ProjectIconId, - ProjectIcon = p.ProjectIcon, - CallToActions = p.CallToActions, - 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, + CallToActions = p.CallToActions, + 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(); return projectResults; } @@ -613,6 +633,7 @@ private List RedactUser(List 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. /// @@ -623,7 +644,8 @@ private IQueryable ApplyFilters( Expression> orderBy, bool orderByAsc, bool? highlighted, - ICollection categories + ICollection categories, + ICollection tags ) { if(highlighted.HasValue) @@ -646,6 +668,11 @@ ICollection categories queryable = queryable.Where(p => p.Categories.Any(cat => categories.Contains(cat.Category.Id))); } + if(tags != null && tags.Count > 0) + { + queryable = queryable.Where(p => p.Tags.Any(tag => tags.Contains(tag.Id))); + } + if(orderBy != null) { if(orderByAsc) @@ -708,6 +735,7 @@ private async Task> GetProjectQueryable(string query) projectsToReturn.Include(p => p.CallToActions).Load(); projectsToReturn.Include(p => p.Likes).Load(); projectsToReturn.Include(p => p.Categories).Load(); + projectsToReturn.Include(p => p.Tags).Load(); foreach(Project project in projectsToReturn) { @@ -721,6 +749,10 @@ private async Task> GetProjectQueryable(string query) .Include(p => p.Category) .Where(p => p.Project.Id == project.Id) .ToListAsync(); + project.Tags = await GetDbSet() + .Include(p => p.Tag) + .Where(p => p.Project.Id == project.Id) + .ToListAsync(); } return projectsToReturn; } diff --git a/Repositories/ProjectTagRepository.cs b/Repositories/ProjectTagRepository.cs new file mode 100644 index 00000000..ed695f95 --- /dev/null +++ b/Repositories/ProjectTagRepository.cs @@ -0,0 +1,57 @@ +/* +* 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 Microsoft.EntityFrameworkCore; +using Models; +using Repositories.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Repositories +{ + public interface IProjectTagRepository : IRepository + { + Task> GetProjectTags(int projectId); + } + + /// + /// This is the abstract base class of the repositories + /// + /// + public class ProjectTagRepository : Repository, IProjectTagRepository + { + + /// + /// This is the tag repository constructor + /// + /// + public ProjectTagRepository(DbContext dbContext) : base(dbContext) { } + + /// + /// Gets project tags by given projectId + /// + /// + /// + public Task> GetProjectTags(int projectId) + { + return DbSet.Where(p => p.Project.Id == projectId).ToListAsync(); + } + } +} diff --git a/Repositories/TagRepository.cs b/Repositories/TagRepository.cs new file mode 100644 index 00000000..6d076317 --- /dev/null +++ b/Repositories/TagRepository.cs @@ -0,0 +1,68 @@ +/* +* 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 Microsoft.EntityFrameworkCore; +using Models; +using Repositories.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Repositories +{ + public interface ITagRepository : IRepository + { + Task FindByNameAsync(string name); + Tag FindByName(string name); + } + + /// + /// This is the abstract base class of the repositories + /// + /// + public class TagRepository : Repository, ITagRepository + { + + /// + /// This is the tag repository constructor + /// + /// + public TagRepository(DbContext dbContext) : base(dbContext) { } + + public async Task FindByNameAsync(string name) + { + return await GetDbSet() + .Where(s => s.Name == name) + .SingleOrDefaultAsync(); + } + + public Tag FindByName(string name) + { + return GetDbSet().Where(s => s.Name == name).FirstOrDefault(); + } + + public override void Remove(Tag entity) + { + + + } + + + } +} diff --git a/Services.Tests/ProjectServiceTest.cs b/Services.Tests/ProjectServiceTest.cs index 860a9b07..6071dfb2 100644 --- a/Services.Tests/ProjectServiceTest.cs +++ b/Services.Tests/ProjectServiceTest.cs @@ -50,7 +50,7 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List project.Updated, true, null, - null)) + null, null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams @@ -72,6 +72,7 @@ public async Task GetAllWithUsersAsync_GoodFlow([ProjectDataSource(10)] List project.Updated, true, null, + null, null), Times.Once); }); @@ -95,17 +96,18 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour project => project.Created, true, null, + null, null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams - { - Page = null, - AmountOnPage = null, - Highlighted = null, - SortBy = "created", - SortDirection = "asc" - }); + { + Page = null, + AmountOnPage = null, + Highlighted = null, + SortBy = "created", + SortDirection = "asc" + }); Assert.DoesNotThrow(() => { @@ -116,6 +118,7 @@ public async Task GetAllOrderedByCreatedAscendingAsync_GoodFlow([ProjectDataSour project => project.Created, true, null, + null, null), Times.Once); }); @@ -139,17 +142,18 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource project => project.Name, false, null, + null, null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams - { - Page = null, - AmountOnPage = null, - Highlighted = null, - SortBy = "name", - SortDirection = "desc" - }); + { + Page = null, + AmountOnPage = null, + Highlighted = null, + SortBy = "name", + SortDirection = "desc" + }); Assert.DoesNotThrow(() => { @@ -160,6 +164,7 @@ public async Task GetAllOrderedByNameDescendingAsync_GoodFlow([ProjectDataSource project => project.Name, false, null, + null, null), Times.Once); }); @@ -183,17 +188,18 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

project.Updated, true, true, + null, null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams - { - Page = null, - AmountOnPage = null, - Highlighted = true, - SortBy = null, - SortDirection = "asc" - }); + { + Page = null, + AmountOnPage = null, + Highlighted = true, + SortBy = null, + SortDirection = "asc" + }); Assert.DoesNotThrow(() => { @@ -204,6 +210,7 @@ public async Task GetAllHighlightedAsync_GoodFlow([ProjectDataSource(10)] List

project.Updated, true, true, + null, null), Times.Once); }); @@ -227,17 +234,18 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List project => project.Updated, true, false, + null, null)) .Returns(Task.FromResult(projects)); List retrievedProjects = await Service.GetAllWithUsersCollaboratorsAndInstitutionsAsync(new ProjectFilterParams - { - Page = null, - AmountOnPage = null, - Highlighted = false, - SortBy = null, - SortDirection = "asc" - }); + { + Page = null, + AmountOnPage = null, + Highlighted = false, + SortBy = null, + SortDirection = "asc" + }); Assert.DoesNotThrow(() => { @@ -248,6 +256,7 @@ public async Task GetAllNoHighlightedAsync_GoodFlow([ProjectDataSource(10)] List project => project.Updated, true, false, + null, null), Times.Once); }); diff --git a/Services.Tests/SearchServiceTest.cs b/Services.Tests/SearchServiceTest.cs index 10026dda..22628a9b 100644 --- a/Services.Tests/SearchServiceTest.cs +++ b/Services.Tests/SearchServiceTest.cs @@ -70,6 +70,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_true( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -92,6 +93,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_true( project => project.Updated, true, null, + null, null), Times.Once); }); @@ -115,6 +117,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_false( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -137,6 +140,7 @@ public async Task SearchInternalProjects_goodflow_sort_direction_false( project => project.Updated, false, null, + null, null), Times.Once); }); @@ -159,6 +163,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_name( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -181,6 +186,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_name( project => project.Name, false, null, + null, null), Times.Once); }); @@ -203,6 +209,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_Created( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -225,6 +232,7 @@ public async Task SearchInternalProjects_goodflow_sortBy_Created( project => project.Created, false, null, + null, null), Times.Once); }); @@ -247,6 +255,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -269,6 +278,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page( project => project.Updated, true, null, + null, null), Times.Once); }); @@ -292,6 +302,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page_custom( It.IsAny>>(), It.IsAny(), It.IsAny(), + It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.AsEnumerable())); @@ -314,6 +325,7 @@ public async Task SearchInternalProjects_goodflow_Amount_on_Page_custom( project => project.Updated, true, null, + null, null), Times.Once); }); @@ -331,7 +343,7 @@ public async Task SearchInternalProjectsCount_highlighted_null( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -345,7 +357,7 @@ public async Task SearchInternalProjectsCount_highlighted_null( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", null, null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", null, null, null), Times.Once); }); @@ -362,7 +374,7 @@ public async Task SearchInternalProjectsCount_highlighted_true( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -376,7 +388,7 @@ public async Task SearchInternalProjectsCount_highlighted_true( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", true, null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", true, null, null), Times.Once); }); @@ -393,7 +405,7 @@ public async Task SearchInternalProjectsCount_highlighted_false( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalCount = await Service.SearchInternalProjectsCount("", @@ -407,7 +419,7 @@ public async Task SearchInternalProjectsCount_highlighted_false( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null, null), Times.Once); }); @@ -423,7 +435,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_custom_amountOnPage( [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalPages = await Service.SearchInternalProjectsTotalPages("", @@ -437,7 +449,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_custom_amountOnPage( }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null, null), Times.Once); }); @@ -454,7 +466,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_default_amountOnPage [ProjectDataSourceAttribute(10)] List projects) { RepositoryMock.Setup(repository => - repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>())) + repository.SearchCountAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>())) .Returns(Task.FromResult(projects.Count)); int totalPages = await Service.SearchInternalProjectsTotalPages("", @@ -468,7 +480,7 @@ public async Task SearchInternalProjectsTotalPages_goodflow_default_amountOnPage }); Assert.DoesNotThrow(() => { - RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null), + RepositoryMock.Verify(repository => repository.SearchCountAsync("", false, null, null), Times.Once); }); diff --git a/Services.Tests/TagServiceTest.cs b/Services.Tests/TagServiceTest.cs new file mode 100644 index 00000000..37b0e463 --- /dev/null +++ b/Services.Tests/TagServiceTest.cs @@ -0,0 +1,96 @@ +/* +* 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 FluentAssertions; +using Models; +using Moq; +using NUnit.Framework; +using Repositories; +using Repositories.Tests.DataSources; +using Services.Services; +using Services.Tests.Base; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Services.Tests +{ + + [TestFixture] + public class TagServiceTest : ServiceTest + { + + protected new ITagService Service => base.Service; + + /// + [Test] + public override void AddRangeTest_GoodFlow([TagDataSource(10)] IEnumerable entities) + { + base.AddRangeTest_GoodFlow(entities); + } + + /// + [Test] + public override void AddTest_GoodFlow([TagDataSource] Tag entity) + { + base.AddTest_GoodFlow(entity); + } + + /// + [Test] + public override Task FindAsyncTest_GoodFlow([TagDataSource] Tag entity) + { + return base.FindAsyncTest_GoodFlow(entity); + } + + /// + [Test] + public override Task GetAll([TagDataSource(10)] List entities) + { + return base.GetAll(entities); + } + + /// + [Test] + public override void Remove([TagDataSource] Tag entity) + { + base.Remove(entity); + } + + /// + [Test] + public Task RemoveAsync() + { + return base.RemoveAsync(1); + } + + /// + [Test] + public override void Save() + { + base.Save(); + } + + /// + [Test] + public override void Update([TagDataSource] Tag entity) + { + base.Update(entity); + } + + } + +} diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index 13378945..11d9719b 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -181,7 +181,8 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj orderBy, orderByDirection, projectFilterParams.Highlighted, - projectFilterParams.Categories); + projectFilterParams.Categories, + projectFilterParams.Tags); } ///

@@ -193,7 +194,7 @@ public virtual async Task ProjectsCount(ProjectFilterParams projectFilterPa { if(userId.HasValue) { - return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories, userId); + return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories, projectFilterParams.Tags, userId); } return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories); } diff --git a/Services/Services/ProjectTagService.cs b/Services/Services/ProjectTagService.cs new file mode 100644 index 00000000..ff7ca4fd --- /dev/null +++ b/Services/Services/ProjectTagService.cs @@ -0,0 +1,199 @@ +/* +* 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 Microsoft.EntityFrameworkCore; +using Models; +using Repositories; +using Repositories.Base; +using Services.Base; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Services.Services +{ + public interface IProjectTagService: IService + { + /// + /// Clear tags by given project + /// + Task ClearProjectTags(Project project); + } + + + /// + /// This is the tag service + /// + /// + public class ProjectTagService : Service, IProjectTagService + { + private readonly IProjectTagRepository repository; + /// + /// This is the tag service constructor + /// + /// + public ProjectTagService(IProjectTagRepository repository) : base(repository) + { + this.repository = repository; + } + + /// + /// Gets the database context + /// + protected DbContext DbContext { get; } + + /// + /// This is the method for finding a single entity by the identifier. + /// + /// + /// The found entity + public virtual async Task FindAsync(int id) + { + return await Repository.FindAsync(id) + .ConfigureAwait(false); + } + + + /// + /// This is the method for adding an entity. + /// + /// + public virtual void AddToProject(ProjectTag entity) + { + Repository.Add(entity); + } + + + /// + /// This is the method adding an entity asynchronous + /// + /// + /// + public virtual async Task AddAsync(ProjectTag entity) + { + await Repository.AddAsync(entity) + .ConfigureAwait(false); + } + + /// + /// This is the method for adding multiple entities at once + /// + /// + public virtual void AddRange(IEnumerable entities) + { + Repository.AddRange(entities); + } + + /// + /// This is the method for adding multiple entities at once asynchronous. + /// + /// + /// + public virtual async Task AddRangeAsync(IEnumerable entities) + { + await Repository.AddRangeAsync(entities); + } + + /// + /// This is the method to update an entity + /// + /// + public virtual void Update(ProjectTag entity) + { + Repository.Update(entity); + } + + /// + /// This methods clears all tags + /// + /// + /// + public async Task ClearProjectTags(Project project) + { + IEnumerable currentProjectTagIds = (await repository.GetProjectTags(project.Id)).Select(t => t.Id); + await Repository.RemoveRangeAsync(currentProjectTagIds); + Repository.Save(); + } + + /// + /// This is the method to remove an entity + /// + /// + public virtual void Remove(ProjectTag entity) + { + Repository.Remove(entity); + } + + /// + /// This is the method to remove an entity asynchronous + /// + /// + /// + public virtual Task RemoveAsync(int id) + { + return Repository.RemoveAsync(id); + } + + /// + /// This is the method to remove multiple entities at ones. + /// + /// + public virtual void RemoveRange(IEnumerable entities) + { + Repository.RemoveRange(entities); + } + + /// + /// This is the method to remove multiple entities at ones asynchronous. + /// + /// + /// + public virtual Task RemoveRangeAsync(IEnumerable ids) + { + return Repository.RemoveRangeAsync(ids); + } + + /// + /// This is the method to get all entities + /// + /// + public virtual async Task> GetAll() + { + return await Repository.GetAll() + .ConfigureAwait(false); + } + + /// + /// This is the method to save changes that were made + /// + public virtual void Save() + { + Repository.Save(); + } + + /// + /// This method gets the database set + /// + /// + /// Database set of entity T + protected DbSet GetDbSet() where T : class + { + return DbContext.Set(); + } + } + +} diff --git a/Services/Services/TagService.cs b/Services/Services/TagService.cs new file mode 100644 index 00000000..1e744a8d --- /dev/null +++ b/Services/Services/TagService.cs @@ -0,0 +1,88 @@ +/* +* 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 Microsoft.EntityFrameworkCore; +using Models; +using Repositories; +using Repositories.Base; +using Services.Base; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Services.Services +{ + public interface ITagService: IService + { + Task FindByNameAsync(string name); + Tag FindByName(string name); + + } + + + /// + /// This is the tag service + /// + /// + public class TagService : Service, ITagService + { + private readonly ITagRepository repository; + /// + /// This is the tag service constructor + /// + /// + public TagService(ITagRepository repository) : base(repository) + { + this.repository = repository; + } + + /// + /// Gets the database context + /// + protected DbContext DbContext { get; } + + /// + /// This is the method for finding a single entity by the name + /// + /// + /// The found entity + public virtual async Task FindByNameAsync(string name) + { + return await repository.FindByNameAsync(name).ConfigureAwait(false); + } + + /// + /// This is the method for finding a single entity by the name + /// + /// + /// The found entity + public Tag FindByName(string name) + { + return repository.FindByName(name); + } + + /// + /// This method gets the database set + /// + /// + /// Database set of entity T + protected DbSet GetDbSet() where T : class + { + return DbContext.Set(); + } + } + +}