diff --git a/.github/workflows/production-deployment.yml b/.github/workflows/production-deployment.yml index 16123239..bd003c1f 100644 --- a/.github/workflows/production-deployment.yml +++ b/.github/workflows/production-deployment.yml @@ -26,5 +26,5 @@ jobs: cd ~/docker_compose docker-compose down docker-compose pull - sleep 10s + sleep 20s docker-compose up -d diff --git a/.github/workflows/staging-deployment.yml b/.github/workflows/staging-deployment.yml index 6d091476..a3e69faa 100644 --- a/.github/workflows/staging-deployment.yml +++ b/.github/workflows/staging-deployment.yml @@ -26,5 +26,5 @@ jobs: cd ~/docker_compose docker-compose down docker-compose pull - sleep 10s + sleep 20s docker-compose up -d diff --git a/API/01_API.csproj b/API/01_API.csproj new file mode 100644 index 00000000..60fad56c --- /dev/null +++ b/API/01_API.csproj @@ -0,0 +1,60 @@ + + + + netcoreapp3.1 + API + .\API.xml + Digital Excellence Fontys + 8 + 1.1.0-beta + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + diff --git a/API/1_API.csproj b/API/1_API.csproj deleted file mode 100644 index f5f9bbd7..00000000 --- a/API/1_API.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - netcoreapp3.1 - API - .\API.xml - Digital Excellence Fontys - 8 - 1.0.1-beta - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/API/Common/AuthorizationHelper.cs b/API/Common/AuthorizationHelper.cs index 893ada4e..95b10a6d 100644 --- a/API/Common/AuthorizationHelper.cs +++ b/API/Common/AuthorizationHelper.cs @@ -21,8 +21,57 @@ namespace API.Common { + + /// + /// The interface for the authorization helper + /// + public interface IAuthorizationHelper + { + + /// + /// This method checks if a user has the correct scope to use the endpoint. + /// This method checks for a normal scope and the data officer scope within the + /// same institution. + /// + /// The user model of the logged in user. + /// The required scope for accessing this endpoint. + /// + /// The required scope for accessing this + /// endpoint for data officers within the same institution. + /// + /// + /// The id of the user owner of the property + /// which the logged in user wants to access. + /// + /// bool: true if the user is allowed, false if the user is not allowed. + public Task UserIsAllowed(User loggedInUser, + string scope, + string dataOfficerScope, + int propertyOfUserId); + + /// + /// This method checks if a user has the same institution, and both should not have null. It + /// also checks if the user has the correct institution scope that allows changes in the + /// same institution. + /// + /// The user model of the logged in user. + /// + /// The required scope for accessing this + /// endpoint for data officers within the same institution. + /// + /// + /// The id of the user owner of the property + /// which the logged in user wants to access. + /// + /// Bool: true if the user is allowed, false if the user is not allowed. + Task SameInstitutionAndInstitutionScope(User loggedInUser, + string institutionScope, + int propertyOfUserId); + + } + /// - /// The implementation for the authorization helper. + /// The implementation for the authorization helper. /// public class AuthorizationHelper : IAuthorizationHelper { @@ -30,7 +79,7 @@ public class AuthorizationHelper : IAuthorizationHelper private readonly IUserService userService; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The user service for communicating with the logic layer. public AuthorizationHelper(IUserService userService) @@ -39,18 +88,25 @@ public AuthorizationHelper(IUserService userService) } /// - /// This method checks if a user has the correct scope to use the endpoint. - /// This method checks for a normal scope and the data officer scope within the - /// same institution. + /// This method checks if a user has the correct scope to use the endpoint. + /// This method checks for a normal scope and the data officer scope within the + /// same institution. /// /// The user model of the logged in user. /// The required scope for accessing this endpoint. - /// The required scope for accessing this - /// endpoint for data officers within the same institution. - /// The id of the user owner of the property - /// which the logged in user wants to access. + /// + /// The required scope for accessing this + /// endpoint for data officers within the same institution. + /// + /// + /// The id of the user owner of the property + /// which the logged in user wants to access. + /// /// bool: true if the user is allowed, false if the user is not allowed. - public async Task UserIsAllowed(User loggedInUser, string scope, string dataOfficerScope, int propertyOfUserId) + public async Task UserIsAllowed(User loggedInUser, + string scope, + string dataOfficerScope, + int propertyOfUserId) { bool hasUserWriteScope = userService.UserHasScope(loggedInUser.IdentityId, scope); bool hasCorrectDataOfficerRights = @@ -60,17 +116,23 @@ public async Task UserIsAllowed(User loggedInUser, string scope, string da } /// - /// This method checks if a user has the same institution, and both should not have null. It - /// also checks if the user has the correct institution scope that allows changes in the - /// same institution. + /// This method checks if a user has the same institution, and both should not have null. It + /// also checks if the user has the correct institution scope that allows changes in the + /// same institution. /// /// The user model of the logged in user. - /// The required scope for accessing this - /// endpoint for data officers within the same institution. - /// The id of the user owner of the property - /// which the logged in user wants to access. + /// + /// The required scope for accessing this + /// endpoint for data officers within the same institution. + /// + /// + /// The id of the user owner of the property + /// which the logged in user wants to access. + /// /// Bool: true if the user is allowed, false if the user is not allowed. - public async Task SameInstitutionAndInstitutionScope(User loggedInUser, string institutionScope, int propertyOfUserId) + public async Task SameInstitutionAndInstitutionScope(User loggedInUser, + string institutionScope, + int propertyOfUserId) { return userService.UserHasScope(loggedInUser.IdentityId, institutionScope) && await userService.HasSameInstitution(loggedInUser.Id, propertyOfUserId); diff --git a/API/Common/IAuthorizationHelper.cs b/API/Common/IAuthorizationHelper.cs deleted file mode 100644 index 8afd6017..00000000 --- a/API/Common/IAuthorizationHelper.cs +++ /dev/null @@ -1,62 +0,0 @@ -/* -* 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 Models; -using System.Threading.Tasks; - -namespace API.Common -{ - /// - /// The interface for the authorization helper - /// - public interface IAuthorizationHelper - { - /// - /// This method checks if a user has the correct scope to use the endpoint. - /// This method checks for a normal scope and the data officer scope within the - /// same institution. - /// - /// The user model of the logged in user. - /// The required scope for accessing this endpoint. - /// The required scope for accessing this - /// endpoint for data officers within the same institution. - /// The id of the user owner of the property - /// which the logged in user wants to access. - /// bool: true if the user is allowed, false if the user is not allowed. - public Task UserIsAllowed(User loggedInUser, - string scope, - string dataOfficerScope, - int propertyOfUserId); - - /// - /// This method checks if a user has the same institution, and both should not have null. It - /// also checks if the user has the correct institution scope that allows changes in the - /// same institution. - /// - /// The user model of the logged in user. - /// The required scope for accessing this - /// endpoint for data officers within the same institution. - /// The id of the user owner of the property - /// which the logged in user wants to access. - /// Bool: true if the user is allowed, false if the user is not allowed. - Task SameInstitutionAndInstitutionScope(User loggedInUser, - string institutionScope, - int propertyOfUserId); - - } - -} diff --git a/API/Configuration/Config.cs b/API/Configuration/Config.cs index c3b53cc5..c9a95f5a 100644 --- a/API/Configuration/Config.cs +++ b/API/Configuration/Config.cs @@ -21,12 +21,14 @@ namespace API.Configuration { + /// - /// Config class + /// Config class /// /// public class Config : IValidatable { + /// /// Gets or sets the original configuration. /// @@ -52,20 +54,21 @@ public class Config : IValidatable public IdentityServerConfig IdentityServer { get; set; } /// - /// Gets or sets the swagger configuration. + /// Gets or sets the swagger configuration. /// /// - /// The swagger. + /// The swagger. /// public SwaggerConfig Swagger { get; set; } /// - /// Gets or sets the RabbitMQ configuration. + /// Gets or sets the RabbitMQ configuration. /// /// - /// The RabbitMQ. + /// The RabbitMQ. /// public RabbitMQConfig RabbitMQ { get; set; } + /// /// Validates this instance. /// @@ -76,13 +79,15 @@ public void Validate() Validator.ValidateObject(Swagger, new ValidationContext(Swagger), true); Validator.ValidateObject(RabbitMQ, new ValidationContext(RabbitMQ), true); } + } /// - /// Configuration settings for the frontend. + /// Configuration settings for the frontend. /// public class FrontendConfig { + /// /// Gets or sets the front end. /// @@ -110,13 +115,15 @@ public class FrontendConfig /// [Required] public string ClientSecret { get; set; } + } /// - /// Contains the identity server configuration. + /// Contains the identity server configuration. /// public class IdentityServerConfig { + /// /// Gets or sets the identity URL. /// @@ -126,62 +133,86 @@ public class IdentityServerConfig [Required] [Url] public string IdentityUrl { get; set; } + /// /// Gets or sets the Development identity URL. - /// This is used mostly to fix docker environments. + /// This is used mostly to fix docker environments. /// /// /// The identity URL. /// [Url] public string DevelopmentIdentityUrl { get; set; } + + /// + /// Gets or sets the client identifier. Use for authorization from the API to the IdentityServer + /// + /// + /// The client identifier + /// + public string ClientId { get; set; } + + /// + /// Gets or sets the client secret + /// + /// + /// The client secret + /// + public string ClientSecret { get; set; } + } + /// - /// Contains the swagger configuration. + /// Contains the swagger configuration. /// public class SwaggerConfig { + /// - /// Gets or sets the client identifier. + /// Gets or sets the client identifier. /// /// - /// The client identifier. + /// The client identifier. /// [Required] public string ClientId { get; set; } + } /// - /// Contains the RabbitMQConfig configuration. + /// Contains the RabbitMQConfig configuration. /// public class RabbitMQConfig { + /// - /// Gets or sets the hostname. + /// Gets or sets the hostname. /// /// - /// The hostname. + /// The hostname. /// [Required] public string Hostname { get; set; } /// - /// Gets or sets the username. + /// Gets or sets the username. /// /// - /// The username. + /// The username. /// [Required] public string Username { get; set; } /// - /// Gets or sets the password. + /// Gets or sets the password. /// /// - /// The password. + /// The password. /// [Required] public string Password { get; set; } + } + } diff --git a/API/Configuration/MappingProfile.cs b/API/Configuration/MappingProfile.cs index e5dc4169..46c544c3 100644 --- a/API/Configuration/MappingProfile.cs +++ b/API/Configuration/MappingProfile.cs @@ -18,15 +18,18 @@ using API.Resources; using AutoMapper; using Models; -using System.Collections.Generic; +using Services.ExternalDataProviders; +using Services.ExternalDataProviders.Resources; namespace API.Configuration { + /// /// This profiles adds every resource mapping. /// public class MappingProfile : Profile { + /// /// Create a map for every resource mapping. /// @@ -55,14 +58,14 @@ public MappingProfile() destination.LikedProject.Description)) .ForAllOtherMembers(member => member.Ignore()); - CreateMap(); + CreateMap(); - CreateMap() + CreateMap() .ForMember(q => q.Id, opt => opt.MapFrom(q => q.FollowedUser.Id)) .ForMember(q => q.Name, opt => opt.MapFrom(q => q.FollowedUser.Name)) .ForAllOtherMembers(o => o.Ignore()); - CreateMap() + CreateMap() .ForMember(q => q.Id, opt => opt.MapFrom(p => p.Project.Id)) .ForMember(q => q.Name, opt => opt.MapFrom(p => p.Project.Name)) .ForMember(q => q.ShortDescription, opt => opt.MapFrom(p => p.Project.ShortDescription)) @@ -71,9 +74,12 @@ public MappingProfile() .ForAllOtherMembers(o => o.Ignore()); - CreateMap(); + CreateMap() + .ForMember(q => q.UserTask, opt => opt.MapFrom(q => q.UserTasks)) + .ForMember(q => q.ExpectedGraduationDateTime, opt => opt.MapFrom(q => q.ExpectedGraduationDate)); - CreateMap(); + CreateMap() + .ForMember(q => q.ExpectedGraduationDate, opt => opt.MapFrom(q => q.ExpectedGraduationDateTime)); CreateMap(); @@ -101,8 +107,9 @@ public MappingProfile() CreateMap(); CreateMap(); - CreateMap().ForMember(e => e.UploaderUserId, - opt => opt.MapFrom(e => e.Uploader.Id)); + CreateMap() + .ForMember(e => e.UploaderUserId, + opt => opt.MapFrom(e => e.Uploader.Id)); CreateMap(); CreateMap(); @@ -110,6 +117,45 @@ public MappingProfile() CreateMap(); CreateMap(); + CreateMap() + .ForMember(e => e.UserResourceResult, + opt => opt.MapFrom(d => d.User)) + .ForMember(e => e.Id, opt => opt.MapFrom(e => e.Id)) + .ForMember(e => e.Status, opt => opt.MapFrom(e => e.Status)) + .ForMember(e => e.Type, opt => opt.MapFrom(e => e.Type)); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.WizardPages, opt => opt.MapFrom(src => src.DataSourceWizardPages)); + + CreateMap() + .ForMember(dest => dest.DataSourceWizardPages, opt => opt.MapFrom(src => src.WizardPageResources)); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.WizardPage.Id)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.WizardPage.Name)); + + CreateMap() + .ForMember(dest => dest.WizardPages, opt => opt.MapFrom(src => src.DataSourceWizardPages)); + CreateMap(); + + CreateMap(); + + CreateExternalSourceMappingProfiles(); + } + + private void CreateExternalSourceMappingProfiles() + { + CreateMap() + .ForMember(d => d.Name, opt => opt.MapFrom(m => m.Title)); + + CreateMap() + .ForMember(dest => dest.ShortDescription, opt => opt.MapFrom(src => src.Description)); + + CreateMap() + .ForMember(dest => dest.ShortDescription, opt => opt.MapFrom(src => src.Description)); + CreateMap() .ForMember(dest => dest.OptionValue, opt => opt.MapFrom(src => src.OptionValue.ToLower())); CreateMap(); @@ -118,6 +164,21 @@ public MappingProfile() .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.Type.ToLower())) .ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value.ToLower())); CreateMap(); + + CreateMap(); + CreateMap(); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.WizardPage.Id)) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.WizardPage.Name)) + .ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.WizardPage.Description)); + + CreateMap() + .ForMember(dest => dest.InstititutionName, opt => opt.MapFrom(src => src.Institution.Name)) + .ForMember(dest => dest.ProjectName, opt => opt.MapFrom(src => src.Project.Name)); + } + } + } diff --git a/API/ControllerAttributes/AllowedFileExtensionsAttribute.cs b/API/ControllerAttributes/AllowedFileExtensionsAttribute.cs new file mode 100644 index 00000000..8ef0ef33 --- /dev/null +++ b/API/ControllerAttributes/AllowedFileExtensionsAttribute.cs @@ -0,0 +1,84 @@ +/* +* 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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace API.ControllerAttributes +{ + /// + /// Attribute for settings the allowed file extensions for every single file in the request. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class AllowedFileExtensionsAttribute : ActionFilterAttribute + { + // The allowed extensions for files .jpg, .png, etc. + private readonly string[] allowedExtensions; + + /// + /// Constructor of the AllowedExtensionsAttribute consumes allowedExtensions. + /// + /// + public AllowedFileExtensionsAttribute(string[] allowedExtensions) + { + this.allowedExtensions = allowedExtensions.Select(x => x.ToLower()).ToArray(); + } + + /// + /// This method is called before the controller(action) is called. + /// + /// + /// + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if(context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + bool fileExtensionsAreValid = true; + IFormCollection form = context.HttpContext.Request.Form; + foreach(IFormFile file in form.Files) + { + string fileExtension = Path.GetExtension(file.FileName); + // Check if file extension is allowed + if(!allowedExtensions.Contains(fileExtension)) + { + fileExtensionsAreValid = false; + ProblemDetails problem = new ProblemDetails + { + Title = "Failed posting file.", + Detail = $"{fileExtension} is not accepted as a valid file extension.", + Instance = "a218b143-37a3-402b-b7e4-f5996a86428a" + }; + context.Result = new JsonResult(problem); + context.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + break; + } + } + if(fileExtensionsAreValid) + { + await next.Invoke(); + } + } + } +} diff --git a/API/ControllerAttributes/MaxFileSizeAttribute.cs b/API/ControllerAttributes/MaxFileSizeAttribute.cs new file mode 100644 index 00000000..bd2ecc6b --- /dev/null +++ b/API/ControllerAttributes/MaxFileSizeAttribute.cs @@ -0,0 +1,80 @@ +/* +* 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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using System; +using System.Threading.Tasks; + +namespace API.ControllerAttributes +{ + /// + /// Attribute for settings the maximum allowed upload size for every single file in bytes in the request. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public class MaxFileSizeAttribute : ActionFilterAttribute + { + // Max file size in bytes. + private readonly int maxFileSize; + + /// + /// Constructor of the MaxFileSizeAttribute consumes maxFileSize in bytes + /// + /// Max file size in bytes + public MaxFileSizeAttribute(int maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /// + /// This method gets called before the controller(Action) is called. + /// + /// Httpcontext + /// + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if(context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + bool fileSizeIsValid = true; + IFormCollection form = context.HttpContext.Request.Form; + foreach(IFormFile file in form.Files) + { + // Check if any files in the request exceeds the maximum file size. + if(file.Length > maxFileSize) + { + fileSizeIsValid = false; + ProblemDetails problem = new ProblemDetails + { + Title = "Failed posting file.", + Detail = $"File is exceeds max upload size of {maxFileSize} bytes.", + Instance = "92483d11-fb44-431e-a682-5b6f150d6425" + }; + context.Result = new JsonResult(problem); + context.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + break; + } + } + if(fileSizeIsValid) + { + await next.Invoke(); + } + } + } +} diff --git a/API/Controllers/CallToActionOptionController.cs b/API/Controllers/CallToActionOptionController.cs index e4fb14f9..f901dccd 100644 --- a/API/Controllers/CallToActionOptionController.cs +++ b/API/Controllers/CallToActionOptionController.cs @@ -31,9 +31,10 @@ namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the call to action options, for example creating, retrieving, updating or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to the call to action options, for example creating, retrieving, updating or deleting. /// /// [Route("api/[controller]")] @@ -41,14 +42,18 @@ namespace API.Controllers public class CallToActionOptionController : ControllerBase { - private readonly IMapper mapper; private readonly ICallToActionOptionService callToActionOptionService; + private readonly IMapper mapper; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The mapper which is used to convert the resources to the models to the resource results. - /// The call to action option service which is used to communicate with the logic layer. + /// + /// The call to action option service which is used to communicate with the logic + /// layer. + /// public CallToActionOptionController(IMapper mapper, ICallToActionOptionService callToActionOptionService) { this.mapper = mapper; @@ -56,7 +61,7 @@ public CallToActionOptionController(IMapper mapper, ICallToActionOptionService c } /// - /// This method is responsible for retrieving all the call to action options. + /// This method is responsible for retrieving all the call to action options. /// /// This method returns a list of call to action option resource results. /// This endpoint returns a list of call to action options. @@ -73,11 +78,13 @@ public async Task GetAllCallToActionOptions() } /// - /// This method is responsible for retrieving all the call to action options with - /// the specified type name. + /// This method is responsible for retrieving all the call to action options with + /// the specified type name. /// - /// The name for the call to action option type which is - /// used for searching all the call to action options. + /// + /// The name for the call to action option type which is + /// used for searching all the call to action options. + /// /// This endpoint returns a list of call to action options with the specified type /// The 400 Bad Request status code is returned when the id is invalid. /// The 404 Not Found status code is returned when the type could not be found. @@ -88,27 +95,28 @@ public async Task GetAllCallToActionOptions() [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task GetAllCallToActionOptionsFromType(string typeName) { - if (string.IsNullOrEmpty(typeName)) + if(string.IsNullOrEmpty(typeName)) { ProblemDetails problem = new ProblemDetails - { - Title = "Invalid type name specified", - Detail = "The specified type name is invalid.", - Instance = "4C5FE712-E286-43B4-8B2E-6C6BC3985F83" - }; + { + Title = "Invalid type name specified", + Detail = "The specified type name is invalid.", + Instance = "4C5FE712-E286-43B4-8B2E-6C6BC3985F83" + }; return BadRequest(problem); } IEnumerable type = await callToActionOptionService.GetCallToActionOptionsFromTypeAsync(typeName.ToLower()); - if (type == null) + if(type == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the call to action option type.", - Detail = "The database does not contain a call to action option type with the specified id.", - Instance = "8F83DE66-7CB8-49E4-A204-153C525BCA28" - }; + { + Title = "Failed getting the call to action option type.", + Detail = + "The database does not contain a call to action option type with the specified id.", + Instance = "8F83DE66-7CB8-49E4-A204-153C525BCA28" + }; return NotFound(problem); } @@ -121,14 +129,16 @@ public async Task GetAllCallToActionOptionsFromType(string typeNa } /// - /// This method is responsible for retrieving a single call to action option by id. + /// This method is responsible for retrieving a single call to action option by id. /// /// The unique identifier which is used for searching the call to action option. /// This method returns the call to action option resource result. /// This endpoint returns a call to action option with the specified id. /// The 400 Bad Request status code is returned when the id is invalid. - /// The 404 Not Found status code is returned when no call to action option could be - /// found with the specified id. + /// + /// The 404 Not Found status code is returned when no call to action option could be + /// found with the specified id. + /// [HttpGet("{id}")] [Authorize] [ProducesResponseType(typeof(CallToActionOptionResourceResult), (int) HttpStatusCode.OK)] @@ -136,26 +146,27 @@ public async Task GetAllCallToActionOptionsFromType(string typeNa [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task GetCallToActionOptionById(int id) { - if (id <= 0) + if(id <= 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Invalid Id specified", - Detail = "The specified id is invalid.", - Instance = "72702E9D-5D99-40F7-A921-033A79275877" - }; + { + Title = "Invalid Id specified", + Detail = "The specified id is invalid.", + Instance = "72702E9D-5D99-40F7-A921-033A79275877" + }; return BadRequest(problem); } CallToActionOption callToActionOption = await callToActionOptionService.FindAsync(id); - if (callToActionOption == null) + if(callToActionOption == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the call to action option.", - Detail = "The database does not contain a call to action option with the specified id.", - Instance = "1EAFDED6-74B8-4DA4-A58E-375F58AFDB2E" - }; + { + Title = "Failed getting the call to action option.", + Detail = + "The database does not contain a call to action option with the specified id.", + Instance = "1EAFDED6-74B8-4DA4-A58E-375F58AFDB2E" + }; return NotFound(problem); } @@ -165,14 +176,18 @@ public async Task GetCallToActionOptionById(int id) } /// - /// This method is responsible for creating a call to action option. + /// This method is responsible for creating a call to action option. /// - /// The call to action option resource which is used - /// to create the call to action option. + /// + /// The call to action option resource which is used + /// to create the call to action option. + /// /// This method returns the created call to action option resource result /// This endpoint returns the created call to action option. - /// The 400 Bad Request status code is returned when the specified - /// resource is invalid or the call to action option could not be saved to the database. + /// + /// The 400 Bad Request status code is returned when the specified + /// resource is invalid or the call to action option could not be saved to the database. + /// [HttpPost] [Authorize(Policy = nameof(Defaults.Scopes.CallToActionOptionWrite))] [ProducesResponseType(typeof(CallToActionOptionResourceResult), (int) HttpStatusCode.OK)] @@ -182,26 +197,26 @@ public async Task CreateCallToActionOption(CallToActionOptionReso if(callToActionOptionResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed creating the call to action option.", - Detail = "The institution resource is null.", - Instance = "E2FD8F7B-96B1-4406-9E90-138AA36B570B" - }; + { + Title = "Failed creating the call to action option.", + Detail = "The institution resource is null.", + Instance = "E2FD8F7B-96B1-4406-9E90-138AA36B570B" + }; return BadRequest(problem); } CallToActionOption option = mapper.Map(callToActionOptionResource); - if((await callToActionOptionService.GetCallToActionOptionsFromTypeAsync(option.Type)).Any() - && (await callToActionOptionService.GetCallToActionOptionFromValueAsync(option.Value)).Any()) + if((await callToActionOptionService.GetCallToActionOptionsFromTypeAsync(option.Type)).Any() && + (await callToActionOptionService.GetCallToActionOptionFromValueAsync(option.Value)).Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed creating the call to action option.", - Detail = "Identical call to action option already exists.", - Instance = "1DA7B168-FAD1-41B6-A90F-3AAEB26147CE" - }; + { + Title = "Failed creating the call to action option.", + Detail = "Identical call to action option already exists.", + Instance = "1DA7B168-FAD1-41B6-A90F-3AAEB26147CE" + }; return BadRequest(problem); } @@ -217,54 +232,61 @@ public async Task CreateCallToActionOption(CallToActionOptionReso Log.Logger.Error("Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Failed Saving the call to action option.", - Detail = "Failed saving the call to action option to the database.", - Instance = "5A1D2B14-E320-4FAE-84DF-BC02B996588B" - }; + { + Title = "Failed Saving the call to action option.", + Detail = "Failed saving the call to action option to the database.", + Instance = "5A1D2B14-E320-4FAE-84DF-BC02B996588B" + }; return BadRequest(problem); } } /// - /// This method is responsible for updating the call to action option. + /// This method is responsible for updating the call to action option. /// /// The call to action option identifier which is used to find the call to action option. - /// The call to action option resource which is used to update the call to action option. + /// + /// The call to action option resource which is used to update the call to action + /// option. + /// /// This method returns the updated call to action option resource result. /// This endpoint returns the updated call to action option. - /// The 404 Not Found status code is returned when no call to action option is - /// found with the specified call to action option id. + /// + /// The 404 Not Found status code is returned when no call to action option is + /// found with the specified call to action option id. + /// [HttpPut("{callToActionId}")] [Authorize(Policy = nameof(Defaults.Scopes.CallToActionOptionWrite))] [ProducesResponseType(typeof(CallToActionOptionResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task UpdateCallToActionOption(int callToActionId, - [FromBody] CallToActionOptionResource callToActionOptionResource) + [FromBody] + CallToActionOptionResource callToActionOptionResource) { CallToActionOption option = await callToActionOptionService.FindAsync(callToActionId); if(option == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the call to action option.", - Detail = "The database does not contain a call to action option with that id.", - Instance = "A939D6FA-4B85-4D3F-B3CC-86658713D76C" - }; + { + Title = "Failed getting the call to action option.", + Detail = + "The database does not contain a call to action option with that id.", + Instance = "A939D6FA-4B85-4D3F-B3CC-86658713D76C" + }; return NotFound(problem); } mapper.Map(callToActionOptionResource, option); - if((await callToActionOptionService.GetCallToActionOptionsFromTypeAsync(option.Type)).Any() - && (await callToActionOptionService.GetCallToActionOptionFromValueAsync(option.Value)).Any()) + if((await callToActionOptionService.GetCallToActionOptionsFromTypeAsync(option.Type)).Any() && + (await callToActionOptionService.GetCallToActionOptionFromValueAsync(option.Value)).Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed creating the call to action option.", - Detail = "Identical call to action option already exists.", - Instance = "1DA7B168-FAD1-41B6-A90F-3AAEB26147CE" - }; + { + Title = "Failed creating the call to action option.", + Detail = "Identical call to action option already exists.", + Instance = "1DA7B168-FAD1-41B6-A90F-3AAEB26147CE" + }; return BadRequest(problem); } @@ -275,13 +297,18 @@ public async Task UpdateCallToActionOption(int callToActionId, } /// - /// This method is responsible for deleting the call to action option by the identifier. + /// This method is responsible for deleting the call to action option by the identifier. /// - /// The call to action option identifier which is used to find the - /// call to action option. + /// + /// The call to action option identifier which is used to find the + /// call to action option. + /// /// This method returns status code 200. /// This endpoint returns status code 200. The call to action option is deleted. - /// The 404 Not Found status code is returned when no call to action option is found with the specified id. + /// + /// The 404 Not Found status code is returned when no call to action option is found with the + /// specified id. + /// [HttpDelete("{id}")] [Authorize(Policy = nameof(Defaults.Scopes.CallToActionOptionWrite))] [ProducesResponseType((int) HttpStatusCode.OK)] @@ -292,11 +319,12 @@ public async Task DeleteCallToActionOption(int id) if(option == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the call to action option.", - Detail = "The database does not contain a call to action option with that id.", - Instance = "6BDD3202-AE32-4CC1-AA87-42A2870CE8E6" - }; + { + Title = "Failed getting the call to action option.", + Detail = + "The database does not contain a call to action option with that id.", + Instance = "6BDD3202-AE32-4CC1-AA87-42A2870CE8E6" + }; return NotFound(problem); } @@ -306,4 +334,5 @@ public async Task DeleteCallToActionOption(int id) } } + } diff --git a/API/Controllers/DataSourceController.cs b/API/Controllers/DataSourceController.cs new file mode 100644 index 00000000..d88c9b87 --- /dev/null +++ b/API/Controllers/DataSourceController.cs @@ -0,0 +1,287 @@ +/* +* 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 API.HelperClasses; +using API.Resources; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Models; +using Models.Defaults; +using Services.ExternalDataProviders; +using Services.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace API.Controllers +{ + + /// + /// This class is responsible for handling HTTP requests that are related to the data sources, for example retrieving + /// and updating. + /// + [Route("api/[controller]")] + [ApiController] + public class DataSourceController : ControllerBase + { + + private readonly IDataProviderService dataProviderService; + private readonly IDataSourceModelService dataSourceModelService; + private readonly IFileService fileService; + private readonly IFileUploader fileUploader; + private readonly IIndexOrderHelper indexOrderHelper; + + private readonly IMapper mapper; + private readonly IWizardPageService wizardPageService; + + /// + /// Initializes a new instance of the class. + /// + /// The mapper which is used to convert the resources to the models to the resource results. + /// The data source model service which is used to communicate with the logic layer. + /// The file service which is used to communicate with the logic layer. + /// The file uploader service which is used for uploading files. + /// The data provider service which is used to communicate with the logic layer. + /// The index order helper, helps validating the index order of the wizard pages. + /// The wizard page service which is used to communicate with the logic layer. + public DataSourceController(IMapper mapper, + IDataSourceModelService dataSourceModelService, + IFileService fileService, + IFileUploader fileUploader, + IDataProviderService dataProviderService, + IIndexOrderHelper indexOrderHelper, + IWizardPageService wizardPageService) + { + this.mapper = mapper; + this.dataSourceModelService = dataSourceModelService; + this.fileService = fileService; + this.fileUploader = fileUploader; + this.dataProviderService = dataProviderService; + this.indexOrderHelper = indexOrderHelper; + this.wizardPageService = wizardPageService; + } + + + /// + /// This method is responsible for retrieving data sources. + /// + /// This parameter specifies whether the data sources should need authentication. + /// This method returns a collection of data sources. + /// This endpoint returns the available data sources with the specified flow. + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAvailableDataSources([FromQuery] bool? needsAuth) + { + IEnumerable dataSources = await dataProviderService.RetrieveDataSources(needsAuth); + IEnumerable dataSourceResourceResult = + mapper.Map, IEnumerable>(dataSources); + return Ok(dataSourceResourceResult); + } + + /// + /// This method is responsible for retrieving a data source by guid. + /// + /// The guid is used for searching the data source with this specified guid. + /// This method returns a data source with the specified guid. + /// This endpoint returns the found data source with the specified guid. + /// The 400 Bad Request status code is returned when the specified guid is invalid. + /// + /// The 404 Not Found status code is returned when no data source with the specified + /// guid could be found. + /// + [HttpGet("guid")] + [Authorize] + [ProducesResponseType(typeof(DataSourceResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetDataSourceById(string guid) + { + if(!Guid.TryParse(guid, out Guid _)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Specified guid is not valid.", + Detail = "The specified guid is not a real or valid guid.", + Instance = "64052C41-FB93-4733-918F-056C765044AE" + }; + return BadRequest(problem); + } + + IDataSourceAdaptee dataSource = await dataProviderService.RetrieveDataSourceByGuid(guid); + + if(dataSource == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "No data source with the specified guid found.", + Detail = "The database does not contain an institution with that guid.", + Instance = "3B2C12E3-CDE0-4853-A687-C1024E096479" + }; + return NotFound(problem); + } + + DataSourceResourceResult dataSourceResourceResult = + mapper.Map(dataSource); + return Ok(dataSourceResourceResult); + } + + /// + /// This method is responsible for updating the data source in the database. + /// + /// The guid parameter is used for searching the data source that should get updated. + /// + /// The data source resource contains the new data that gets used + /// for updating the specified data source. + /// + /// This method returns the updated data source resource result. + /// This endpoint returns the updated data source. + /// The 400 Bad Request status code is returned when the specified data source guid is invalid. + /// + /// The 404 Not Found status code is returned when no data source is found with the specified data source guid + /// or whenever no file is found with the specified id. + /// + [HttpPut("{guid}")] + [Authorize(Policy = nameof(Defaults.Scopes.DataSourceWrite))] + [ProducesResponseType(typeof(DataSourceResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task UpdateDataSource(string guid, [FromBody] DataSourceResource dataSourceResource) + { + if(!Guid.TryParse(guid, out Guid _)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Specified guid is not valid.", + Detail = "The specified guid is not a real or valid guid.", + Instance = "F472CEEC-BBC7-41A7-87C9-24B669DB9D80" + }; + return BadRequest(problem); + } + + DataSource dataSourceModel = await dataSourceModelService.GetDataSourceByGuid(guid); + + if(dataSourceModel == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed retrieving the data source.", + Detail = "The database does not contain an institution with that guid.", + Instance = "031FE0E3-D8CF-4DEC-81D5-E89B33BED8D0" + }; + return NotFound(problem); + } + + DataSource dataSourceWithSpecifiedName = + await dataSourceModelService.GetDataSourceByName(dataSourceResource.Title); + + if(dataSourceWithSpecifiedName != null && + dataSourceWithSpecifiedName.Guid != guid) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Specified name of the data source already exists", + Detail = + "Another data source already has the specified name, no doubles are allowed", + Instance = "804F134C-E679-4AF5-B602-18433F26019A" + }; + return BadRequest(problem); + } + + if(!await wizardPageService.ValidateWizardPagesExist( + dataSourceResource.WizardPageResources.Select(w => w.WizardPageId))) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Not all specified wizard pages could be found.", + Detail = "One or more specified wizard page ids don't exist", + Instance = "EF1490B0-DB22-4A0D-B6D1-D6E89192381E" + }; + return NotFound(problem); + } + + if(dataSourceResource.IconId != 0) + { + if(dataSourceModel.Icon != null) + { + File fileToDelete = await fileService.FindAsync(dataSourceModel.Icon.Id); + fileUploader.DeleteFileFromDirectory(fileToDelete); + await fileService.RemoveAsync(dataSourceModel.Icon.Id); + fileService.Save(); + } + + File file = await fileService.FindAsync(dataSourceResource.IconId); + if(file != null) + { + dataSourceModel.Icon = file; + } else + { + ProblemDetails problem = new ProblemDetails + { + Title = "File was not found.", + Detail = "The specified file was not found while updating project.", + Instance = "7A6BF2DE-A0BC-4C84-8CC4-89EC0C706EAB" + }; + return NotFound(problem); + } + } + + int[] wizardPageOrderIndexesAuthFlow = dataSourceResource.WizardPageResources?.Where(p => p.AuthFlow) + .Select(p => p.OrderIndex) + .ToArray(); + int[] wizardPageOrderIndexesPublicFlow = dataSourceResource.WizardPageResources?.Where(p => !p.AuthFlow) + .Select(p => p.OrderIndex) + .ToArray(); + + bool authFlowIsValid = wizardPageOrderIndexesAuthFlow?.Length == 0 || + indexOrderHelper.ValidateAscendingConsecutiveOrder(wizardPageOrderIndexesAuthFlow, + 1); + + bool publicFlowIsValid = wizardPageOrderIndexesPublicFlow?.Length == 0 || + indexOrderHelper.ValidateAscendingConsecutiveOrder( + wizardPageOrderIndexesPublicFlow, + 1); + + if(!authFlowIsValid || + !publicFlowIsValid) + { + ProblemDetails problem = new ProblemDetails + { + Title = "The order from the wizard page indexes is invalid.", + Detail = + "The order indexes from the wizard pages should start at 1, be consecutive and have no doubles.", + Instance = "A5F70346-8044-42AC-8BFD-76FCD108ABBE" + }; + return BadRequest(problem); + } + + mapper.Map(dataSourceResource, dataSourceModel); + + dataSourceModelService.Update(dataSourceModel); + dataSourceModelService.Save(); + + DataSource updatedDataSourceModel = await dataSourceModelService.GetDataSourceByGuid(guid); + DataSourceResourceResult model = mapper.Map(updatedDataSourceModel); + return Ok(model); + } + + } + +} diff --git a/API/Controllers/EmbedController.cs b/API/Controllers/EmbedController.cs index 4159b013..a0e2c9ae 100644 --- a/API/Controllers/EmbedController.cs +++ b/API/Controllers/EmbedController.cs @@ -33,29 +33,34 @@ namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the embedded projects, for example creating, retrieving or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to the embedded projects, for example creating, retrieving or deleting. /// /// [Route("api/[controller]")] [ApiController] public class EmbedController : ControllerBase { + + private readonly IAuthorizationHelper authorizationHelper; private readonly IEmbedService embedService; private readonly IMapper mapper; private readonly IProjectService projectService; private readonly IUserService userService; - private readonly IAuthorizationHelper authorizationHelper; /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// /// The embed service which is used to communicate with the logic layer. /// The mapper which is used to convert the resources to the models to the resource results. /// The project service which is used to communicate with the logic layer. /// The user service which is used to communicate with the logic layer. - /// The authorization helper which is used to communicate with the authorization helper class. + /// + /// The authorization helper which is used to communicate with the authorization helper + /// class. + /// public EmbedController(IEmbedService embedService, IMapper mapper, IProjectService projectService, @@ -70,7 +75,7 @@ public EmbedController(IEmbedService embedService, } /// - /// This method is responsible for retrieving all embedded projects. + /// This method is responsible for retrieving all embedded projects. /// /// This method returns a list of embedded projects resource result. /// This endpoint returns a list with embedded projects. @@ -79,20 +84,23 @@ public EmbedController(IEmbedService embedService, [Authorize(Policy = nameof(Defaults.Scopes.EmbedRead))] public async Task GetAllEmbeddedProjects() { - IEnumerable embeddedProjects= await embedService.GetEmbeddedProjectsAsync(); + IEnumerable embeddedProjects = await embedService.GetEmbeddedProjectsAsync(); - return Ok(mapper.Map, IEnumerable>(embeddedProjects)); + return Ok( + mapper.Map, IEnumerable>(embeddedProjects)); } /// - /// This method is responsible for retrieving a single embedded project. + /// This method is responsible for retrieving a single embedded project. /// /// The unique identifier which is used for searching the embedded project. /// This method returns the project resource result. /// This endpoint returns an embedded project with the specified guid. /// The 400 Bad Request status code is returned when the guid is not specified. - /// The 404 Not Found status code is returned when no project could be - /// found with the specified guid. + /// + /// The 404 Not Found status code is returned when no project could be + /// found with the specified guid. + /// [HttpGet("{guid}")] [ProducesResponseType(typeof(ProjectResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] @@ -102,11 +110,11 @@ public async Task GetEmbeddedProject(string guid) if(string.IsNullOrEmpty(guid)) { ProblemDetails problem = new ProblemDetails - { - Title = "No Guid specified.", - Detail = "There was no guid specified.", - Instance = "DA33DBE1-55DC-4574-B65F-C7A76A7309CF" - }; + { + Title = "No Guid specified.", + Detail = "There was no guid specified.", + Instance = "DA33DBE1-55DC-4574-B65F-C7A76A7309CF" + }; return BadRequest(problem); } @@ -114,45 +122,50 @@ public async Task GetEmbeddedProject(string guid) try { validGuid = new Guid(guid); - } - catch(FormatException e) + } catch(FormatException e) { - Log.Logger.Error(e,"Guid format error"); + Log.Logger.Error(e, "Guid format error"); ProblemDetails problem = new ProblemDetails - { - Title = "The given guid was not a valid guid.", - Detail = "The format of the guid was not valid, see https://github.com/DigitalExcellence/dex-backend/wiki/Specific-error-details for a detailed explanation.", - Instance = "DA33DBE1-55DC-4574-B62F-C7B76A7309CF" - }; + { + Title = "The given guid was not a valid guid.", + Detail = + "The format of the guid was not valid, see https://github.com/DigitalExcellence/dex-backend/wiki/Specific-error-details for a detailed explanation.", + Instance = "DA33DBE1-55DC-4574-B62F-C7B76A7309CF" + }; return NotFound(problem); } - EmbeddedProject embeddedProject = await embedService.FindAsync(validGuid).ConfigureAwait(false); + EmbeddedProject embeddedProject = await embedService.FindAsync(validGuid) + .ConfigureAwait(false); if(embeddedProject == null) { ProblemDetails problem = new ProblemDetails - { - Title = "No Embedded Project found.", - Detail = "There is no embedded project with this GUID.", - Instance = "DA33DBE1-55DC-4574-B62F-C7A76A7309CF" - }; + { + Title = "No Embedded Project found.", + Detail = "There is no embedded project with this GUID.", + Instance = "DA33DBE1-55DC-4574-B62F-C7A76A7309CF" + }; return NotFound(problem); } - Project project = await projectService.FindWithUserAndCollaboratorsAsync(embeddedProject.ProjectId); + Project project = await projectService.FindWithUserCollaboratorsAndInstitutionsAsync(embeddedProject.ProjectId); return Ok(mapper.Map(project)); } /// - /// This method is responsible for creating an embedded project. + /// This method is responsible for creating an embedded project. /// /// The embed resource which is used to create an embedded project /// This method return the embedded project resource result. /// This endpoint returns the created embedded project. - /// The 400 Bad Request status code is returned when the specified - /// resource is invalid or the project could not be saved to the database. - /// The 401 Unauthorized status code is returned when the user - /// is not allowed to create an embed project. + /// + /// The 400 Bad Request status code is returned when the specified + /// resource is invalid or the project could not be saved to the database. + /// + /// + /// The 401 Unauthorized status code is returned when the user + /// is not allowed to create an embed project. + /// [HttpPost] [Authorize] [ProducesResponseType(typeof(EmbeddedProjectResourceResult), (int) HttpStatusCode.Created)] @@ -163,11 +176,11 @@ public async Task CreateEmbeddedProject(EmbeddedProjectResource e if(embedResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "the embed resource is not valid.", - Detail = "The embed resource is null.", - Instance = "48C4A6DD-30AD-434F-BE98-694AA9F80140" - }; + { + Title = "the embed resource is not valid.", + Detail = "The embed resource is null.", + Instance = "48C4A6DD-30AD-434F-BE98-694AA9F80140" + }; return BadRequest(problem); } EmbeddedProject embeddedProject = mapper.Map(embedResource); @@ -176,11 +189,11 @@ public async Task CreateEmbeddedProject(EmbeddedProjectResource e if(project == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Project does not exist.", - Detail = "There is no project with this project ID.", - Instance = "644FE34C-FC98-4BE9-8BB7-D0773409F636" - }; + { + Title = "Project does not exist.", + Detail = "There is no project with this project ID.", + Instance = "644FE34C-FC98-4BE9-8BB7-D0773409F636" + }; return BadRequest(problem); } @@ -191,11 +204,12 @@ public async Task CreateEmbeddedProject(EmbeddedProjectResource e if(!(project.UserId == user.Id || isAllowed)) { ProblemDetails problem = new ProblemDetails - { - Title = "User is not allowed to create an embed project.", - Detail = "The user does not own the project and does not have enough privileges to add an embed project.", - Instance = "D6E83BEC-D9FA-4C86-9FA7-7D74DE0F5B23" - }; + { + Title = "User is not allowed to create an embed project.", + Detail = + "The user does not own the project and does not have enough privileges to add an embed project.", + Instance = "D6E83BEC-D9FA-4C86-9FA7-7D74DE0F5B23" + }; return Unauthorized(problem); } @@ -214,31 +228,36 @@ public async Task CreateEmbeddedProject(EmbeddedProjectResource e { embedService.Add(embeddedProject); embedService.Save(); - return Created(nameof(CreateEmbeddedProject), mapper.Map(embeddedProject)); + return Created(nameof(CreateEmbeddedProject), + mapper.Map(embeddedProject)); } catch(DbUpdateException e) { - Log.Logger.Error(e,"Database exception"); + Log.Logger.Error(e, "Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Could not create the Embedded project.", - Detail = "The database failed to save the embed project.", - Instance = "D481A8DD-B507-4AC5-A2CB-16EBEF758097" - }; + { + Title = "Could not create the Embedded project.", + Detail = "The database failed to save the embed project.", + Instance = "D481A8DD-B507-4AC5-A2CB-16EBEF758097" + }; return BadRequest(problem); } } /// - /// This method is responsible for deleting the embedded project. + /// This method is responsible for deleting the embedded project. /// /// The unique identifier which is used for searching the embedded project. /// This method returns status code 200. /// This endpoint returns status code 200. The embedded project is deleted. - /// The 401 Unauthorized status code is returned when the user - /// is not allowed to delete the embedded project . - /// The 404 Not Found status code is returned when the embedded - /// project could not be found with the specified guid. + /// + /// The 401 Unauthorized status code is returned when the user + /// is not allowed to delete the embedded project . + /// + /// + /// The 404 Not Found status code is returned when the embedded + /// project could not be found with the specified guid. + /// [HttpDelete("{guid}")] [Authorize] [ProducesResponseType((int) HttpStatusCode.OK)] @@ -247,14 +266,14 @@ public async Task CreateEmbeddedProject(EmbeddedProjectResource e public async Task DeleteEmbeddedProject(string guid) { EmbeddedProject embeddedProject = await embedService.FindAsync(new Guid(guid)); - if( embeddedProject == null) + if(embeddedProject == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Embedded project not found.", - Detail = "There was no embedded project found with this GUID.", - Instance = "35730158-1DED-4767-9C70-253C7A975715" - }; + { + Title = "Embedded project not found.", + Detail = "There was no embedded project found with this GUID.", + Instance = "35730158-1DED-4767-9C70-253C7A975715" + }; return NotFound(problem); } @@ -268,11 +287,12 @@ public async Task DeleteEmbeddedProject(string guid) if(!(embeddedProject.User.IdentityId == identity || isAllowed)) { ProblemDetails problem = new ProblemDetails - { - Title = "User is not allowed to delete the embedded project.", - Detail = "The user does not own the project and does not have enough privileges to delete an embed project.", - Instance = "35730158-1DED-4767-9C70-253C7A975715" - }; + { + Title = "User is not allowed to delete the embedded project.", + Detail = + "The user does not own the project and does not have enough privileges to delete an embed project.", + Instance = "35730158-1DED-4767-9C70-253C7A975715" + }; return Unauthorized(problem); } @@ -280,5 +300,7 @@ public async Task DeleteEmbeddedProject(string guid) embedService.Save(); return Ok(); } + } + } diff --git a/API/Controllers/FileController.cs b/API/Controllers/FileController.cs index 8cd6ab68..25dac51d 100644 --- a/API/Controllers/FileController.cs +++ b/API/Controllers/FileController.cs @@ -20,23 +20,24 @@ using API.Resources; using AutoMapper; using Microsoft.AspNetCore.Authorization; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Models; using Models.Defaults; using Models.Exceptions; using Services.Services; using System; +using System.Collections.Generic; using System.IO; using System.Net; +using System.Threading.Tasks; using File = Models.File; namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to file uploading, for example creating, retrieving or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to file uploading, for example creating, retrieving or deleting. /// [Route("api/[controller]")] [ApiController] @@ -44,17 +45,21 @@ public class FileController : ControllerBase { private readonly IFileService fileService; + private readonly IFileUploader fileUploader; private readonly IMapper mapper; private readonly IUserService userService; - private readonly IFileUploader fileUploader; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The file service. /// The mapper. /// The User service /// The file uploader extension - public FileController(IFileService fileService, IMapper mapper, IFileUploader fileUploader, IUserService userService) + public FileController(IFileService fileService, + IMapper mapper, + IFileUploader fileUploader, + IUserService userService) { this.fileService = fileService; this.mapper = mapper; @@ -63,7 +68,7 @@ public FileController(IFileService fileService, IMapper mapper, IFileUploader fi } /// - /// This method is responsible for retrieving all files + /// This method is responsible for retrieving all files /// /// A response and list of files. /// This endpoint returns all projects. @@ -78,7 +83,7 @@ public async Task GetFilesAsync() } /// - /// This method is responsible for uploading a file + /// This method is responsible for uploading a file /// /// This methods return status code 200 /// This endpoint returns all files. @@ -86,6 +91,8 @@ public async Task GetFilesAsync() [HttpPost] [Authorize] [Consumes("multipart/form-data")] + [ControllerAttributes.AllowedFileExtensions(new string[] { ".jpeg", ".png", ".jpg", ".gif" })] + [ControllerAttributes.MaxFileSize(2097152)] [ProducesResponseType(typeof(FileResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] public async Task UploadSingleFile([FromForm] FileResource fileResource) @@ -119,20 +126,20 @@ public async Task UploadSingleFile([FromForm] FileResource fileRe } catch(FileExistException fileExistException) { ProblemDetails problem = new ProblemDetails - { - Title = fileExistException.Message, - Detail = "Please rename filename.", - Instance = "D902F8C6-23FF-4506-B272-C757BD709464" + { + Title = fileExistException.Message, + Detail = "Please rename filename.", + Instance = "D902F8C6-23FF-4506-B272-C757BD709464" }; - return BadRequest(problem); + return BadRequest(problem); } } /// - /// Find file by id + /// Find file by id /// /// - ///This endpoint returns one single file. + /// This endpoint returns one single file. /// File [HttpGet("{fileId}")] [ProducesResponseType(typeof(FileResourceResult), (int) HttpStatusCode.OK)] @@ -156,7 +163,7 @@ public async Task GetSingleFile(int fileId) } /// - /// Deletes single file + /// Deletes single file /// /// /// This endpoint deletes one single file. @@ -177,10 +184,10 @@ public async Task DeleteSingleFile(int fileId) if(file == null) { ProblemDetails problem = new ProblemDetails - { - Title = "File was not found.", - Detail = "File was not found.", - Instance = "9D3830A2-E7D1-4610-A147-1D43BFB8DDBC" + { + Title = "File was not found.", + Detail = "File was not found.", + Instance = "9D3830A2-E7D1-4610-A147-1D43BFB8DDBC" }; return NotFound(problem); } @@ -189,35 +196,34 @@ public async Task DeleteSingleFile(int fileId) if(!(file.Uploader.Id.Equals(user.Id) || isAllowed)) { ProblemDetails problem = new ProblemDetails - { - Title = "Not authorized.", - Detail = "You do not have the required permissions to delete this file.", - Instance = "88967A6F-B168-44E2-A8E7-E9EBD555940E" - }; + { + Title = "Not authorized.", + Detail = "You do not have the required permissions to delete this file.", + Instance = "88967A6F-B168-44E2-A8E7-E9EBD555940E" + }; return Unauthorized(problem); - } try { await fileService.RemoveAsync(fileId) - .ConfigureAwait(false); + .ConfigureAwait(false); fileService.Save(); fileUploader.DeleteFileFromDirectory(file); return Ok(); } catch(FileNotFoundException) { ProblemDetails problem = new ProblemDetails - { - Title = "File could not be deleted because the path does not exist.", - Detail = "File could not be found.", - Instance = "436349B4-50D9-49FD-8618-82367BEB7941" - }; + { + Title = "File could not be deleted because the path does not exist.", + Detail = "File could not be found.", + Instance = "436349B4-50D9-49FD-8618-82367BEB7941" + }; return NotFound(problem); - } - + } } } + } diff --git a/API/Controllers/HighlightController.cs b/API/Controllers/HighlightController.cs index e89670bb..7d102f6c 100644 --- a/API/Controllers/HighlightController.cs +++ b/API/Controllers/HighlightController.cs @@ -24,7 +24,6 @@ using Models.Defaults; using Serilog; using Services.Services; -using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -32,19 +31,21 @@ namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the highlights, for example creating, retrieving, updating or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to the highlights, for example creating, retrieving, updating or deleting. /// [Route("api/[controller]")] [ApiController] public class HighlightController : ControllerBase { + private readonly IHighlightService highlightService; private readonly IMapper mapper; /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// /// The highlight service which is used to communicate with the logic layer. /// The mapper which is used to convert the resources to the models to resource results. @@ -55,7 +56,7 @@ public HighlightController(IHighlightService highlightService, IMapper mapper) } /// - /// This method is responsible for retrieving all active highlights. + /// This method is responsible for retrieving all active highlights. /// /// This method returns a list of highlight resource results. /// This endpoint returns a list highlights. @@ -69,7 +70,7 @@ public async Task GetAllHighlights() } /// - /// This method is responsible for retrieving a single highlight by the identifier. + /// This method is responsible for retrieving a single highlight by the identifier. /// /// The highlight identifier which is used to find the highlight. /// This method returns a highlight resource result. @@ -85,11 +86,11 @@ public async Task GetHighlight(int highlightId) if(highlightId < 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting highlight.", - Detail = "the highlight id cannot be smaller then 0.", - Instance = "BBC86ABA-142B-4BEB-801B-03100C08500B" - }; + { + Title = "Failed getting highlight.", + Detail = "the highlight id cannot be smaller then 0.", + Instance = "BBC86ABA-142B-4BEB-801B-03100C08500B" + }; return BadRequest(problem); } @@ -97,11 +98,11 @@ public async Task GetHighlight(int highlightId) if(highlight == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting highlight.", - Detail = "The database does not contain a highlight with this id.", - Instance = "1EA0824A-D017-4BAB-9606-2872487D1EDA" - }; + { + Title = "Failed getting highlight.", + Detail = "The database does not contain a highlight with this id.", + Instance = "1EA0824A-D017-4BAB-9606-2872487D1EDA" + }; return NotFound(problem); } @@ -109,13 +110,16 @@ public async Task GetHighlight(int highlightId) } /// - /// This method is responsible for retrieving all highlight by project id. + /// This method is responsible for retrieving all highlight by project id. /// /// The project identifier which is used to retrieve the corresponding highlights. /// /// This endpoint returns a list of highlights from a project. /// The 400 Bad Request status code is returned when the specified project id is not valid. - /// The 404 Not Found status code is return when there are no highlight found with the specified project id. + /// + /// The 404 Not Found status code is return when there are no highlight found with the specified + /// project id. + /// [HttpGet("Project/{projectId}")] [Authorize(Policy = nameof(Defaults.Scopes.HighlightRead))] [ProducesResponseType(typeof(IEnumerable), (int) HttpStatusCode.OK)] @@ -126,35 +130,37 @@ public async Task GetHighlightsByProjectId(int projectId) if(projectId < 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting highlights.", - Detail = "The project id cannot be smaller than 0.", - Instance = "744F5E01-FC84-4D4A-9A73-0D7C48886A30" - }; + { + Title = "Failed getting highlights.", + Detail = "The project id cannot be smaller than 0.", + Instance = "744F5E01-FC84-4D4A-9A73-0D7C48886A30" + }; return BadRequest(problem); } IEnumerable highlights = await highlightService.GetHighlightsByProjectIdAsync(projectId); if(!highlights.Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting highlights.", - Detail = "The database does not contain highlights with this project id.", - Instance = "D8D040F1-7B29-40AF-910B-D1B1CE809ADC" - }; + { + Title = "Failed getting highlights.", + Detail = "The database does not contain highlights with this project id.", + Instance = "D8D040F1-7B29-40AF-910B-D1B1CE809ADC" + }; return NotFound(problem); } return Ok(mapper.Map, IEnumerable>(highlights)); } /// - /// This method is responsible for creating a highlight. + /// This method is responsible for creating a highlight. /// /// The highlight resource which is used to create the highlight. /// This method returns the created highlight resource result. /// This endpoint returns the created highlight. - /// The 400 Bad Request status code is returned when the specified - /// resource is invalid or the highlight could not be saved to the database. + /// + /// The 400 Bad Request status code is returned when the specified + /// resource is invalid or the highlight could not be saved to the database. + /// [HttpPost] [Authorize(Policy = nameof(Defaults.Scopes.HighlightWrite))] [ProducesResponseType(typeof(HighlightResourceResult), (int) HttpStatusCode.Created)] @@ -164,11 +170,11 @@ public IActionResult CreateHighlight(HighlightResource highlightResource) if(highlightResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed creating highlight.", - Detail = "The highlight resource is null.", - Instance = "2206D5EC-9E20-44A7-A729-0205BEA994E5" - }; + { + Title = "Failed creating highlight.", + Detail = "The highlight resource is null.", + Instance = "2206D5EC-9E20-44A7-A729-0205BEA994E5" + }; return BadRequest(problem); } @@ -184,23 +190,26 @@ public IActionResult CreateHighlight(HighlightResource highlightResource) Log.Logger.Error(e, "Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Failed Saving highlight.", - Detail = "Failed saving the highlight to the database.", - Instance = "764E41C3-06A5-47D6-9642-C9E8A0B7CFC7" - }; + { + Title = "Failed Saving highlight.", + Detail = "Failed saving the highlight to the database.", + Instance = "764E41C3-06A5-47D6-9642-C9E8A0B7CFC7" + }; return BadRequest(problem); } } /// - /// This method is responsible for updating the highlight. + /// This method is responsible for updating the highlight. /// /// The highlight identifier which is used to find the highlight. /// The highlight resource which is used to update the highlight. /// This method return the updated highlight resource result /// This endpoint returns the updated highlight. - /// The 404 Not Found status code is returned when no highlight is found with the specified highlight id. + /// + /// The 404 Not Found status code is returned when no highlight is found with the specified highlight + /// id. + /// [HttpPut("{highlightId}")] [Authorize(Policy = nameof(Defaults.Scopes.HighlightWrite))] [ProducesResponseType(typeof(HighlightResourceResult), (int) HttpStatusCode.OK)] @@ -212,11 +221,11 @@ public async Task UpdateHighlight(int highlightId, if(highlight == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the highlight.", - Detail = "The database does not contain a highlight with that id.", - Instance = "795CDB7E-DB46-4A24-8F12-7557BDD79D15" - }; + { + Title = "Failed getting the highlight.", + Detail = "The database does not contain a highlight with that id.", + Instance = "795CDB7E-DB46-4A24-8F12-7557BDD79D15" + }; return NotFound(problem); } @@ -229,7 +238,7 @@ public async Task UpdateHighlight(int highlightId, } /// - /// This method is responsible for deleting the highlight by the identifier. + /// This method is responsible for deleting the highlight by the identifier. /// /// The highlight identifier which is used to find the highlight. /// This method returns status code 200. @@ -244,16 +253,18 @@ public async Task DeleteHighlight(int highlightId) if(await highlightService.FindAsync(highlightId) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting highlight.", - Detail = "The database does not contain a highlight with that id.", - Instance = "E5B9B140-8B1C-434A-913B-4DB460342BE1" - }; + { + Title = "Failed getting highlight.", + Detail = "The database does not contain a highlight with that id.", + Instance = "E5B9B140-8B1C-434A-913B-4DB460342BE1" + }; return NotFound(problem); } await highlightService.RemoveAsync(highlightId); highlightService.Save(); return Ok(); } + } + } diff --git a/API/Controllers/InstitutionController.cs b/API/Controllers/InstitutionController.cs index defaaa0d..995887bf 100644 --- a/API/Controllers/InstitutionController.cs +++ b/API/Controllers/InstitutionController.cs @@ -33,8 +33,8 @@ namespace API.Controllers { /// - /// This class is responsible for handling HTTP requests that are related - /// to the institutions, for example creating, retrieving, updating or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to the institutions, for example creating, retrieving, updating or deleting. /// /// [Route("api/[controller]")] @@ -46,7 +46,7 @@ public class InstitutionController : ControllerBase private readonly IMapper mapper; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The institution service which is used to communicate with the logic layer. /// The mapper which is used to convert the resources to the models to the resource results. @@ -57,7 +57,7 @@ public InstitutionController(IInstitutionService institutionService, IMapper map } /// - /// This method is responsible for retrieving all the institutions. + /// This method is responsible for retrieving all the institutions. /// /// This method returns a list of institution resource results. /// This endpoint returns a list of institutions. @@ -72,11 +72,11 @@ public async Task GetAllInstitutions() if(!institutions.Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "No Institutions found", - Detail = "There where no institutions scopes found.", - Instance = "7736D583-3263-4946-BF48-35299812AA56" - }; + { + Title = "No Institutions found", + Detail = "There where no institutions scopes found.", + Instance = "7736D583-3263-4946-BF48-35299812AA56" + }; return NotFound(problem); } @@ -86,14 +86,16 @@ public async Task GetAllInstitutions() } /// - /// This method is responsible for retrieving a single institution by id. + /// This method is responsible for retrieving a single institution by id. /// /// The unique identifier which is used for searching the institution. /// This method returns the institution resource result. /// This endpoint returns an institution with the specified id. /// The 400 Bad Request status code is returned when the id is invalid. - /// The 404 Not Found status code is returned when no institution could be - /// found with the specified id. + /// + /// The 404 Not Found status code is returned when no institution could be + /// found with the specified id. + /// [HttpGet("{id}")] [ProducesResponseType(typeof(InstitutionResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] @@ -104,11 +106,11 @@ public async Task GetInstitution(int id) if(id <= 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Invalid id specified.", - Detail = "The specified id is invalid.", - Instance = "17DE6E26-6759-423D-A33B-8CEC38F158A3" - }; + { + Title = "Invalid id specified.", + Detail = "The specified id is invalid.", + Instance = "17DE6E26-6759-423D-A33B-8CEC38F158A3" + }; return BadRequest(problem); } @@ -116,11 +118,12 @@ public async Task GetInstitution(int id) if(institution == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the institution.", - Detail = "The database does not contain an institution with the specified id.", - Instance = "89378B11-601A-4512-BFF9-F02FC510DF03" - }; + { + Title = "Failed getting the institution.", + Detail = + "The database does not contain an institution with the specified id.", + Instance = "89378B11-601A-4512-BFF9-F02FC510DF03" + }; return NotFound(problem); } @@ -129,13 +132,15 @@ public async Task GetInstitution(int id) } /// - /// This method is responsible for creating an institution. + /// This method is responsible for creating an institution. /// /// The institution resource which is used to create the institution. /// This method returns the created institution resource result. /// This endpoint returns the created institution. - /// The 400 Bad Request status code is returned when the specified - /// resource is invalid or the institution could not be saved to the database. + /// + /// The 400 Bad Request status code is returned when the specified + /// resource is invalid or the institution could not be saved to the database. + /// [HttpPost] [Authorize(Policy = nameof(Defaults.Scopes.InstitutionWrite))] [ProducesResponseType(typeof(InstitutionResourceResult), (int) HttpStatusCode.OK)] @@ -145,11 +150,11 @@ public IActionResult CreateInstitution(InstitutionResource institutionResource) if(institutionResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed creating the institution.", - Detail = "The institution resource is null.", - Instance = "E80F9611-EE07-4FF0-8D53-7693CE1AE26E" - }; + { + Title = "Failed creating the institution.", + Detail = "The institution resource is null.", + Instance = "E80F9611-EE07-4FF0-8D53-7693CE1AE26E" + }; return BadRequest(problem); } @@ -161,29 +166,31 @@ public IActionResult CreateInstitution(InstitutionResource institutionResource) institutionService.Save(); InstitutionResourceResult model = mapper.Map(institution); return Created(nameof(CreateInstitution), model); - } - catch(DbUpdateException e) + } catch(DbUpdateException e) { Log.Logger.Error(e, "Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Failed Saving the institution.", - Detail = "Failed saving the institution to the database.", - Instance = "20C197B7-24E1-4112-8999-6BB3DFD03FB6" - }; + { + Title = "Failed Saving the institution.", + Detail = "Failed saving the institution to the database.", + Instance = "20C197B7-24E1-4112-8999-6BB3DFD03FB6" + }; return BadRequest(problem); } } /// - /// This method is responsible for updating the institution. + /// This method is responsible for updating the institution. /// /// The institution identifier which is used to find the institution. /// The institution resource which is used to update the institution. /// This method returns the updated institution resource result. /// This endpoint returns the updated institution. - /// The 404 Not Found status code is returned when no institution is found with the specified institution id. + /// + /// The 404 Not Found status code is returned when no institution is found with the specified + /// institution id. + /// [HttpPut("{institutionId}")] [Authorize(Policy = nameof(Defaults.Scopes.InstitutionWrite))] [ProducesResponseType(typeof(InstitutionResourceResult), (int) HttpStatusCode.OK)] @@ -195,11 +202,11 @@ public async Task UpdateInstitution(int institutionId, if(institution == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the institution.", - Detail = "The database does not contain an institution with that id.", - Instance = "FA51F980-D114-44B3-85BD-9419B58D68F2" - }; + { + Title = "Failed getting the institution.", + Detail = "The database does not contain an institution with that id.", + Instance = "FA51F980-D114-44B3-85BD-9419B58D68F2" + }; return NotFound(problem); } @@ -212,7 +219,7 @@ public async Task UpdateInstitution(int institutionId, } /// - /// This method is responsible for deleting the institution by the identifier. + /// This method is responsible for deleting the institution by the identifier. /// /// The institution identifier which is used to find the institution. /// This method returns status code 200. @@ -228,18 +235,17 @@ public async Task DeleteInstitution(int institutionId) if(institution == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the institution.", - Detail = "The database does not contain an institution with that id.", - Instance = "9981200A-B22C-42B3-8035-5D3A6B9696C8" - }; + { + Title = "Failed getting the institution.", + Detail = "The database does not contain an institution with that id.", + Instance = "9981200A-B22C-42B3-8035-5D3A6B9696C8" + }; return NotFound(problem); } await institutionService.RemoveAsync(institutionId); institutionService.Save(); return Ok(); - } } diff --git a/API/Controllers/ProjectController.cs b/API/Controllers/ProjectController.cs index 733a3a7b..68bfe4bf 100644 --- a/API/Controllers/ProjectController.cs +++ b/API/Controllers/ProjectController.cs @@ -28,7 +28,6 @@ using Models.Defaults; using Serilog; using Services.Services; -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -57,7 +56,8 @@ public class ProjectController : ControllerBase private readonly IUserProjectLikeService userProjectLikeService; private readonly IUserProjectService userProjectService; private readonly IUserService userService; - + private readonly IProjectInstitutionService projectInstitutionService; + private readonly IInstitutionService institutionService; /// /// Initializes a new instance of the class /// @@ -79,6 +79,8 @@ public class ProjectController : ControllerBase /// projects. /// /// The call to action option service is used to communicate with the logic layer. + /// The projectinstitution service is responsible for link projects and institutions. + /// The institution service which is used to communicate with the logic layer public ProjectController(IProjectService projectService, IUserService userService, IMapper mapper, @@ -87,7 +89,9 @@ public ProjectController(IProjectService projectService, IAuthorizationHelper authorizationHelper, IFileUploader fileUploader, IUserProjectService userProjectService, - ICallToActionOptionService callToActionOptionService) + ICallToActionOptionService callToActionOptionService, + IProjectInstitutionService projectInstitutionService, + IInstitutionService institutionService) { this.projectService = projectService; this.userService = userService; @@ -98,6 +102,8 @@ public ProjectController(IProjectService projectService, this.authorizationHelper = authorizationHelper; this.userProjectService = userProjectService; this.callToActionOptionService = callToActionOptionService; + this.projectInstitutionService = projectInstitutionService; + this.institutionService = institutionService; } /// @@ -116,12 +122,10 @@ public ProjectController(IProjectService projectService, public async Task GetAllProjects( [FromQuery] ProjectFilterParamsResource projectFilterParamsResource) { - - ProblemDetails problem = new ProblemDetails - { - Title = "Invalid search request." - }; + { + Title = "Invalid search request." + }; if(projectFilterParamsResource.Page != null && projectFilterParamsResource.Page < 1) { @@ -151,53 +155,48 @@ public async Task GetAllProjects( mapper.Map(projectFilterParamsResource); IEnumerable projects = - await projectService.GetAllWithUsersAndCollaboratorsAsync(projectFilterParams); + await projectService.GetAllWithUsersCollaboratorsAndInstitutionsAsync(projectFilterParams); List filteredProjects = new List(); - if(HttpContext.User.CheckIfUserIsAuthenticated()) - { - User currentUser = await HttpContext.GetContextUser(userService) - .ConfigureAwait(false); + if(HttpContext.User.CheckIfUserIsAuthenticated()) + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); - foreach(Project project in projects) + foreach(Project project in projects) + { + if(project.CanAccess(currentUser)) { - if(project.InstitutePrivate == false) - { - filteredProjects.Add(project); - } - if(project.InstitutePrivate && currentUser.InstitutionId == project.User.InstitutionId) - { - filteredProjects.Add(project); - } + filteredProjects.Add(project); } } - else - { - foreach(Project project in projects) + } else + { + foreach(Project project in projects) + { + if(project.InstitutePrivate == false) { - if(project.InstitutePrivate == false) - { - filteredProjects.Add(project); - } + filteredProjects.Add(project); } } + } IEnumerable results = mapper.Map, IEnumerable>(filteredProjects); ProjectResultsResource resultsResource = new ProjectResultsResource - { - Results = results.ToArray(), - Count = results.Count(), - TotalCount = + { + Results = results.ToArray(), + Count = results.Count(), + TotalCount = await projectService.ProjectsCount(projectFilterParams), - Page = projectFilterParams.Page, - TotalPages = + Page = projectFilterParams.Page, + TotalPages = await projectService.GetProjectsTotalPages( projectFilterParams) - }; + }; return Ok(resultsResource); } @@ -218,48 +217,42 @@ await projectService.GetProjectsTotalPages( [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task GetProject(int projectId) { - - if(projectId < 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting project.", - Detail = + { + Title = "Failed getting project.", + Detail = "The Id is smaller then 0 and therefore it could never be a valid project id.", - Instance = "D590A4FE-FDBA-4AE5-B184-BC7395C45D4E" - }; + Instance = "D590A4FE-FDBA-4AE5-B184-BC7395C45D4E" + }; return BadRequest(problem); } - Project project = await projectService.FindWithUserAndCollaboratorsAsync(projectId) + Project project = await projectService.FindWithUserCollaboratorsAndInstitutionsAsync(projectId) .ConfigureAwait(false); if(project == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting project.", - Detail = "The project could not be found in the database.", - Instance = "38516C41-4BFB-47BE-A759-1206BE6D2D13" - }; + { + Title = "Failed getting project.", + Detail = "The project could not be found in the database.", + Instance = "38516C41-4BFB-47BE-A759-1206BE6D2D13" + }; return NotFound(problem); } if(HttpContext.User.CheckIfUserIsAuthenticated()) { User currentUser = await HttpContext.GetContextUser(userService) - .ConfigureAwait(false); + .ConfigureAwait(false); - if(project.InstitutePrivate && currentUser.InstitutionId == project.User.InstitutionId) - { - return Ok(mapper.Map(project)); - } - if(project.InstitutePrivate == false) + if(project.CanAccess(currentUser)) { return Ok(mapper.Map(project)); } - } - else + + } else { if(project.InstitutePrivate == false) { @@ -279,21 +272,23 @@ public async Task GetProject(int projectId) /// /// The 400 Bad Request status code is returned when the project /// resource is not specified or failed to save project to the database. + /// 404 not found when the user is not bound to and institution and tries to make the project isntitute private /// [HttpPost] - [Authorize] + [Authorize(Policy = nameof(Defaults.Scopes.ProjectWrite))] [ProducesResponseType(typeof(ProjectResourceResult), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task CreateProjectAsync([FromBody] ProjectResource projectResource) { if(projectResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to create a new project.", - Detail = "The specified project resource was null.", - Instance = "8D3D9119-0D12-4631-B2DC-56494639A849" - }; + { + Title = "Failed to create a new project.", + Detail = "The specified project resource was null.", + Instance = "8D3D9119-0D12-4631-B2DC-56494639A849" + }; return BadRequest(problem); } @@ -305,12 +300,12 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( if(!callToActionOptions.Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "Call to action value was not found.", - Detail = + { + Title = "Call to action value was not found.", + Detail = "The specified call to action value was not found while creating the project.", - Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" - }; + Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" + }; return BadRequest(problem); } } @@ -322,11 +317,11 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( file == null) { ProblemDetails problem = new ProblemDetails - { - Title = "File was not found.", - Detail = "The specified file was not found while creating project.", - Instance = "8CABE64D-6B73-4C88-BBD8-B32FA9FE6EC7" - }; + { + Title = "File was not found.", + Detail = "The specified file was not found while creating project.", + Instance = "8CABE64D-6B73-4C88-BBD8-B32FA9FE6EC7" + }; return BadRequest(problem); } @@ -334,6 +329,22 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( project.User = await HttpContext.GetContextUser(userService) .ConfigureAwait(false); + if(project.InstitutePrivate) + { + if(project.User.InstitutionId == default) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Unable to create project.", + Detail = "The insitute private is set to true, but the user creating the project isn't bound to an institution.", + Instance = "b942c55d-01be-4fd1-90a1-a5ad3d172403" + }; + return NotFound(problem); + } + + project.LinkedInstitutions.Add(new ProjectInstitution { Project = project, Institution = project.User.Institution }); + } + try { projectService.Add(project); @@ -345,11 +356,11 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( ProblemDetails problem = new ProblemDetails - { - Title = "Failed to save new project.", - Detail = "There was a problem while saving the project to the database.", - Instance = "9FEEF001-F91F-44E9-8090-6106703AB033" - }; + { + Title = "Failed to save new project.", + Detail = "There was a problem while saving the project to the database.", + Instance = "9FEEF001-F91F-44E9-8090-6106703AB033" + }; return BadRequest(problem); } } @@ -363,11 +374,13 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( /// This endpoint returns the updated project. /// The 401 Unauthorized status code is return when the user has not the correct permission to update. /// The 404 not Found status code is returned when the project to update is not found. + /// The 404 if the use tries to update the institute private property with this endpoint. [HttpPut("{projectId}")] [Authorize] [ProducesResponseType(typeof(ProjectResourceResult), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task UpdateProject(int projectId, [FromBody] ProjectResource projectResource) { Project project = await projectService.FindAsync(projectId) @@ -375,26 +388,26 @@ public async Task UpdateProject(int projectId, [FromBody] Project if(project == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to update project.", - Detail = "The specified project could not be found in the database.", - Instance = "b27d3600-33b0-42a0-99aa-4b2f28ea07bb" - }; + { + Title = "Failed to update project.", + Detail = "The specified project could not be found in the database.", + Instance = "b27d3600-33b0-42a0-99aa-4b2f28ea07bb" + }; return NotFound(problem); } User user = await HttpContext.GetContextUser(userService) .ConfigureAwait(false); - bool isAllowed = userService.UserHasScope(user.IdentityId, nameof(Defaults.Scopes.ProjectWrite)); + bool isAllowed = userService.UserHasScope(user.IdentityId, nameof(Defaults.Scopes.AdminProjectWrite)); if(!(project.UserId == user.Id || isAllowed)) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to edit the project.", - Detail = "The user is not allowed to edit the project.", - Instance = "906cd8ad-b75c-4efb-9838-849f99e8026b" - }; + { + Title = "Failed to edit the project.", + Detail = "The user is not allowed to edit the project.", + Instance = "906cd8ad-b75c-4efb-9838-849f99e8026b" + }; return Unauthorized(problem); } @@ -406,27 +419,41 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( if(!callToActionOptions.Any()) { ProblemDetails problem = new ProblemDetails - { - Title = "Call to action value was not found.", - Detail = + { + Title = "Call to action value was not found.", + Detail = "The specified call to action value was not found while creating the project.", - Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" - }; + Instance = "40EE82EB-930F-40C8-AE94-0041F7573FE9" + }; return BadRequest(problem); } } + if (projectResource.InstitutePrivate != project.InstitutePrivate) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Unable to update project.", + Detail = $"It is not possible to update the institute private of a project with this endpoint. Please use the {nameof(UpdateProjectPrivateStatus)} endpoint.", + Instance = "6a92321e-c6a7-41d9-90ee-9148566718e3" + }; + return BadRequest(problem); + } + // Upload the new file if there is one File file = null; if(projectResource.FileId != 0) { - if(project.ProjectIconId != 0 && project.ProjectIconId != null) + if(project.ProjectIconId != 0 && + project.ProjectIconId != null) { if(project.ProjectIconId != projectResource.FileId) { File fileToDelete = await fileService.FindAsync(project.ProjectIconId.Value); + // Remove the file from the filesystem fileUploader.DeleteFileFromDirectory(fileToDelete); + // Remove file from DB await fileService.RemoveAsync(project.ProjectIconId.Value); @@ -444,17 +471,20 @@ await callToActionOptionService.GetCallToActionOptionFromValueAsync( } else { ProblemDetails problem = new ProblemDetails - { - Title = "File was not found.", - Detail = "The specified file was not found while updating project.", - Instance = "69166D3D-6D34-4050-BD25-71F1BEBE43D3" - }; + { + Title = "File was not found.", + Detail = "The specified file was not found while updating project.", + Instance = "69166D3D-6D34-4050-BD25-71F1BEBE43D3" + }; return BadRequest(problem); } } + mapper.Map(projectResource, project); projectService.Update(project); projectService.Save(); + + return Ok(mapper.Map(project)); } @@ -480,29 +510,29 @@ public async Task DeleteProject(int projectId) if(project == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to delete the project.", - Detail = "The project could not be found in the database.", - Instance = "AF63CF48-ECAA-4996-BAA0-BF52926D12AC" - }; + { + Title = "Failed to delete the project.", + Detail = "The project could not be found in the database.", + Instance = "AF63CF48-ECAA-4996-BAA0-BF52926D12AC" + }; return NotFound(problem); } User user = await HttpContext.GetContextUser(userService) .ConfigureAwait(false); bool isAllowed = await authorizationHelper.UserIsAllowed(user, - nameof(Defaults.Scopes.ProjectWrite), + nameof(Defaults.Scopes.AdminProjectWrite), nameof(Defaults.Scopes.InstitutionProjectWrite), project.UserId); if(!(project.UserId == user.Id || isAllowed)) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to delete the project.", - Detail = "The user is not allowed to delete the project.", - Instance = "D0363680-5B4F-40A1-B381-0A7544C70164" - }; + { + Title = "Failed to delete the project.", + Detail = "The user is not allowed to delete the project.", + Instance = "D0363680-5B4F-40A1-B381-0A7544C70164" + }; return Unauthorized(problem); } @@ -522,11 +552,11 @@ await fileService.RemoveAsync(fileToDelete.Id) } catch(FileNotFoundException) { ProblemDetails problem = new ProblemDetails - { - Title = "File could not be deleted because the path does not exist.", - Detail = "File could not be found.", - Instance = "367594c4-1fab-47ae-beb4-a41b53c65a18" - }; + { + Title = "File could not be deleted because the path does not exist.", + Detail = "File could not be found.", + Instance = "367594c4-1fab-47ae-beb4-a41b53c65a18" + }; return NotFound(problem); } @@ -557,22 +587,22 @@ public async Task FollowProject(int projectId) if(await userService.FindAsync(user.Id) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with this user id.", - Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" + }; return NotFound(problem); } if(userProjectService.CheckIfUserFollows(user.Id, projectId)) { ProblemDetails problem = new ProblemDetails - { - Title = "User already follows this project", - Detail = "You are already following this project.", - Instance = "27D14082-9906-4EB8-AE4C-65BAEC0BB4FD" - }; + { + Title = "User already follows this project", + Detail = "You are already following this project.", + Instance = "27D14082-9906-4EB8-AE4C-65BAEC0BB4FD" + }; return Conflict(problem); } @@ -581,11 +611,11 @@ public async Task FollowProject(int projectId) if(await projectService.FindAsync(projectId) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the project.", - Detail = "The database does not contain a project with this project id.", - Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" - }; + { + Title = "Failed getting the project.", + Detail = "The database does not contain a project with this project id.", + Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" + }; return NotFound(problem); } UserProject userProject = new UserProject(project, user); @@ -613,22 +643,22 @@ public async Task UnfollowProject(int projectId) if(await userService.FindAsync(user.Id) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with this user id.", - Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" + }; return NotFound(problem); } if(userProjectService.CheckIfUserFollows(user.Id, projectId) == false) { ProblemDetails problem = new ProblemDetails - { - Title = "User is not following this project", - Detail = "You are not following this project.", - Instance = "27D14082-9906-4EB8-AE4C-65BAEC0BB4FD" - }; + { + Title = "User is not following this project", + Detail = "You are not following this project.", + Instance = "27D14082-9906-4EB8-AE4C-65BAEC0BB4FD" + }; return Conflict(problem); } @@ -637,11 +667,11 @@ public async Task UnfollowProject(int projectId) if(await projectService.FindAsync(projectId) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the project.", - Detail = "The database does not contain a project with this project id.", - Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" - }; + { + Title = "Failed getting the project.", + Detail = "The database does not contain a project with this project id.", + Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" + }; return NotFound(problem); } UserProject userProject = new UserProject(project, user); @@ -674,23 +704,23 @@ public async Task LikeProject(int projectId) if(currentUser == null) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "Failed to getting the user account.", - Detail = + { + Title = "Failed to getting the user account.", + Detail = "The database does not contain a user with the provided user id.", - Instance = "F8DB2F94-48DA-4FEB-9BDA-FF24A59333C1" - }; + Instance = "F8DB2F94-48DA-4FEB-9BDA-FF24A59333C1" + }; return NotFound(problemDetails); } if(userProjectLikeService.CheckIfUserAlreadyLiked(currentUser.Id, projectId)) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "User already liked this project", - Detail = "You already liked this project.", - Instance = "5B0104E2-C864-4ADB-9321-32CD352DC124" - }; + { + Title = "User already liked this project", + Detail = "You already liked this project.", + Instance = "5B0104E2-C864-4ADB-9321-32CD352DC124" + }; return Conflict(problemDetails); } @@ -699,12 +729,12 @@ public async Task LikeProject(int projectId) if(projectToLike == null) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "Failed to getting the project.", - Detail = + { + Title = "Failed to getting the project.", + Detail = "The database does not contain a project with the provided project id.", - Instance = "711B2DDE-D028-479E-8CB7-33F587478F8F" - }; + Instance = "711B2DDE-D028-479E-8CB7-33F587478F8F" + }; return NotFound(problemDetails); } @@ -721,11 +751,11 @@ await userProjectLikeService.AddAsync(like) Log.Logger.Error(e, "Database exception!"); ProblemDetails problemDetails = new ProblemDetails - { - Title = "Could not create the liked project details.", - Detail = "The database failed to save the liked project.", - Instance = "F941879E-6C25-4A35-A962-8E86382E1849" - }; + { + Title = "Could not create the liked project details.", + Detail = "The database failed to save the liked project.", + Instance = "F941879E-6C25-4A35-A962-8E86382E1849" + }; return BadRequest(problemDetails); } } @@ -752,23 +782,23 @@ public async Task UnlikeProject(int projectId) if(currentUser == null) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "Failed to getting the user account.", - Detail = + { + Title = "Failed to getting the user account.", + Detail = "The database does not contain a user with the provided user id.", - Instance = "F8DB2F94-48DA-4FEB-9BDA-FF24A59333C1" - }; + Instance = "F8DB2F94-48DA-4FEB-9BDA-FF24A59333C1" + }; return NotFound(problemDetails); } if(!userProjectLikeService.CheckIfUserAlreadyLiked(currentUser.Id, projectId)) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "User didn't like this project.", - Detail = "You did not like this project at the moment.", - Instance = "03590F81-C06D-4707-A646-B9B7F79B8A15" - }; + { + Title = "User didn't like this project.", + Detail = "You did not like this project at the moment.", + Instance = "03590F81-C06D-4707-A646-B9B7F79B8A15" + }; return Conflict(problemDetails); } @@ -778,12 +808,12 @@ public async Task UnlikeProject(int projectId) if(projectToLike == null) { ProblemDetails problemDetails = new ProblemDetails - { - Title = "Failed to getting the project.", - Detail = + { + Title = "Failed to getting the project.", + Detail = "The database does not contain a project with the provided project id.", - Instance = "711B2DDE-D028-479E-8CB7-33F587478F8F" - }; + Instance = "711B2DDE-D028-479E-8CB7-33F587478F8F" + }; return NotFound(problemDetails); } @@ -794,6 +824,240 @@ public async Task UnlikeProject(int projectId) return Ok(mapper.Map(projectLike)); } + + + /// + /// Updates the institutionprivate property of a project to the given value in the body + /// and adds the institution of the creator to the project if the institution isn't yet linked + /// to the project and institutePrivate is true. + /// + /// The project identifier + /// The new value for InstitutePrivate in the given project + /// If the project or current user couldn't be found. + /// The creator of the project doesn't belong to an institution + /// If the user doesn't have the rights to make the project private. + /// If success + [HttpPut("instituteprivate/{projectId}")] + [Authorize] + [ProducesResponseType(typeof(ProjectResultResource), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status403Forbidden)] + public async Task UpdateProjectPrivateStatus(int projectId, [FromBody] bool institutePrivate) + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + + if(currentUser == null) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed to getting the user account.", + Detail = "The database does not contain a user with the provided user id.", + Instance = "88cfe86d-fcdd-42d2-8460-1ea1d582d879" + }; + return NotFound(problemDetails); + } + + Project project = await projectService.FindWithUserCollaboratorsAndInstitutionsAsync(projectId) + .ConfigureAwait(false); + + if(project == null) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed setting the status of the project.", + Detail = "The project send in the request could not be found in the database.", + Instance = "68cdf62d-26ef-4b6e-9f98-fdddcfc0fc71" + }; + return NotFound(problemDetails); + } + + bool isAllowed = await authorizationHelper.UserIsAllowed(currentUser, + nameof(Defaults.Scopes.AdminProjectWrite), + nameof(Defaults.Scopes.InstitutionProjectWrite), + project.UserId); + + bool isCreatorAndHasScope = + project.IsCreator(currentUser.Id) && + userService.UserHasScope(currentUser.IdentityId, nameof(Defaults.Scopes.ProjectWrite)); + + //If the current user isn't the creator of the project or the dataofficer of the organization which the creator belongs to + //Then this user is not authorized to link the institution to the project + if(!(isCreatorAndHasScope || + isAllowed)) + { + return Forbid(); + } + + int projectCreatorInstitutionId = project.User.InstitutionId.GetValueOrDefault(); + + if(projectCreatorInstitutionId == default) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed setting the status of the project.", + Detail = "The creator of the project send in the request is not bound to an institution and can therefore not make the project private.", + Instance = "6972f184-7715-41e6-8bca-8b4ee63d5c58" + }; + return NotFound(problemDetails); + } + + //Link institution of the creator of the project to project if the institution isn't linked to the project yet + if(institutePrivate && + !projectInstitutionService.InstitutionIsLinkedToProject(projectId, projectCreatorInstitutionId)) + { + ProjectInstitution projectInstitution = new ProjectInstitution(projectId, projectCreatorInstitutionId); + await projectInstitutionService.AddAsync(projectInstitution).ConfigureAwait(false); + } + + project.InstitutePrivate = institutePrivate; + projectService.Update(project); + + projectService.Save(); + + return Ok(mapper.Map(project)); + } + + /// + /// Links given project and institution. This function is admin only! + /// + /// The project identifier + /// The institution identifier + /// If the project, institution or current user couldn't be found. + /// If the project is already linked to the institution. + /// If success + [HttpPost("linkedinstitution/{projectId}/{institutionId}")] + [Authorize(Policy = nameof(Defaults.Scopes.AdminProjectWrite))] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] + [ProducesResponseType(typeof(ProjectInstitutionResourceResult), StatusCodes.Status201Created)] + public async Task LinkInstitutionToProjectAsync(int projectId, int institutionId) + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + + if(currentUser == null) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with the provided user id.", + Instance = "11deede6-c76c-42cd-ac50-97743e3cff2a" + }; + return NotFound(problemDetails); + } + + if(!await projectService.ProjectExistsAsync(projectId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed setting the status of the project.", + Detail = "The project send in the request could not be found in the database.", + Instance = "4a73928f-827f-44a5-b508-e6a8c73c1717" + }; + return NotFound(problemDetails); + } + + if(!await institutionService.InstitutionExistsAsync(institutionId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed getting the institution.", + Detail = "The institution send in the request could not be found in the database.", + Instance = "01cdc6f4-42aa-4394-bbc8-2576e4f99498" + }; + return NotFound(problemDetails); + } + + if(projectInstitutionService.InstitutionIsLinkedToProject(projectId, institutionId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Institution is already linked to project.", + Detail = "The institution cannot be linked to the project because the institution is already part of the project.", + Instance = "01b26993-0c9e-4890-a7fe-f0f39450e616" + }; + return Conflict(problemDetails); + } + + ProjectInstitution projectInstitution = new ProjectInstitution(projectId, institutionId); + await projectInstitutionService.AddAsync(projectInstitution); + projectInstitutionService.Save(); + + //Maybe a bit wasteful to get the entire project and institution but its fast enough for now. + projectInstitution.Project = await projectService.FindAsync(projectId); + projectInstitution.Institution = await institutionService.FindAsync(institutionId); + + return Created(nameof(LinkInstitutionToProjectAsync), mapper.Map(projectInstitution)); + } + + /// + /// Links given project and institution. This function is admin only! + /// + /// The project identifier + /// The institution identifier + /// If the project, institution or current user couldn't be found. + /// If the project is not yet linked to the institution. + /// If success + [HttpDelete("linkedinstitution/{projectId}/{institutionId}")] + [Authorize(Policy = nameof(Defaults.Scopes.AdminProjectWrite))] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] + public async Task UnlinkInstitutionFromProjectAsync(int projectId, int institutionId) + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + + if(currentUser == null) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed to getting the user account.", + Detail = "The database does not contain a user with the provided user id.", + Instance = "7561e57a-d957-4823-9b90-d107aa54f893" + }; + return NotFound(problemDetails); + } + + if(!await projectService.ProjectExistsAsync(projectId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed setting the status of the project.", + Detail = "The project send in the request could not be found in the database.", + Instance = "f358fc51-0761-4929-8cca-71a6225fe0cd" + }; + return NotFound(problemDetails); + } + + if(!await institutionService.InstitutionExistsAsync(institutionId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Failed getting the institution.", + Detail = "The institution send in the request could not be found in the database.", + Instance = "7065c1e7-0543-470e-ab55-403c13418626" + }; + return NotFound(problemDetails); + } + + if(!projectInstitutionService.InstitutionIsLinkedToProject(projectId, institutionId)) + { + ProblemDetails problemDetails = new ProblemDetails + { + Title = "Institution is not yet linked to the project.", + Detail = "The institution cannot be unlinked from the project, because it is not linked to the project in the first place.", + Instance = "695ef305-06b3-4ad8-99f2-773cd0f03e6e" + }; + return Conflict(problemDetails); + } + + projectInstitutionService.RemoveByProjectIdAndInstitutionId(projectId, institutionId); + projectInstitutionService.Save(); + + return Ok(); + } } } diff --git a/API/Controllers/RoleController.cs b/API/Controllers/RoleController.cs index d532a801..d4e1fbdc 100644 --- a/API/Controllers/RoleController.cs +++ b/API/Controllers/RoleController.cs @@ -4,10 +4,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Models; -using Models.Defaults; using Serilog; using Services.Services; -using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; @@ -15,20 +13,22 @@ namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the roles, for example creating, retrieving, updating or deleting. + /// 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 RoleController : ControllerBase { + private readonly IMapper mapper; - private readonly IUserService userService; private readonly IRoleService roleService; + private readonly IUserService userService; /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// /// The role service which is used to communicate with the logic layer. /// The user service which is used to communicate with the logic layer. @@ -41,7 +41,7 @@ public RoleController(IRoleService roleService, IUserService userService, IMappe } /// - /// This method is responsible for retrieving all roles. + /// This method is responsible for retrieving all roles. /// /// This method returns a list of role resource results. /// This endpoint returns a list of roles. @@ -51,13 +51,13 @@ public RoleController(IRoleService roleService, IUserService userService, IMappe public async Task GetAllRoles() { List roles = await roleService.GetAllAsync() - .ConfigureAwait(false); + .ConfigureAwait(false); return Ok(mapper.Map, IEnumerable>(roles)); } /// - /// This method is responsible for retrieving all scopes. + /// This method is responsible for retrieving all scopes. /// /// This method returns a list of valid scopes. /// This endpoint returns a list of scopes. @@ -72,11 +72,11 @@ public IActionResult GetAllPossibleScopes() if(scopeList.Count == 0) { ProblemDetails problem = new ProblemDetails - { - Title = "No valid Scopes found", - Detail = "There where no valid scopes found.", - Instance = "DEB2161D-A8E7-4AAE-BB0F-CDB3CA5D5B9E" - }; + { + Title = "No valid Scopes found", + Detail = "There where no valid scopes found.", + Instance = "DEB2161D-A8E7-4AAE-BB0F-CDB3CA5D5B9E" + }; return NotFound(problem); } @@ -84,7 +84,7 @@ public IActionResult GetAllPossibleScopes() } /// - /// This method is responsible for retrieving a single role. + /// This method is responsible for retrieving a single role. /// /// This method return the role resource result. /// This endpoint returns the role with the specified id. @@ -100,23 +100,25 @@ public async Task GetRole(int roleId) if(roleId < 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting role.", - Detail = "The Id is smaller then 0 and therefore it could never be a valid role id.", - Instance = "5024ADDA-6DE2-4B49-896A-526E8EC4313D" - }; + { + Title = "Failed getting role.", + Detail = + "The Id is smaller then 0 and therefore it could never be a valid role id.", + Instance = "5024ADDA-6DE2-4B49-896A-526E8EC4313D" + }; return BadRequest(problem); } - Role role = await roleService.FindAsync(roleId).ConfigureAwait(false); + Role role = await roleService.FindAsync(roleId) + .ConfigureAwait(false); if(role == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting role.", - Detail = "The role could not be found in the database.", - Instance = "1739EFA6-3F31-4C88-B596-74DA403AC51B" - }; + { + Title = "Failed getting role.", + Detail = "The role could not be found in the database.", + Instance = "1739EFA6-3F31-4C88-B596-74DA403AC51B" + }; return NotFound(problem); } @@ -124,7 +126,7 @@ public async Task GetRole(int roleId) } /// - /// This method is responsible for creating the role. + /// This method is responsible for creating the role. /// /// The role resource which is used to create a role. /// This method returns the created role resource result. @@ -134,16 +136,16 @@ public async Task GetRole(int roleId) [Authorize(Policy = nameof(Scopes.RoleWrite))] [ProducesResponseType(typeof(RoleResourceResult), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] - public async Task CreateRoleAsync([FromBody]RoleResource roleResource) + public async Task CreateRoleAsync([FromBody] RoleResource roleResource) { if(roleResource == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to create a new role.", - Detail = "The specified role resource was null", - Instance = "ABA3B997-1B80-47FC-A72B-69BC0D8DFA93" - }; + { + Title = "Failed to create a new role.", + Detail = "The specified role resource was null", + Instance = "ABA3B997-1B80-47FC-A72B-69BC0D8DFA93" + }; return BadRequest(problem); } Role role = mapper.Map(roleResource); @@ -153,37 +155,37 @@ public async Task CreateRoleAsync([FromBody]RoleResource roleReso if(!roleService.IsValidScope(roleScope.Scope)) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to create a new role.", - Detail = $"The specified scope: {roleScope.Scope} is not valid.", - Instance = "1F40D851-8A4C-41F6-917C-D876970D825F" - }; + { + Title = "Failed to create a new role.", + Detail = $"The specified scope: {roleScope.Scope} is not valid.", + Instance = "1F40D851-8A4C-41F6-917C-D876970D825F" + }; return BadRequest(problem); } } try { - await roleService.AddAsync(role).ConfigureAwait(false); + await roleService.AddAsync(role) + .ConfigureAwait(false); roleService.Save(); return Created(nameof(CreateRoleAsync), mapper.Map(role)); - } - catch(DbUpdateException e) + } catch(DbUpdateException e) { Log.Logger.Error(e, "Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Failed to save the new role.", - Detail = "There was a problem while saving the role to the database.", - Instance = "D56DBE55-57A1-4655-99C5-4F4ECEEE3BE4" - }; + { + Title = "Failed to save the new role.", + Detail = "There was a problem while saving the role to the database.", + Instance = "D56DBE55-57A1-4655-99C5-4F4ECEEE3BE4" + }; return BadRequest(problem); } } /// - /// This method is responsible for updating the role. + /// This method is responsible for updating the role. /// /// The role identifier which is used for searching the role. /// The role resource which is used to update the role. @@ -198,28 +200,30 @@ public async Task CreateRoleAsync([FromBody]RoleResource roleReso [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task UpdateRole(int roleId, RoleResource roleResource) { - Role currentRole = await roleService.FindAsync(roleId).ConfigureAwait(false); + Role currentRole = await roleService.FindAsync(roleId) + .ConfigureAwait(false); if(currentRole == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to update the role.", - Detail = "The specified role could not be found in the database", - Instance = "8F167FDF-3B2B-4E71-B3D0-AA2B1C1CE2C3" - }; + { + Title = "Failed to update the role.", + Detail = "The specified role could not be found in the database", + Instance = "8F167FDF-3B2B-4E71-B3D0-AA2B1C1CE2C3" + }; return NotFound(problem); } - mapper.Map(roleResource,currentRole); + mapper.Map(roleResource, currentRole); foreach(RoleScope roleScope in currentRole.Scopes) { if(!roleService.IsValidScope(roleScope.Scope)) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to update the role", - Detail = $"The specified scope is not a valid scope: {roleScope.Scope}.", - Instance = "E0BB725C-4013-4B0E-AEBC-857F1F75B29C" - }; + { + Title = "Failed to update the role", + Detail = + $"The specified scope is not a valid scope: {roleScope.Scope}.", + Instance = "E0BB725C-4013-4B0E-AEBC-857F1F75B29C" + }; return BadRequest(problem); } } @@ -232,7 +236,7 @@ public async Task UpdateRole(int roleId, RoleResource roleResourc } /// - /// This method is responsible for deleting the role. + /// This method is responsible for deleting the role. /// /// The role identifier which is used for searching the role. /// This method returns status code 200. @@ -249,46 +253,50 @@ public async Task UpdateRole(int roleId, RoleResource roleResourc [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task DeleteRole(int roleId) { - Role role = await roleService.FindAsync(roleId).ConfigureAwait(false); + Role role = await roleService.FindAsync(roleId) + .ConfigureAwait(false); if(role == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to delete the role.", - Detail = "The role could not be found in the database.", - Instance = "CBC4C09D-DFEA-44D8-A310-2CE149BAD498" - }; + { + Title = "Failed to delete the role.", + Detail = "The role could not be found in the database.", + Instance = "CBC4C09D-DFEA-44D8-A310-2CE149BAD498" + }; return NotFound(problem); } - if(role.Name == nameof(Defaults.Roles.Administrator) || role.Name == nameof(Defaults.Roles.RegisteredUser)) + if(role.Name == nameof(Roles.Administrator) || + role.Name == nameof(Roles.RegisteredUser)) { ProblemDetails problem = new ProblemDetails - { - Title = "Not allowed.", - Detail = "For the stability of the program we need at least the registered user role and the admin role so these are not deletable..", - Instance = "CBC4C09D-DFEA-44D8-A310-2CE14123D498" - }; + { + Title = "Not allowed.", + Detail = + "For the stability of the program we need at least the registered user role and the admin role so these are not deletable..", + Instance = "CBC4C09D-DFEA-44D8-A310-2CE14123D498" + }; return Unauthorized(problem); } if(userService.UserWithRoleExists(role)) { ProblemDetails problem = new ProblemDetails - { - Title = "Role is still assigned.", - Detail = "The role is still assigned to a user.", - Instance = "46E4AD0A-5947-4F9B-8001-A4D77CBC1A92" - }; + { + Title = "Role is still assigned.", + Detail = "The role is still assigned to a user.", + Instance = "46E4AD0A-5947-4F9B-8001-A4D77CBC1A92" + }; return BadRequest(problem); } - await roleService.RemoveAsync(role.Id).ConfigureAwait(false); + await roleService.RemoveAsync(role.Id) + .ConfigureAwait(false); roleService.Save(); return Ok(); } /// - /// This method is responsible for setting the role. + /// This method is responsible for setting the role. /// /// The user identifier which is used for searching the user. /// The role identifier which is used for searching the role. @@ -301,26 +309,28 @@ public async Task DeleteRole(int roleId) [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task SetRole(int userId, int roleId) { - Role role = await roleService.FindAsync(roleId).ConfigureAwait(false); + Role role = await roleService.FindAsync(roleId) + .ConfigureAwait(false); if(role == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to set role.", - Detail = "The role could not be found in the database.", - Instance = "A4D7DA5F-F47B-4FF6-8241-93D6808EEEDB" - }; + { + Title = "Failed to set role.", + Detail = "The role could not be found in the database.", + Instance = "A4D7DA5F-F47B-4FF6-8241-93D6808EEEDB" + }; return NotFound(problem); } - User user = await userService.FindAsync(userId).ConfigureAwait(false); + User user = await userService.FindAsync(userId) + .ConfigureAwait(false); if(user == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to set role.", - Detail = "The user could not be found in the database.", - Instance = "3C6F3A13-5045-41FF-8C77-FB3A7B49F597" - }; + { + Title = "Failed to set role.", + Detail = "The user could not be found in the database.", + Instance = "3C6F3A13-5045-41FF-8C77-FB3A7B49F597" + }; return NotFound(problem); } user.Role = role; @@ -329,5 +339,7 @@ public async Task SetRole(int userId, int roleId) return Ok(mapper.Map(user)); } + } + } diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 89b1b18c..3265346d 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -27,20 +27,22 @@ namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the search requests. + /// This class is responsible for handling HTTP requests that are related + /// to the search requests. /// [Route("api/[controller]")] [ApiController] public class SearchController : ControllerBase { + private readonly IMapper mapper; private readonly ISearchService searchService; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The search service which is used to communicate with the logic layer. /// The mapper which is used to convert the resource to the model to the resource result. @@ -51,7 +53,7 @@ public SearchController(ISearchService searchService, IMapper mapper) } /// - /// This method is responsible for searching and retrieving projects. + /// This method is responsible for searching and retrieving projects. /// /// The search query which is used to search for a project. /// The parameters to filter which is ued to sort and paginate the projects. @@ -62,12 +64,13 @@ public SearchController(ISearchService searchService, IMapper mapper) [ProducesResponseType(typeof(ProjectResultsResource), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] public async Task SearchInternalProjects(string query, - [FromQuery] ProjectFilterParamsResource projectFilterParamsResource) + [FromQuery] + ProjectFilterParamsResource projectFilterParamsResource) { ProblemDetails problem = new ProblemDetails - { - Title = "Invalid search request." - }; + { + Title = "Invalid search request." + }; if(string.IsNullOrEmpty(query)) { problem.Detail = "The Query parameter cannot be empty."; @@ -99,23 +102,31 @@ public async Task SearchInternalProjects(string query, return BadRequest(problem); } - ProjectFilterParams projectFilterParams = mapper.Map(projectFilterParamsResource); + ProjectFilterParams projectFilterParams = + mapper.Map(projectFilterParamsResource); IEnumerable projects = await searchService.SearchInternalProjects(query, projectFilterParams); IEnumerable searchResults = mapper.Map, IEnumerable>(projects); - ProjectResultsResource searchResultsResource = new ProjectResultsResource() - { - Results = searchResults.ToArray(), - Query = query, - Count = searchResults.Count(), - TotalCount = await searchService.SearchInternalProjectsCount(query, projectFilterParams), - Page = projectFilterParams.Page, - TotalPages = - await searchService.SearchInternalProjectsTotalPages(query, projectFilterParams) - }; + ProjectResultsResource searchResultsResource = new ProjectResultsResource + { + Results = searchResults.ToArray(), + Query = query, + Count = searchResults.Count(), + TotalCount = + await searchService.SearchInternalProjectsCount( + query, + projectFilterParams), + Page = projectFilterParams.Page, + TotalPages = + await searchService.SearchInternalProjectsTotalPages( + query, + projectFilterParams) + }; return Ok(searchResultsResource); } + } + } diff --git a/API/Controllers/UserController.cs b/API/Controllers/UserController.cs index f79353d8..21558071 100644 --- a/API/Controllers/UserController.cs +++ b/API/Controllers/UserController.cs @@ -26,41 +26,50 @@ using Models.Defaults; using Serilog; using Services.Services; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the users, for example creating, retrieving, updating or deleting. + /// This class is responsible for handling HTTP requests that are related + /// to the users, for example creating, retrieving, updating or deleting. /// [Route("api/[controller]")] [ApiController] public class UserController : ControllerBase { - private readonly IMapper mapper; - private readonly IUserService userService; - private readonly IRoleService roleService; + private readonly IAuthorizationHelper authorizationHelper; private readonly IInstitutionService institutionService; + private readonly IMapper mapper; + private readonly IRoleService roleService; + private readonly IUserService userService; private readonly IUserUserService userUserService; + private readonly IProjectService projectService; /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// /// The user service which is used to communicate with the logic layer. /// The mapper which is used to convert the resources to the models to the resource results. /// The role service which is used to communicate with the logic layer. /// The institution service which is used to communicate with the logic layer. - /// The authorization helper which is used to communicate with the authorization helper class. + /// + /// The authorization helper which is used to communicate with the authorization helper + /// class. + /// /// The user user service is responsible for users that are following users. + /// The project service which is responsible for retreiving the users projects public UserController(IUserService userService, IMapper mapper, IRoleService roleService, IAuthorizationHelper authorizationHelper, IInstitutionService institutionService, - IUserUserService userUserService) + IUserUserService userUserService, + IProjectService projectService) { this.userService = userService; this.mapper = mapper; @@ -68,10 +77,11 @@ public UserController(IUserService userService, this.authorizationHelper = authorizationHelper; this.institutionService = institutionService; this.userUserService = userUserService; + this.projectService = projectService; } /// - /// The method is responsible for retrieving the current user. + /// The method is responsible for retrieving the current user. /// /// The current user as user resource result. /// This endpoint returns the current user. @@ -87,10 +97,10 @@ public async Task GetCurrentUser() if(user == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The user could not be found in the database.", - Instance = "A4C4EEFA-1D3E-4E64-AF00-76C44D805D98" + { + Title = "Failed getting the user account.", + Detail = "The user could not be found in the database.", + Instance = "d9a88dca-5c91-49c4-bf7d-7cac0c6d5673" }; return NotFound(problem); } @@ -98,7 +108,7 @@ public async Task GetCurrentUser() } /// - /// This method is responsible for retrieving a user account. + /// This method is responsible for retrieving a user account. /// /// the user identifier which is used for searching a user. /// This method returns the user resource result. @@ -112,51 +122,81 @@ public async Task GetCurrentUser() [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task GetUser(int userId) { - User currentUser = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); bool isAllowed = await authorizationHelper.UserIsAllowed(currentUser, - nameof(Defaults.Scopes.UserRead), - nameof(Defaults.Scopes.InstitutionUserRead), - userId); + nameof(Defaults.Scopes.UserRead), + nameof(Defaults.Scopes.InstitutionUserRead), + userId); - if(!isAllowed) - return Forbid(); + if(!isAllowed) return Forbid(); if(userId < 0) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The user id is less then zero and therefore cannot exist in the database.", - Instance = "EAF7FEA1-47E9-4CF8-8415-4D3BC843FB71", - }; + { + Title = "Failed getting the user account.", + Detail = + "The user id is less then zero and therefore cannot exist in the database.", + Instance = "EAF7FEA1-47E9-4CF8-8415-4D3BC843FB71" + }; return BadRequest(problem); } User user = await userService.FindAsync(userId); if(user == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the user account.", + Detail = "The user could not be found in the database.", + Instance = "140B718F-9ECD-4F68-B441-F85C1DC7DC32" + }; + return NotFound(problem); + } + + return Ok(mapper.Map(user)); + } + + /// + /// The method is responsible for retrieving the current users projects. + /// + /// The current user projects as resource result. + /// This endpoint returns the current users projects. + /// The 404 Not found status code is returned when the user could not be found. + [HttpGet("projects")] + [Authorize] + [ProducesResponseType(typeof(UserResourceResult), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task GetUserProjects() + { + User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + if(user == null) { ProblemDetails problem = new ProblemDetails { Title = "Failed getting the user account.", Detail = "The user could not be found in the database.", - Instance = "140B718F-9ECD-4F68-B441-F85C1DC7DC32" + Instance = "ddcfef77-af2a-4ef0-a6bc-bd458b79306d" }; return NotFound(problem); } - return Ok(mapper.Map(user)); + IEnumerable userProjects = await projectService.GetUserProjects(user.Id); + + return Ok(mapper.Map, IEnumerable>(userProjects)); } - /// - /// This method is responsible for creating the account. - /// - /// The account resource which is used for creating the account. - /// This method returns the created user as user resource result. - /// This endpoint returns the created user. - /// The 400 Bad Request status code is return when the institution id is invalid - /// or when saving the user to the database failed. - /// The institution with the specified institution id could not be found. - [HttpPost] + /// + /// This method is responsible for creating the account. + /// + /// The account resource which is used for creating the account. + /// This method returns the created user as user resource result. + /// This endpoint returns the created user. + /// The 400 Bad Request status code is return when the institution id is invalid + /// or when saving the user to the database failed. + /// The institution with the specified institution id could not be found. + [HttpPost] [Authorize(Policy = nameof(Defaults.Scopes.UserWrite))] [ProducesResponseType(typeof(UserResourceResult), (int) HttpStatusCode.Created)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] @@ -169,11 +209,11 @@ public async Task CreateAccountAsync([FromBody] UserResource acco if(institutionId < 1) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting institution.", - Detail = "The id of an institution can't be smaller than 1", - Instance = "7C50A0D7-459D-473B-9ADE-7FC5B7EEE39E" - }; + { + Title = "Failed getting institution.", + Detail = "The id of an institution can't be smaller than 1", + Instance = "7C50A0D7-459D-473B-9ADE-7FC5B7EEE39E" + }; return BadRequest(problem); } @@ -181,11 +221,11 @@ public async Task CreateAccountAsync([FromBody] UserResource acco if(foundInstitution == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting institution.", - Detail = "The institution could not be found in the database.", - Instance = "6DECDE32-BE44-43B1-9DDD-4D14AE9CE731" - }; + { + Title = "Failed getting institution.", + Detail = "The institution could not be found in the database.", + Instance = "6DECDE32-BE44-43B1-9DDD-4D14AE9CE731" + }; return NotFound(problem); } } @@ -207,17 +247,17 @@ public async Task CreateAccountAsync([FromBody] UserResource acco Log.Logger.Error(e, "Database exception"); ProblemDetails problem = new ProblemDetails - { - Title = "Failed to create user account.", - Detail = "Failed saving the user account to the database.", - Instance = "D8C786C1-9E6D-4D36-83F4-A55D394B5017" - }; + { + Title = "Failed to create user account.", + Detail = "Failed saving the user account to the database.", + Instance = "D8C786C1-9E6D-4D36-83F4-A55D394B5017" + }; return BadRequest(problem); } } /// - /// This method is responsible for updating the account. + /// This method is responsible for updating the account. /// /// The user identifier which is used for searching the user. /// The user resource which is used for updating the user. @@ -238,11 +278,11 @@ public async Task UpdateAccount(int userId, [FromBody] UserResour if(institutionId < 1) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting institution.", - Detail = "The id of an institution can't be smaller than 1", - Instance = "7C50A0D7-459D-473B-9ADE-7FC5B7EEE39E" - }; + { + Title = "Failed getting institution.", + Detail = "The id of an institution can't be smaller than 1", + Instance = "7C50A0D7-459D-473B-9ADE-7FC5B7EEE39E" + }; return BadRequest(problem); } @@ -250,11 +290,11 @@ public async Task UpdateAccount(int userId, [FromBody] UserResour if(foundInstitution == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting institution.", - Detail = "The institution could not be found in the database.", - Instance = "6DECDE32-BE44-43B1-9DDD-4D14AE9CE731" - }; + { + Title = "Failed getting institution.", + Detail = "The institution could not be found in the database.", + Instance = "6DECDE32-BE44-43B1-9DDD-4D14AE9CE731" + }; return NotFound(problem); } } @@ -263,11 +303,11 @@ public async Task UpdateAccount(int userId, [FromBody] UserResour if(userToUpdate == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with that user id.", - Instance = "EF4DA55A-C31A-4BC4-AE30-098DEB0D3457" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with that user id.", + Instance = "EF4DA55A-C31A-4BC4-AE30-098DEB0D3457" + }; return NotFound(problem); } @@ -278,18 +318,20 @@ public async Task UpdateAccount(int userId, [FromBody] UserResour // Has institution excluded allowance if it's your own account or if the user has the right scope for the same institution. // In the last case, the institution has to be the same. bool hasInstitutionExcludedAllowance = currentUser.Id == userId || - await authorizationHelper.SameInstitutionAndInstitutionScope(currentUser, - nameof(Defaults.Scopes.InstitutionUserWrite), userToUpdate.Id); + await authorizationHelper.SameInstitutionAndInstitutionScope( + currentUser, + nameof(Defaults.Scopes.InstitutionUserWrite), + userToUpdate.Id); if(!hasFullAllowance && !hasInstitutionExcludedAllowance) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to edit the user.", - Detail = "The user is not allowed to edit this user.", - Instance = "E28BEBC0-AE7C-49F5-BDDC-3C13972B75D0" - }; + { + Title = "Failed to edit the user.", + Detail = "The user is not allowed to edit this user.", + Instance = "E28BEBC0-AE7C-49F5-BDDC-3C13972B75D0" + }; return Unauthorized(problem); } @@ -303,11 +345,11 @@ await authorizationHelper.SameInstitutionAndInstitutionScope(currentUser, userResource.InstitutionId != userToUpdate.InstitutionId) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to edit the user", - Detail = "The user has not enough rights to update the institution id", - Instance = "DD72C521-1D06-4E11-A0E0-AAE515E7F900" - }; + { + Title = "Failed to edit the user", + Detail = "The user has not enough rights to update the institution id", + Instance = "DD72C521-1D06-4E11-A0E0-AAE515E7F900" + }; return Unauthorized(problem); } } @@ -321,7 +363,7 @@ await authorizationHelper.SameInstitutionAndInstitutionScope(currentUser, } /// - /// This method is responsible for deleting the current account. + /// This method is responsible for deleting the current account. /// /// This method returns status code 200. /// This endpoint returns status code 200. The current account is deleted. @@ -332,16 +374,17 @@ await authorizationHelper.SameInstitutionAndInstitutionScope(currentUser, [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task DeleteAccount() { - User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); if(await userService.FindAsync(user.Id) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with this user id.", - Instance = "C4C62149-FF9A-4E4C-8C9F-6BBF518BA085" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "C4C62149-FF9A-4E4C-8C9F-6BBF518BA085" + }; return NotFound(problem); } @@ -349,10 +392,10 @@ public async Task DeleteAccount() userService.Save(); return Ok(); } - + /// - /// This method is responsible for deleting a user account. + /// This method is responsible for deleting a user account. /// /// This method returns status code 200. /// This endpoint returns status code 200. The account with the specified id is deleted. @@ -365,20 +408,22 @@ public async Task DeleteAccount() [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] public async Task DeleteAccount(int userId) { - User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); bool isAllowed = await authorizationHelper.UserIsAllowed(user, nameof(Defaults.Scopes.UserWrite), nameof(Defaults.Scopes.InstitutionUserWrite), userId); - if(user.Id != userId && !isAllowed) + if(user.Id != userId && + !isAllowed) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed to delete the user.", - Detail = "The user is not allowed to delete this user.", - Instance = "26DA6D58-DB7B-467D-90AA-69EFBF55A83C" - }; + { + Title = "Failed to delete the user.", + Detail = "The user is not allowed to delete this user.", + Instance = "26DA6D58-DB7B-467D-90AA-69EFBF55A83C" + }; return Unauthorized(problem); } @@ -399,7 +444,7 @@ public async Task DeleteAccount(int userId) } /// - /// Follows user + /// Follows user /// /// /// @@ -407,27 +452,28 @@ public async Task DeleteAccount(int userId) [Authorize] public async Task FollowUser(int followedUserId) { - User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); if(await userService.FindAsync(user.Id) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with this user id.", - Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" + }; return NotFound(problem); } if(userUserService.CheckIfUserFollows(user.Id, followedUserId)) { ProblemDetails problem = new ProblemDetails - { - Title = "You are already following this user", - Detail = "You are already following this user.", - Instance = "6B4D9745-4A18-4516-86A3-466678A3F891" - }; + { + Title = "You are already following this user", + Detail = "You are already following this user.", + Instance = "6B4D9745-4A18-4516-86A3-466678A3F891" + }; return Conflict(problem); } @@ -436,11 +482,11 @@ public async Task FollowUser(int followedUserId) if(await userService.FindAsync(followedUserId) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user", - Detail = "Unable to find user to follow", - Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" - }; + { + Title = "Failed getting the user", + Detail = "Unable to find user to follow", + Instance = "57C13F73-6D22-41F3-AB05-0CCC1B3C8328" + }; return NotFound(problem); } if(user.Id == followedUserId) @@ -453,16 +499,15 @@ public async Task FollowUser(int followedUserId) }; return NotFound(problem); } - UserUser userUser = new UserUser(user,followedUser); + UserUser userUser = new UserUser(user, followedUser); userUserService.Add(userUser); userUserService.Save(); return Ok(mapper.Map(userUser)); - } /// - /// Unfollow user + /// Unfollow user /// /// /// @@ -470,40 +515,41 @@ public async Task FollowUser(int followedUserId) [Authorize] public async Task UnfollowUser(int followedUserId) { - User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); if(await userService.FindAsync(user.Id) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the user account.", - Detail = "The database does not contain a user with this user id.", - Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" - }; + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "B778C55A-D41E-4101-A7A0-F02F76E5A6AE" + }; return NotFound(problem); } if(userUserService.CheckIfUserFollows(user.Id, followedUserId) == false) { ProblemDetails problem = new ProblemDetails - { - Title = "User is not following this user", - Detail = "You are not following this user.", - Instance = "103E6317-4546-4985-8E39-7D9FD3E14E35" - }; + { + Title = "User is not following this user", + Detail = "You are not following this user.", + Instance = "103E6317-4546-4985-8E39-7D9FD3E14E35" + }; return Conflict(problem); } - User followedUser= await userService.FindAsync(followedUserId); + User followedUser = await userService.FindAsync(followedUserId); if(await userService.FindAsync(followedUserId) == null) { ProblemDetails problem = new ProblemDetails - { - Title = "Failed getting the project.", - Detail = "The database does not contain a project with this project id.", - Instance = "ED4E8B26-7D7B-4F5E-BA04-983B5F114FB5" - }; + { + Title = "Failed getting the project.", + Detail = "The database does not contain a project with this project id.", + Instance = "ED4E8B26-7D7B-4F5E-BA04-983B5F114FB5" + }; return NotFound(problem); } UserUser userToUnfollow = new UserUser(user, followedUser); @@ -512,5 +558,42 @@ public async Task UnfollowUser(int followedUserId) userUserService.Save(); return Ok(); } + + + /// + /// This method changes the expected graduation date for the user. + /// + /// + /// This method returns status code 200. + /// This endpoint returns status code 200. The account has changed the graduation date. + /// The 404 Not Found status code is returned when the user is not found. + [HttpPut("graduationdate")] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + [Authorize] + public async Task SetUserGraduationDate([FromBody] UserResource userResource) + { + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + + if(await userService.FindAsync(user.Id) == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "DB0A5629-4A79-48BB-870E-C02FE7C1A768" + }; + return NotFound(problem); + } + + user.ExpectedGraduationDate = userResource.ExpectedGraduationDateTime; + + userService.Update(user); + userService.Save(); + + return Ok(mapper.Map(user)); + } + } + } diff --git a/API/Controllers/UserTaskController.cs b/API/Controllers/UserTaskController.cs new file mode 100644 index 00000000..e6a22af0 --- /dev/null +++ b/API/Controllers/UserTaskController.cs @@ -0,0 +1,267 @@ +/* +* 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 API.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Models; +using Models.Defaults; +using Services.Services; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace API.Controllers +{ + + /// + /// This class is responsible for handling HTTP requests that are related + /// to the user tasks, for example creating, retrieving or deleting. + /// + /// + [Route("api/[controller]")] + [ApiController] + public class UserTaskController : ControllerBase + { + + private readonly HttpClient identityHttpClient; + private readonly IRoleService roleService; + private readonly IUserService userService; + + private readonly IUserTaskService userTaskService; + + /// + /// The constructor for user tasks + /// + /// + /// The user task service is responsible for getting and setting the tasks that users should + /// follow up. + /// + /// The user service is responsible for getting and setting users. + /// The role service is responsible for getting and setting roles. + /// The client factory is a HttpClient which is used to communicate with the identity server. + public UserTaskController(IUserTaskService userTaskService, + IUserService userService, + IRoleService roleService, + IHttpClientFactory clientFactory) + { + this.userTaskService = userTaskService; + this.userService = userService; + this.roleService = roleService; + identityHttpClient = clientFactory.CreateClient("identityclient"); + } + + /// + /// Creates and returns all graduation user tasks for expecting graduation users. + /// + /// + /// This value determines the range of graduating users. The range is between now and + /// entered number of months. + /// + /// All user tasks which are created or open for graduation users. + /// This endpoint returns a list of user tasks. + [HttpGet("CreateUserTasks/{withinAmountOfMonths}")] + [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] + public async Task CreateUserTasksForGraduatingUsers(int withinAmountOfMonths) + { + // First check if worker service is trying to execute method to prevent internal server error. + bool isAllowed = HttpContext.User.HasClaim("client_role", Defaults.Roles.BackendApplication); + + if(isAllowed == false) + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + if(currentUser.Role.Name == Defaults.Roles.Administrator) + { + isAllowed = true; + } + } + + if(isAllowed) + { + List userTasks = await userTaskService.GetAllOpenGraduateUserTasks(withinAmountOfMonths); + + return Ok(userTasks); + } + + return Forbid(); + } + + /// + /// Creates and returns all graduation user tasks for expecting graduation users. + /// + /// All user tasks which are created or open for graduation users. + /// This status code is returned when the user tasks were found successfully. + /// This status code is returned when no user was found. + [HttpGet] + [ProducesResponseType(typeof(List), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task GetUserTasksForCurrentUser() + { + User currentUser = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + if(currentUser == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the user account.", + Detail = "The user could not be found in the database.", + Instance = "548DA96F-0183-483F-8CE9-2848A868DC57" + }; + return NotFound(problem); + } + + List userTasks = await userTaskService.GetUserTasksForUser(currentUser.Id); + + return Ok(userTasks); + } + + /// + /// This endpoint is responsible for converting an account to Alumni. + /// New credentials in the headers due to security. + /// + /// The updated user. + /// This endpoint returns the converted user. + /// The 404 Not found status code is returned when the user could not be found. + /// + /// The 503 Service unavailable status code is returned if the identity server cannot execute the + /// request. + /// + [HttpPut] + [Authorize] + [ProducesResponseType(typeof(User), (int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.ServiceUnavailable)] + public async Task ConvertToAlumni() + { + User user = await HttpContext.GetContextUser(userService) + .ConfigureAwait(false); + + if(await userService.FindAsync(user.Id) == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the user account.", + Detail = "The database does not contain a user with this user id.", + Instance = "598E61EC-1C0F-4ED2-AC42-F5B5503D4A5E" + }; + return NotFound(problem); + } + + List userTasks = await userTaskService.GetUserTasksForUser(user.Id); + UserTask userTask = userTasks.Find(u => u.Type == UserTaskType.GraduationReminder); + + if(userTask == null || + userTask.Status == UserTaskStatus.Completed) + { + ProblemDetails problem = new ProblemDetails + { + Title = "No graduation user task exists.", + Detail = + "The database does not contain a (uncompleted) user task for graduating.", + Instance = "A10E4AE8-633D-4334-BADA-99B7AD077B6D" + }; + return NotFound(problem); + } + + List roles = await roleService.GetAllAsync(); + Role alumniRole = roles.Find(r => r.Name == "Alumni"); + + if(alumniRole == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Alumni role does not exist.", + Detail = "The database does not contain a role for alumni.", + Instance = "2DE31766-5D9C-4E41-908F-389A9A85F723" + }; + return NotFound(problem); + } + + + // Rest call to Identity server to change credentials. Credentials are in the headers due to security issues. + identityHttpClient.DefaultRequestHeaders.Add("password", + Request.Headers.FirstOrDefault(h => h.Key == "password") + .Value.FirstOrDefault()); + identityHttpClient.DefaultRequestHeaders.Add("email", + Request.Headers.FirstOrDefault(h => h.Key == "email") + .Value.FirstOrDefault()); + identityHttpClient.DefaultRequestHeaders.Add("subjectId", user.IdentityId); + HttpResponseMessage resp = await identityHttpClient.PutAsync("ExternalAccount", new StringContent("")); + + if(!resp.IsSuccessStatusCode) + { + ProblemDetails problem = new ProblemDetails + { + Title = "IdentityServer had an error.", + Detail = resp.ReasonPhrase, + Instance = "94B362FE-F038-43AF-B2C3-462513D1C7F8" + }; + return StatusCode(503, problem); + } + + userTask.Status = UserTaskStatus.Completed; + userTaskService.Update(userTask); + + + user.Role = alumniRole; + user.Email = Request.Headers.FirstOrDefault(h => h.Key == "email") + .Value.FirstOrDefault(); + userService.Update(user); + + userTaskService.Save(); + userService.Save(); + return Ok(user); + } + + /// + /// Sets the user tasks to status mailed. + /// + /// All user tasks which are created or open for graduation users. + /// This status code is returned when the user tasks were found successfully. + /// This status code is returned when no user was found. + [HttpPut("SetToMailed/{userTaskId}")] + [Authorize(Policy = nameof(Defaults.Roles.BackendApplication))] + [ProducesResponseType((int) HttpStatusCode.OK)] + [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] + public async Task SetUserTasksToStatusMailed(int userTaskId) + { + UserTask userTask = await userTaskService.FindAsync(userTaskId); + + if(userTask == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "User task was not found.", + Detail = "User task with this ID does not exist.", + Instance = "CA523709-D2CB-4EC5-BE31-32758D2587D0" + }; + return NotFound(problem); + } + + userTask.Status = UserTaskStatus.Mailed; + userTaskService.Update(userTask); + userTaskService.Save(); + + return Ok(); + } + + } + +} diff --git a/API/Controllers/WizardController.cs b/API/Controllers/WizardController.cs index 5af49746..1d2991b2 100644 --- a/API/Controllers/WizardController.cs +++ b/API/Controllers/WizardController.cs @@ -15,75 +15,416 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using API.Resources; +using AutoMapper; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Models; -using Services.Services; +using Models.Exceptions; +using Services.ExternalDataProviders; using System; -using System.Net; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace API.Controllers { + /// - /// This class is responsible for handling HTTP requests that are related - /// to the wizard, for exampling retrieving. + /// This class is responsible for handling HTTP requests that are related + /// to the wizard, for exampling retrieving. /// [Route("api/[controller]")] [ApiController] public class WizardController : ControllerBase { - private readonly ISourceManagerService sourceManagerService; + + private readonly IDataProviderService dataProviderService; + private readonly IMapper mapper; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The source manager service which is used to communicate with the logic layer. - public WizardController(ISourceManagerService sourceManagerService) + /// The source manager service which is used to communicate with the logic layer. + /// The mapper which is used to convert the resources to the models to the resource results. + public WizardController( + IDataProviderService dataProviderService, + IMapper mapper) { - this.sourceManagerService = sourceManagerService; + this.dataProviderService = dataProviderService; + this.mapper = mapper; } /// - /// This method is responsible for retrieving the wizard information. + /// This method is responsible for retrieving a project from an external data source by + /// the specified source uri. /// - /// The source URI which is used for searching the project. - /// This method returns the filled in project. - /// This endpoint returns the project with the specified source Uri. - /// The 400 Bad Request status code is returned when the source Uri is not specified. - /// The 404 Not Found status code is returned when the project could not be found with the specified source Uri. - [HttpGet] + /// The guid that specifies the data source. + /// The uri that specifies which project will get retrieved. + /// This method returns the found project with the specified source uri. + /// This endpoint returns the project with the specified source uri. + /// + /// The 400 Bad Request status code is returned when the source uri is empty + /// or whenever the data source guid is invalid. + /// + /// + /// The 404 Not Found status code is returned when no data source is found + /// with the specified data source guid or no project is found with the specified source uri. + /// + [HttpGet("project/uri/{sourceUri}")] [Authorize] - [ProducesResponseType(typeof(Project), (int) HttpStatusCode.OK)] - [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.BadRequest)] - [ProducesResponseType((int) HttpStatusCode.NotFound)] - public IActionResult GetWizardInformation(Uri sourceURI) + [ProducesResponseType(typeof(WizardProjectResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetProjectByUriFromExternalDataSource( + [FromQuery] string dataSourceGuid, + string sourceUri) { - if(sourceURI == null) + if(sourceUri == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Source uri is null or empty.", + Detail = "The incoming source uri is not valid.", + Instance = "6D63D9FA-91D6-42D5-9ACB-461FBEB0D2ED" + }; + return BadRequest(problem); + } + + if(!Guid.TryParse(dataSourceGuid, out Guid _)) { ProblemDetails problem = new ProblemDetails + { + Title = "Specified guid is not valid.", + Detail = "The specified guid is not a real or valid guid.", + Instance = "9FAF4C56-5B09-46C2-9A52-902D82ADAFA6" + }; + return BadRequest(problem); + } + + if(!dataProviderService.IsExistingDataSourceGuid(dataSourceGuid)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Data source not found.", + Detail = "Data source could not be found with specified data source guid.", + Instance = "DA33EB64-13EF-46CC-B3E6-785E4027377A" + }; + return NotFound(problem); + } + + try + { + Project project = await dataProviderService.GetProjectFromUri(dataSourceGuid, sourceUri); + if(project == null) { - Title = "Source uri is null or empty.", - Detail = "The incoming source uri is not valid.", - Instance = "6D63D9FA-91D6-42D5-9ACB-461FBEB0D2ED" - }; + ProblemDetails problem = new ProblemDetails + { + Title = "Project could not be found.", + Detail = + "The project could not be found with the specified source Uri and data source guid.", + Instance = "993252E8-61C4-422D-A547-EB9F56BA47B7" + }; + return NotFound(problem); + } + return Ok(mapper.Map(project)); + } catch(NotSupportedByExternalApiException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "External API does not support the functionality from the method.", + Detail = e.Message, + Instance = "DD815174-8711-4EF0-B01B-776709EDF485" + }; + return BadRequest(problem); + } catch(ExternalException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "An problem encountered while using the external API.", + Detail = e.Message, + Instance = "AA4FC30F-85F0-4120-A479-728DADABAB32" + }; + return BadRequest(problem); + } catch(NotSupportedException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "The specified data source is not supported.", + Detail = e.Message, + Instance = "E7834AC0-43D0-4D40-AB7C-E120A6EFCD5B" + }; + return BadRequest(problem); + } + } + + /// + /// This method is responsible for retrieving projects from an external data source. + /// + /// The guid that specifies the data source. + /// + /// The token which is used for retrieving the projects from the user. This token can be the + /// access token for the auth flow, but can also be the username for the public flow. + /// + /// The bool that represents whether the flow with authorization should get used. + /// This method returns a collection of all the projects. + /// This endpoint returns the project with the specified id. + /// The 400 Bad Request status code is returned when the specified data source guid is invalid. + /// + /// The 404 Not Found status code is returned when no data source is found with the specified data + /// source guid. + /// + [HttpGet("projects")] + [Authorize] + [ProducesResponseType(typeof(Project), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetProjectsFromExternalDataSource( + [FromQuery] string dataSourceGuid, + [FromQuery] string token, + [FromQuery] bool needsAuth) + { + if(!Guid.TryParse(dataSourceGuid, out Guid _)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Specified guid is not valid.", + Detail = "The specified guid is not a real or valid guid.", + Instance = "D84D3112-855D-480A-BCDE-7CADAC2C6C55" + }; return BadRequest(problem); } - Project project = sourceManagerService.FetchProject(sourceURI); - if(project == null) + + if(!dataProviderService.IsExistingDataSourceGuid(dataSourceGuid)) { - return NotFound(); + ProblemDetails problem = new ProblemDetails + { + Title = "Data source not found.", + Detail = "Data source could not be found with specified data source guid.", + Instance = "4FB90F9A-8499-40F1-B7F3-3C2838BDB1D4" + }; + return NotFound(problem); } - if(project.Name == null && project.ShortDescription == null && project.Description == null) + + try + { + IEnumerable projects = + await dataProviderService.GetAllProjects(dataSourceGuid, token, needsAuth); + return Ok(mapper.Map, IEnumerable>(projects)); + } catch(NotSupportedByExternalApiException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "External API does not support the functionality from the method.", + Detail = e.Message, + Instance = "8492B945-7C09-425B-9D1D-77869CE67146" + }; + return BadRequest(problem); + } catch(ExternalException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "An problem encountered while using the external API.", + Detail = e.Message, + Instance = "DE7A6BFD-2A72-46CC-AB6E-1D8568F2EB19" + }; + return BadRequest(problem); + } catch(NotSupportedException e) { ProblemDetails problem = new ProblemDetails + { + Title = "The specified data source is not supported.", + Detail = e.Message, + Instance = "E1500627-AAF8-46E3-9B20-8A3C952CDBC3" + }; + return BadRequest(problem); + } + } + + /// + /// This method is responsible for retrieving a specified project from an external data source. + /// + /// The guid that specifies the data source. + /// + /// The token which is used for retrieving the projects from the user. This token can be the + /// access token for the auth flow, but can also be the username for the public flow. + /// + /// The id of the project which is used for searching a specific project. + /// The bool that represents whether the flow with authorization should get used. + /// This method returns the project data source resource result + /// This endpoint returns the project with the specified id. + /// The 400 Bad Request status code is returned when the specified data source guid is invalid. + /// + /// The 404 Not Found status code is returned when no data source is found with the specified data + /// source guid. + /// + [HttpGet("project/{projectId}")] + [Authorize] + [ProducesResponseType(typeof(Project), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetProjectByGuidFromExternalDataSource([FromQuery] string dataSourceGuid, + [FromQuery] string token, + int projectId, + [FromQuery] bool needsAuth) + { + if(!Guid.TryParse(dataSourceGuid, out Guid _)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Specified guid is not valid.", + Detail = "The specified guid is not a real or valid guid.", + Instance = "019146D8-4162-43DD-8531-57DDD26E221C" + }; + return BadRequest(problem); + } + + if(!dataProviderService.IsExistingDataSourceGuid(dataSourceGuid)) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Data source not found.", + Detail = "Data source could not be found with specified data source guid.", + Instance = "4E3837F4-9D35-40C4-AB7C-D325FBA225E6" + }; + return NotFound(problem); + } + + try + { + Project project = + await dataProviderService.GetProjectById(dataSourceGuid, token, projectId, needsAuth); + + if(project == null) { - Title = "Project not found.", - Detail = "The incoming source uri aims at a gitlab which is either not instantiated or is a group.", - Instance = "E56D89C5-8760-4503-839C-F695092C79BF" - }; + ProblemDetails problem = new ProblemDetails + { + Title = "Project not found.", + Detail = "Project could not be found with specified project guid.", + Instance = "0D96A77A-D35F-487C-B552-BF6D1C0CDD42" + }; + return NotFound(problem); + } + + return Ok(mapper.Map(project)); + } catch(NotSupportedByExternalApiException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "External API does not support the functionality from the method.", + Detail = e.Message, + Instance = "F20B0D1F-D6B7-4BCE-9BC8-28B9E9618214" + }; + return BadRequest(problem); + } catch(ExternalException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "An problem encountered while using the external API.", + Detail = e.Message, + Instance = "21D8A923-02CB-4F1B-86C0-88FDA002294D" + }; + return BadRequest(problem); + } catch(NotSupportedException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "The specified data source is not supported.", + Detail = e.Message, + Instance = "0D02B0F5-71F8-427E-AB28-D4831B91639D" + }; return BadRequest(problem); } - return Ok(project); } + + /// + /// This method is responsible for converting the code from the data provider to the correct tokens. + /// + /// The guid or name that specifies the data source. + /// The access token which is used for authentication. + /// This method returns the project data source resource result + /// This endpoint returns the project with the specified id. + /// The 400 Bad Request status code is returned when the specified data source guid is invalid. + /// + /// The 404 Not Found status code is returned when no data source is found with the specified data + /// source guid. + /// + [HttpGet("oauth/callback/{provider}")] + [ProducesResponseType(typeof(Project), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task DataProviderCallback(string provider, string code) + { + IDataSourceAdaptee dataSourceAdaptee; + if(Guid.TryParse(provider, out Guid _)) + { + dataSourceAdaptee = await dataProviderService.RetrieveDataSourceByGuid(provider); + } else + { + dataSourceAdaptee = await dataProviderService.RetrieveDataSourceByName(provider); + } + + if(dataSourceAdaptee == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Data source not found.", + Detail = "Data source could not be found with specified data source guid.", + Instance = "5B4E11A6-8209-4F49-B76A-1EF4297D990F" + }; + return NotFound(problem); + } + + IAuthorizedDataSourceAdaptee authorizedDataSourceAdaptee = + dataSourceAdaptee as IAuthorizedDataSourceAdaptee; + + if(authorizedDataSourceAdaptee == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "The specified provider does not allowed authorization.", + Detail = "The specified provider is not able to verify the code.", + Instance = "EB1F47B2-5526-41F3-8C69-8068F12A92D1" + }; + return BadRequest(problem); + } + + try + { + OauthTokens tokens = await authorizedDataSourceAdaptee.GetTokens(code); + OauthTokensResourceResult resourceResult = mapper.Map(tokens); + return Ok(resourceResult); + } catch(NotSupportedByExternalApiException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "External API does not support the functionality from the method.", + Detail = e.Message, + Instance = "CDFFA448-38B1-450F-8D14-FB89FB7B5462" + }; + return BadRequest(problem); + } catch(ExternalException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "An problem encountered while using the external API.", + Detail = e.Message, + Instance = "7D445CB5-7C19-449C-B9FF-4214E4BE4CF0" + }; + return BadRequest(problem); + } catch(NotSupportedException e) + { + ProblemDetails problem = new ProblemDetails + { + Title = "The specified data source is not supported.", + Detail = e.Message, + Instance = "7F2C173E-F001-49CA-8DF8-C18A0837B4AF" + }; + return BadRequest(problem); + } + } + } + } diff --git a/API/Controllers/WizardPageController.cs b/API/Controllers/WizardPageController.cs new file mode 100644 index 00000000..67588207 --- /dev/null +++ b/API/Controllers/WizardPageController.cs @@ -0,0 +1,268 @@ +/* +* 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 API.Resources; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Models; +using Serilog; +using Services.Services; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace API.Controllers +{ + + /// + /// This class is responsible for handling HTTP requests that are related to + /// the wizard page, for example creating, retrieving, updating and deleting. + /// + [Route("api/[controller]")] + [ApiController] + public class WizardPageController : ControllerBase + { + + private readonly IMapper mapper; + + private readonly IWizardPageService wizardPageService; + + /// + /// Initializes a new instance of the class. + /// + /// The mapper which is used to convert the resources to the models to the resource results. + /// The wizard page service which is used to communicate with the logic layer. + public WizardPageController(IMapper mapper, IWizardPageService wizardPageService) + { + this.mapper = mapper; + this.wizardPageService = wizardPageService; + } + + /// + /// This method is responsible for retrieving all the wizard pages stored in the database. + /// + /// This method returns a collection of wizard pages. + /// This endpoint returns the collection of wizard pages. + [HttpGet] + [Authorize] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllPages() + { + IEnumerable pages = await wizardPageService.GetAll(); + IEnumerable models = + mapper.Map, IEnumerable>(pages); + return Ok(models); + } + + /// + /// This method is responsible for retrieving a wizard page by the specified id. + /// + /// The id which is used for searching the project wizard page. + /// This method returns the wizard page with the specified id. + /// This endpoint returns a wizard page with the specified id. + /// The 400 Bad Request status code is returned when the id is not specified. + /// + /// The 404 Not Found status code is returned when no wizard page could be + /// found with the specified id. + /// + [HttpGet("{id}")] + [Authorize] + [ProducesResponseType(typeof(WizardPageResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task GetPageById(int id) + { + if(id <= 0) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Invalid id specified.", + Detail = "The specified id is invalid.", + Instance = "C204ED32-70A4-498D-9EB2-A73EF69F4DA0" + }; + return BadRequest(problem); + } + + WizardPage page = await wizardPageService.FindAsync(id); + if(page == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the wizard page.", + Detail = + "The database does not contain a wizard page with the specified id.", + Instance = "E562B217-5847-4429-B61B-FAF1F8B33975" + }; + return NotFound(problem); + } + + WizardPageResourceResult model = mapper.Map(page); + return Ok(model); + } + + /// + /// This method is responsible for creating a wizard page. + /// + /// The wizard page resource is used for creating the wizard page. + /// This method returns the created wizard page. + /// This endpoint returns the created wizard page. + /// + /// The 400 Bad Request status code is returned when the id is invalid or + /// when a database update exception occured. + /// + [HttpPost] + [Authorize] + [ProducesResponseType(typeof(WizardPageResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + public async Task CreateWizardPage(WizardPageResource wizardPageResource) + { + if(wizardPageResource == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed creating the wizard page.", + Detail = "The wizard page resource is null.", + Instance = "9EDA7F00-BA1E-4DD2-808D-093853FC5534" + }; + return BadRequest(problem); + } + + WizardPage wizardPage = mapper.Map(wizardPageResource); + + try + { + await wizardPageService.AddAsync(wizardPage); + wizardPageService.Save(); + WizardPageResourceResult createdPage = mapper.Map(wizardPage); + return Created(nameof(CreateWizardPage), createdPage); + } catch(DbUpdateException e) + { + Log.Logger.Error(e, "Database exception"); + + ProblemDetails problem = new ProblemDetails + { + Title = "Failed saving wizard page.", + Detail = "Failed saving the wizard page to the database.", + Instance = "09BCA75E-7615-4E30-9379-61A0C9DC05B8" + }; + return BadRequest(problem); + } + } + + /// + /// This method is responsible for updating a wizard page. + /// + /// The wizard page resource is used for updating the wizard page. + /// The id is used for searching the wizard page that will get updated. + /// This method returns the updated wizard page. + /// This endpoint returns the updated wizard page. + /// The 400 Bad Request status code is returned when the id is invalid. + /// + /// The 404 Not Found status code is returned when no wizard page could could get found with + /// the specified id. + /// + [HttpPut("{id}")] + [Authorize] + [ProducesResponseType(typeof(WizardPageResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task UpdatedWizardPage(int id, [FromBody] WizardPageResource wizardPageResource) + { + if(id <= 0) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Invalid id specified.", + Detail = "The specified id is invalid.", + Instance = "EC827999-28A5-42EF-A160-F8729F26DB13" + }; + return BadRequest(problem); + } + + WizardPage wizardPage = await wizardPageService.FindAsync(id); + if(wizardPage == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the wizard page.", + Detail = + "The database does not contain a wizard page with the specified id.", + Instance = "ED11431F-AE28-43EE-A1E4-780354217A1E" + }; + return NotFound(problem); + } + + mapper.Map(wizardPageResource, wizardPage); + + wizardPageService.Update(wizardPage); + wizardPageService.Save(); + + WizardPageResourceResult model = mapper.Map(wizardPage); + return Ok(model); + } + + /// + /// This method is responsible for deleting a wizard page. + /// + /// The id is used for searching the wizard page that will get deleted. + /// This method returns status code 200 Ok. The wizard page is deleted. + /// This endpoint returns status cod 200 Ok. The wizard page is deleted. + /// The 400 Bad Request status code is returned when the id is invalid. + /// + /// The 404 Not Found status code is returned when no wizard page could could get found with + /// the specified id. + /// + [HttpDelete("{id}")] + [Authorize] + [ProducesResponseType(typeof(WizardPageResourceResult), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + public async Task DeleteWizardPage(int id) + { + if(id <= 0) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Invalid id specified.", + Detail = "The specified id is invalid.", + Instance = "CB6F045F-0F2A-4988-B1F2-3B6B1E8F34AD" + }; + return BadRequest(problem); + } + WizardPage wizardPage = await wizardPageService.FindAsync(id); + if(wizardPage == null) + { + ProblemDetails problem = new ProblemDetails + { + Title = "Failed getting the wizard page.", + Detail = + "The database does not contain a wizard page with the specified id.", + Instance = "E225D99D-34DB-4B4C-99B5-D6E0722A1F4F" + }; + return NotFound(problem); + } + + await wizardPageService.RemoveAsync(id); + wizardPageService.Save(); + return Ok(); + } + + } + +} diff --git a/API/Dockerfile b/API/Dockerfile index f6857276..82a580f5 100644 --- a/API/Dockerfile +++ b/API/Dockerfile @@ -16,7 +16,7 @@ ENV App__IdentityServer__IdentityUrl = '' ENV SENTRY_DSN = '' -RUN dotnet publish API/1_API.csproj -c Release -o out +RUN dotnet publish API/01_API.csproj -c Release -o out FROM mcr.microsoft.com/dotnet/aspnet:3.1 @@ -24,4 +24,4 @@ WORKDIR /app COPY --from=build-env /app/out . -ENTRYPOINT ["dotnet", "1_API.dll"] +ENTRYPOINT ["dotnet", "01_API.dll"] diff --git a/API/Dockerfile.dev b/API/Dockerfile.dev index b604eb9a..00e512f6 100644 --- a/API/Dockerfile.dev +++ b/API/Dockerfile.dev @@ -23,4 +23,4 @@ COPY --from=build-env /app/API/bin/Debug/netcoreapp3.1 . COPY --from=build-env /app/API/Uploads/Images ./Uploads/Images COPY --from=build-env /app/dex-api.pfx . -ENTRYPOINT ["dotnet", "1_API.dll", "--environment=Development"] +ENTRYPOINT ["dotnet", "01_API.dll", "--environment=Development"] diff --git a/API/Extensions/AllowedExtensionsAttribute.cs b/API/Extensions/AllowedExtensionsAttribute.cs index 15a4e75b..e9d62316 100644 --- a/API/Extensions/AllowedExtensionsAttribute.cs +++ b/API/Extensions/AllowedExtensionsAttribute.cs @@ -1,22 +1,38 @@ +/* +* 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.AspNetCore.Http; -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; -using System.Threading.Tasks; namespace API.Extensions { + /// - /// Attribute for allowed file extensions + /// Attribute for allowed file extensions /// public class AllowedExtensionsAttribute : ValidationAttribute { + private readonly string[] extensions; /// - /// Constructor for allowed extensions + /// Constructor for allowed extensions /// /// array of extensions public AllowedExtensionsAttribute(string[] extensions) @@ -25,13 +41,14 @@ public AllowedExtensionsAttribute(string[] extensions) } /// - /// Method which checks if extensions are allowed + /// Method which checks if extensions are allowed /// /// /// /// protected override ValidationResult IsValid( - object value, ValidationContext validationContext) + object value, + ValidationContext validationContext) { IFormFile file = value as IFormFile; if(file == null) @@ -40,32 +57,34 @@ protected override ValidationResult IsValid( } string extension = Path.GetExtension(file.FileName); - + if(!extensions.Contains(extension.ToLower())) { return new ValidationResult(GetErrorMessage()); } - + return ValidationResult.Success; } /// - /// Error message + /// Error message /// /// public string GetErrorMessage() { - return $"This file extension is not allowed!"; + return "This file extension is not allowed!"; } /// - /// Error message + /// Error message /// /// public string FileIsNullError() { return "File is null"; } + } + } diff --git a/API/Extensions/AuthorizeScopeAttribute.cs b/API/Extensions/AuthorizeScopeAttribute.cs index 33caf93e..c7b73ed6 100644 --- a/API/Extensions/AuthorizeScopeAttribute.cs +++ b/API/Extensions/AuthorizeScopeAttribute.cs @@ -27,31 +27,35 @@ namespace API.Extensions { + /// /// Easily grab scopes /// public class AuthorizeScopeAttribute : TypeFilterAttribute { + /// - /// Currently deprecated - /// Initializes a new instance of the class. + /// Currently deprecated + /// Initializes a new instance of the class. /// /// The claim value. public AuthorizeScopeAttribute(string claimValue) : base(typeof(AuthorizeScopeFilter)) { Arguments = new object[] {new Claim("scope", claimValue)}; } + } /// - /// Deprecated attribute filter. + /// Deprecated attribute filter. /// public class AuthorizeScopeFilter : IAuthorizationFilter { + private readonly Claim claim; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The claim. public AuthorizeScopeFilter(Claim claim) @@ -60,16 +64,20 @@ public AuthorizeScopeFilter(Claim claim) } /// - /// Called early in the filter pipeline to confirm request is authorized. + /// Called early in the filter pipeline to confirm request is authorized. /// - /// The The current context. + /// + /// The The current + /// context. + /// public void OnAuthorization(AuthorizationFilterContext context) { - UserService userService = context.HttpContext.RequestServices.GetService(typeof(UserService)) as UserService; + UserService userService = + context.HttpContext.RequestServices.GetService(typeof(UserService)) as UserService; bool hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == claim.Type && c.Value == claim.Value); //Get all scopes from the user - IEnumerable scopes = + IEnumerable scopes = context.HttpContext.User.FindAll("scope"); bool hasIdentityClaim = false; @@ -109,5 +117,7 @@ public void OnAuthorization(AuthorizationFilterContext context) context.Result = new ForbidResult(); } } + } + } diff --git a/API/Extensions/AutoMapperExtensions.cs b/API/Extensions/AutoMapperExtensions.cs index 428fa9b1..55b9ffeb 100644 --- a/API/Extensions/AutoMapperExtensions.cs +++ b/API/Extensions/AutoMapperExtensions.cs @@ -21,11 +21,13 @@ namespace API.Extensions { + /// /// AutoMapperExtensions /// public static class AutoMapperExtensions { + /// /// Adds the autoMapper. /// @@ -41,5 +43,7 @@ public static IServiceCollection AddAutoMapper(this IServiceCollection services) return services; } + } + } diff --git a/API/Extensions/DependencyInjectionExtensions.cs b/API/Extensions/DependencyInjectionExtensions.cs index dc1d9b2d..9fcb3a9e 100644 --- a/API/Extensions/DependencyInjectionExtensions.cs +++ b/API/Extensions/DependencyInjectionExtensions.cs @@ -19,21 +19,23 @@ using API.HelperClasses; using Data; using MessageBrokerPublisher; -using MessageBrokerPublisher.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Repositories; +using Services.ExternalDataProviders; using Services.Services; using Services.Sources; namespace API.Extensions { + /// /// DependencyInjectionExtensions /// public static class DependencyInjectionExtensions { + /// /// Adds all the services and repositories. /// @@ -65,6 +67,9 @@ public static IServiceCollection AddServicesAndRepositories(this IServiceCollect services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); @@ -77,6 +82,7 @@ public static IServiceCollection AddServicesAndRepositories(this IServiceCollect services.AddScoped(); services.AddScoped(); + services.AddScoped(typeof(IIndexOrderHelper<>), typeof(IndexOrderHelper<>)); services.AddScoped(); services.AddScoped(); @@ -84,14 +90,41 @@ public static IServiceCollection AddServicesAndRepositories(this IServiceCollect services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + services.AddExternalDataSources(); + + return services; + } + + private static IServiceCollection AddExternalDataSources(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); - + services.AddScoped(); + return services; } + } + } diff --git a/API/Extensions/MaxFileSizeAttribute.cs b/API/Extensions/MaxFileSizeAttribute.cs index ab1f9ec1..973a4cf8 100644 --- a/API/Extensions/MaxFileSizeAttribute.cs +++ b/API/Extensions/MaxFileSizeAttribute.cs @@ -1,36 +1,52 @@ +/* +* 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.AspNetCore.Http; -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; namespace API.Extensions { + /// - /// Attribute for maximum file size + /// Attribute for maximum file size /// public class MaxFileSizeAttribute : ValidationAttribute { + private readonly int maxFileSize; /// - /// Constructor for maximum filesize attribute + /// Constructor for maximum filesize attribute with file size in bytes. /// - /// + /// Max file size in bytes public MaxFileSizeAttribute(int maxFileSize) { this.maxFileSize = maxFileSize; } /// - /// Methods which checks if file is not larger than allowed size + /// Methods which checks if file is not larger than allowed size /// /// /// /// protected override ValidationResult IsValid( - object value, ValidationContext validationContext) + object value, + ValidationContext validationContext) { IFormFile file = value as IFormFile; if(file == null) @@ -42,12 +58,12 @@ protected override ValidationResult IsValid( { return new ValidationResult(GetErrorMessage()); } - + return ValidationResult.Success; } /// - /// Error messsage + /// Error messsage /// /// public string GetErrorMessage() @@ -56,12 +72,14 @@ public string GetErrorMessage() } /// - /// Error message + /// Error message /// /// public string FileIsNullError() { return "File is null"; } + } + } diff --git a/API/Extensions/ScopeRequirement.cs b/API/Extensions/ScopeRequirement.cs index 3cc22da8..9797524c 100644 --- a/API/Extensions/ScopeRequirement.cs +++ b/API/Extensions/ScopeRequirement.cs @@ -1,44 +1,67 @@ -using System.Threading.Tasks; +/* +* 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.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Serilog; using Services.Services; using System; +using System.Threading.Tasks; namespace API.Extensions { + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// - public class ScopeRequirement: IAuthorizationRequirement + public class ScopeRequirement : IAuthorizationRequirement { + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The scope. public ScopeRequirement(string scope) { RequiredScope = scope; } + /// - /// Gets or sets the required scope. + /// Gets or sets the required scope. /// /// - /// The required scope. + /// The required scope. /// public string RequiredScope { get; set; } + } /// - /// This handler is called every time authorize is called with a policy created by the scope requirement class. + /// This handler is called every time authorize is called with a policy created by the scope requirement class. /// public class ScopeRequirementHandler : AuthorizationHandler { - private readonly IUserService userService; + private readonly IHttpContextAccessor httpContextAccessor; + private readonly IUserService userService; + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The HTTP context accessor. /// The userservice. @@ -47,13 +70,15 @@ public ScopeRequirementHandler(IHttpContextAccessor httpContextAccessor, IUserSe userService = userservice; this.httpContextAccessor = httpContextAccessor; } + /// - /// Makes a decision if authorization is allowed based on a specific requirement. + /// Makes a decision if authorization is allowed based on a specific requirement. /// /// The authorization context. /// The requirement to evaluate. /// CompletedTask. - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopeRequirement requirement) + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, + ScopeRequirement requirement) { string identityId; try @@ -76,5 +101,7 @@ protected override Task HandleRequirementAsync(AuthorizationHandlerContext conte } return Task.CompletedTask; } + } + } diff --git a/API/Extensions/ServiceExtensions.cs b/API/Extensions/ServiceExtensions.cs index c75fa059..8ec990b8 100644 --- a/API/Extensions/ServiceExtensions.cs +++ b/API/Extensions/ServiceExtensions.cs @@ -22,11 +22,13 @@ namespace API.Extensions { + /// /// ServicesExtensions /// public static class ServicesExtensions { + /// /// Adds the policies. /// @@ -53,5 +55,7 @@ public static IServiceCollection AddPolicies(this IServiceCollection services) return services; } + } + } diff --git a/API/Extensions/UsersExtensions.cs b/API/Extensions/UsersExtensions.cs index feac5cc6..5657c368 100644 --- a/API/Extensions/UsersExtensions.cs +++ b/API/Extensions/UsersExtensions.cs @@ -17,10 +17,7 @@ using API.Configuration; using API.InternalResources; -using IdentityModel.Client; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Models; using Models.Defaults; using Newtonsoft.Json; @@ -28,27 +25,27 @@ using RestSharp; using Services.Services; using System; +using System.IdentityModel.Tokens.Jwt; using System.Linq; -using System.Net.Http; using System.Security.Claims; -using System.Security.Principal; using System.Threading.Tasks; -using System.IdentityModel.Tokens.Jwt; namespace API.Extensions { + internal static class UsersExtensions { + /// - /// Gets the identity identifier. + /// Gets the identity identifier. /// /// The claims principal. /// The action context. /// The users identity id as string /// - /// User is not authenticated! - /// or - /// The back-end header isn't added! + /// User is not authenticated! + /// or + /// The back-end header isn't added! /// /// The jwt doesn't have a sub public static string GetIdentityId(this ClaimsPrincipal claimsPrincipal, HttpContext actionContext) @@ -60,11 +57,12 @@ public static string GetIdentityId(this ClaimsPrincipal claimsPrincipal, HttpCon throw new UnauthorizedAccessException("User is not authenticated!"); } - if(claimsPrincipal.IsInRole(Defaults.Roles.BackendApplication) || claimsPrincipal.HasClaim("client_role", Defaults.Roles.BackendApplication)) + if(claimsPrincipal.IsInRole(Defaults.Roles.BackendApplication) || + claimsPrincipal.HasClaim("client_role", Defaults.Roles.BackendApplication)) { string identityIdHeader = actionContext.Request.Headers.SingleOrDefault(h => h.Key == "IdentityId") - .Value - .FirstOrDefault(); + .Value + .FirstOrDefault(); if(string.IsNullOrWhiteSpace(identityIdHeader)) { @@ -88,7 +86,7 @@ public static string GetIdentityId(this ClaimsPrincipal claimsPrincipal, HttpCon } /// - /// Gets the context user. + /// Gets the context user. /// /// The action context. /// The user service. @@ -101,15 +99,16 @@ public static async Task GetContextUser(this HttpContext actionContext, IU /// - /// Gets the user information synchronous. - /// this is triggered when a user makes a request who does not have an account already. + /// Gets the user information synchronous. + /// this is triggered when a user makes a request who does not have an account already. /// /// The action context. /// The configuration. /// The user object with information retrieved from the identity server public static UserCreateInternalResource GetUserInformation(this HttpContext actionContext, Config config) { - string bearerToken = actionContext.Request.Headers.GetCommaSeparatedValues("Authorization").FirstOrDefault(); + string bearerToken = actionContext.Request.Headers.GetCommaSeparatedValues("Authorization") + .FirstOrDefault(); string providerId = ""; if(bearerToken != null) @@ -118,7 +117,8 @@ public static UserCreateInternalResource GetUserInformation(this HttpContext act JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); if(handler.ReadToken(token) is JwtSecurityToken tokens) { - providerId = tokens.Claims.FirstOrDefault(claim => claim.Type == "idp")?.Value; + providerId = tokens.Claims.FirstOrDefault(claim => claim.Type == "idp") + ?.Value; } } @@ -126,6 +126,7 @@ public static UserCreateInternalResource GetUserInformation(this HttpContext act { return null; } + // Not sure maybe has to be retrieved from the originating identity server aka from the token iss. RestClient client = new RestClient(config.IdentityServer.IdentityUrl + "/connect/userinfo"); RestRequest request = new RestRequest(Method.POST); @@ -140,15 +141,20 @@ public static UserCreateInternalResource GetUserInformation(this HttpContext act } UserCreateInternalResource newUser = new UserCreateInternalResource - { - Name = (string) jsonResponse["name"], - Email = (string) jsonResponse["email"], - IdentityId = (string) jsonResponse["sub"], - IdentityInstitutionId = providerId - }; - return newUser ; + { + Name = (string) jsonResponse["name"], + Email = (string) jsonResponse["email"], + IdentityId = (string) jsonResponse["sub"], + IdentityInstitutionId = providerId + }; + return newUser; } + /// + /// This method checks if the user is authenticated + /// + /// + /// boolean public static bool CheckIfUserIsAuthenticated(this ClaimsPrincipal claimsPrincipal) { if(claimsPrincipal.Identities.Any(i => i.IsAuthenticated)) @@ -160,4 +166,5 @@ public static bool CheckIfUserIsAuthenticated(this ClaimsPrincipal claimsPrincip } } + } diff --git a/API/Filters/DefaultOperationFilter.cs b/API/Filters/DefaultOperationFilter.cs index 92f09361..4c78cf53 100644 --- a/API/Filters/DefaultOperationFilter.cs +++ b/API/Filters/DefaultOperationFilter.cs @@ -22,15 +22,17 @@ namespace API.Filters { + /// - /// Filter for all endpoints to make sure that the response - /// media type will be set to 'application/json'. + /// Filter for all endpoints to make sure that the response + /// media type will be set to 'application/json'. /// public class DefaultOperationFilter : IOperationFilter { + /// - /// Foreach http status code keep the media type for application/json - /// and remove the other media types. + /// Foreach http status code keep the media type for application/json + /// and remove the other media types. /// /// API Operation /// Filter context @@ -41,8 +43,10 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) bool hasConsumeAttribute = context.MethodInfo.GetCustomAttributes(true) .Union(context.MethodInfo.GetCustomAttributes(true)) .OfType() - .Count() != 0; - if(operation.RequestBody != null && !hasConsumeAttribute) + .Count() != + 0; + if(operation.RequestBody != null && + !hasConsumeAttribute) { OpenApiMediaType mediaType = operation.RequestBody.Content["application/json"]; operation.RequestBody.Content.Clear(); @@ -54,10 +58,14 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) if(operation.Responses[code] .Content.TryGetValue("application/json", out OpenApiMediaType mediaType)) { - operation.Responses[code].Content.Clear(); - operation.Responses[code].Content.Add("application/json", mediaType); + operation.Responses[code] + .Content.Clear(); + operation.Responses[code] + .Content.Add("application/json", mediaType); } } } + } + } diff --git a/API/GlobalSuppressions.cs b/API/GlobalSuppressions.cs index f246a2f5..7c23e09b 100644 --- a/API/GlobalSuppressions.cs +++ b/API/GlobalSuppressions.cs @@ -5,4 +5,10 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "The exception builder is allowed to catch every exception because every exception should be logged and the api should be stopped.", Scope = "member", Target = "~M:API.Program.Main(System.String[])~System.Int32")] +[assembly: + SuppressMessage("Design", + "CA1031:Do not catch general exception types", + Justification = + "The exception builder is allowed to catch every exception because every exception should be logged and the api should be stopped.", + Scope = "member", + Target = "~M:API.Program.Main(System.String[])~System.Int32")] diff --git a/API/HelperClasses/EmailSender.cs b/API/HelperClasses/EmailSender.cs deleted file mode 100644 index cbcc0169..00000000 --- a/API/HelperClasses/EmailSender.cs +++ /dev/null @@ -1,48 +0,0 @@ -using MessageBrokerPublisher; -using MessageBrokerPublisher.Models; -using MessageBrokerPublisher.Services; - -namespace API.HelperClasses -{ - /// - /// EmailSender Interface - /// - public interface IEmailSender - { - /// - /// Method to send email - /// - /// The email address of the recipient - /// The text content of the email - /// The HTML content of the email - public void Send(string recipient, string textContent, string htmlContent); - } - - /// - /// Class which is responsible for sending emails - /// - public class EmailSender : IEmailSender - { - private ITaskPublisher notificationSender; - - /// - /// Constructor to instantiate the email sender - /// - public EmailSender(ITaskPublisher notificationSender) - { - this.notificationSender = notificationSender; - } - - /// - /// Method to send email - /// - /// The email address of the recipient - /// The text content of the email - /// The HTML content of the email - public void Send(string recipient, string textContent, string htmlContent = null) - { - EmailNotificationRegister emailNotification = new EmailNotificationRegister(recipient, textContent, htmlContent); - notificationSender.RegisterTask(Newtonsoft.Json.JsonConvert.SerializeObject(emailNotification), Subject.EMAIL); - } - } -} diff --git a/API/HelperClasses/FileUploader.cs b/API/HelperClasses/FileUploader.cs index 474314ec..9ae71b41 100644 --- a/API/HelperClasses/FileUploader.cs +++ b/API/HelperClasses/FileUploader.cs @@ -1,3 +1,20 @@ +/* +* 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.AspNetCore.Http; using Models.Defaults; using Models.Exceptions; @@ -9,15 +26,15 @@ namespace API.HelperClasses { - /// - /// Interface for file uploader + /// Interface for file uploader /// public interface IFileUploader { + /// - /// Uploads single file + /// Uploads single file /// /// /// @@ -25,14 +42,15 @@ public interface IFileUploader Task CopyFileToDirectory(IFormFile file, string fileName); /// - /// Method deletes the file from the file server + /// Method deletes the file from the file server /// /// void DeleteFileFromDirectory(File file); + } /// - /// Class which is responsible for uploading files + /// Class which is responsible for uploading files /// public class FileUploader : IFileUploader { @@ -40,18 +58,18 @@ public class FileUploader : IFileUploader private readonly string uploadPath; /// - /// File Uploader + /// File Uploader /// public FileUploader() { - uploadPath = Defaults.Path.filePath; + uploadPath = Defaults.Path.FilePath; } /// - /// Uploads single file + /// Uploads single file /// /// File to upload - /// Name of file + /// Name of file /// path of file location public async Task CopyFileToDirectory(IFormFile file, string fileName) { @@ -71,13 +89,14 @@ public async Task CopyFileToDirectory(IFormFile file, string fileName) } /// - /// Method deletes the file from the file server + /// Method deletes the file from the file server /// /// /// Bool which tells if file is deleted successfully or not public void DeleteFileFromDirectory(File file) { - if(System.IO.File.Exists(Path.Combine(uploadPath, file.Name))) { + if(System.IO.File.Exists(Path.Combine(uploadPath, file.Name))) + { System.IO.File.Delete(Path.Combine(uploadPath, file.Name)); return; } @@ -86,4 +105,5 @@ public void DeleteFileFromDirectory(File file) } } + } diff --git a/API/HelperClasses/IndexOrderHelper.cs b/API/HelperClasses/IndexOrderHelper.cs new file mode 100644 index 00000000..62befff4 --- /dev/null +++ b/API/HelperClasses/IndexOrderHelper.cs @@ -0,0 +1,92 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +using System; +using System.Linq; + +namespace API.HelperClasses +{ + + /// + /// The interface for the Index Order helper. + /// + /// Type T which should implement the IComparable interface. + public interface IIndexOrderHelper where T : IComparable + { + + /// + /// This method check if a series of indexes is ascending and consecutive (order is not + /// looked at). + /// + /// The indexes are the series of indexes that will get checked. + /// The starting index is the minimal value where the indexes should start from. + /// + /// This method returns true or false. This method returns true when the series of indexes are ascending and + /// consecutive + /// and will return false whenever this is not the case. + /// + bool ValidateAscendingConsecutiveOrder(T[] indexes, T startingIndex); + + } + + /// + /// The implementation for the Index Order helper. + /// + /// Type T which should implement the IComparable interface. + public class IndexOrderHelper : IIndexOrderHelper where T : IComparable + { + + /// + /// This method check if a series of indexes is ascending and consecutive (order is not + /// looked at). + /// + /// The indexes are the series of indexes that will get checked. + /// The starting index is the minimal value where the indexes should start from. + /// + /// This method returns true or false. This method returns true when the series of indexes are ascending and + /// consecutive + /// and will return false whenever this is not the case. + /// + public bool ValidateAscendingConsecutiveOrder(T[] indexes, T startingIndex) + { + if(indexes != null) + { + // Check if there are no doubles + if(indexes.Length != + indexes.Distinct() + .Count()) + return false; + + // Check if there is nothing smaller then the starting index + if(indexes.Min() + .CompareTo(startingIndex) < + 0) + return false; + + // Check if the highest index is same as the length of the list. + if(indexes.Max() + .CompareTo(indexes.Length) != + 0) + return false; + } + + return true; + } + + } + +} diff --git a/API/HelperClasses/SeedHelper.cs b/API/HelperClasses/SeedHelper.cs new file mode 100644 index 00000000..282dee40 --- /dev/null +++ b/API/HelperClasses/SeedHelper.cs @@ -0,0 +1,87 @@ +using Data; +using Microsoft.EntityFrameworkCore; +using Models; +using System.Collections.Generic; +using System.Linq; + +namespace API.HelperClasses +{ + + /// + /// This class checks if the seed matches the data in the database. If it doesn't match, it updates the database. + /// + public static class SeedHelper + { + + /// + /// This method check if roles in the seed match the roles in the database. If they don't match, the roles are updated + /// or added. + /// + /// + /// + public static void InsertRoles(List seededRoles, ApplicationDbContext context) + { + List rolesInDb = context.Role.AsQueryable() + .Include(s => s.Scopes) + .ToList(); + + foreach(Role entityInSeed in seededRoles) + { + if(rolesInDb.Find(e => e.Name == entityInSeed.Name) == null) + { + context.Role.Add(entityInSeed); + continue; + } + + Role foundEntity = rolesInDb.Find(e => e.Name == entityInSeed.Name); + + List roleScopesToAdd = FindRoleScopesNotInDb(entityInSeed.Scopes, foundEntity.Scopes); + foundEntity.Scopes.AddRange(roleScopesToAdd); + context.Role.Update(foundEntity); + } + context.SaveChanges(); + } + + /// + /// This method checks if the role scopes for a specific role match the ones in the seed. If they don't they are added + /// to a list and being returned. + /// + /// + /// + /// + public static List FindRoleScopesNotInDb(List seededRoleScope, + List roleScopeInDb) + { + return seededRoleScope + .Where(entityInSeed => roleScopeInDb?.Find(e => e.Scope == entityInSeed.Scope) == null) + .ToList(); + } + + /// + /// This method checks if the seeded user is already in the database. The user should match identityId and role. If it + /// does not match, the user is updated or added. + /// + /// + /// + public static void InsertUser(User seedUser, ApplicationDbContext context) + { + List usersInDb = context.User.AsQueryable() + .Include(e => e.Role) + .ToList(); + + if(usersInDb.Find(e => e.IdentityId == seedUser.IdentityId) != null) + { + User foundEntity = usersInDb.Find(e => e.IdentityId == seedUser.IdentityId); + foundEntity.Role = seedUser.Role; + context.Update(foundEntity); + context.SaveChanges(); + return; + } + + context.User.Add(seedUser); + context.SaveChanges(); + } + + } + +} diff --git a/API/InternalResources/UserCreateInternalResource.cs b/API/InternalResources/UserCreateInternalResource.cs index 9d1d31c2..3c08d320 100644 --- a/API/InternalResources/UserCreateInternalResource.cs +++ b/API/InternalResources/UserCreateInternalResource.cs @@ -17,29 +17,31 @@ namespace API.InternalResources { + /// - /// The internal model resource for retrieving user data and - /// creating the user + /// The internal model resource for retrieving user data and + /// creating the user /// public class UserCreateInternalResource { + /// - /// Gets or sets the name of the internal user resource. + /// Gets or sets the name of the internal user resource. /// public string Name { get; set; } /// - /// Gets or sets the Email of the internal user resource. + /// Gets or sets the Email of the internal user resource. /// public string Email { get; set; } /// - /// Gets or sets the identity id of the internal user resource. + /// Gets or sets the identity id of the internal user resource. /// public string IdentityId { get; set; } /// - /// Gets or sets the identity institution id of the internal user resource. + /// Gets or sets the identity institution id of the internal user resource. /// public string IdentityInstitutionId { get; set; } diff --git a/API/Program.cs b/API/Program.cs index 0b61586f..f4328977 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -15,8 +15,6 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using MessageBrokerPublisher; -using MessageBrokerPublisher.Services; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -28,13 +26,15 @@ namespace API { + /// /// Program.cs /// public static class Program { + /// - /// Mains the specified arguments. + /// Mains the specified arguments. /// /// The arguments. /// The exit code of the program. @@ -74,7 +74,7 @@ public static int Main(string[] args) } /// - /// Creates the host builder. + /// Creates the host builder. /// /// The arguments. /// The webhostbuilder instance. @@ -95,5 +95,7 @@ public static IWebHostBuilder CreateHostBuilder(string[] args) .UseKestrel(o => o.AddServerHeader = false) .UseSerilog(); } + } + } diff --git a/API/Resources/CallToActionOptionResource.cs b/API/Resources/CallToActionOptionResource.cs index a69a8322..edb5f4f6 100644 --- a/API/Resources/CallToActionOptionResource.cs +++ b/API/Resources/CallToActionOptionResource.cs @@ -17,18 +17,20 @@ namespace API.Resources { + /// - /// The view model of a call to action option. + /// The view model of a call to action option. /// public class CallToActionOptionResource { + /// - /// Gets or sets the name of the Type of a call to action option. + /// Gets or sets the name of the Type of a call to action option. /// public string Type { get; set; } /// - /// Gets or sets the name of the Value of a call to action option. + /// Gets or sets the name of the Value of a call to action option. /// public string Value { get; set; } diff --git a/API/Resources/CallToActionOptionResourceResult.cs b/API/Resources/CallToActionOptionResourceResult.cs index b349f278..cad879ff 100644 --- a/API/Resources/CallToActionOptionResourceResult.cs +++ b/API/Resources/CallToActionOptionResourceResult.cs @@ -19,13 +19,14 @@ namespace API.Resources { /// - /// Resource Result for call to action option + /// Resource Result for call to action option /// public class CallToActionOptionResourceResult : CallToActionOptionResource { + /// - /// Gets or sets the id of the call to action option. + /// Gets or sets the id of the call to action option. /// public int Id { get; set; } diff --git a/API/Resources/CallToActionResource.cs b/API/Resources/CallToActionResource.cs index 584a9c17..21cb03eb 100644 --- a/API/Resources/CallToActionResource.cs +++ b/API/Resources/CallToActionResource.cs @@ -19,20 +19,20 @@ namespace API.Resources { /// - /// The view model of a call to action. + /// The view model of a call to action. /// public class CallToActionResource { /// - /// Gets or sets a value for the OptionValue property. - /// For example, this would be the Title on the call to action button. + /// Gets or sets a value for the OptionValue property. + /// For example, this would be the Title on the call to action button. /// public string OptionValue { get; set; } /// - /// Gets or sets a value for the Value property. - /// For example, this would be the redirect url for the call to action button. + /// Gets or sets a value for the Value property. + /// For example, this would be the redirect url for the call to action button. /// public string Value { get; set; } diff --git a/API/Resources/CallToActionResourceResult.cs b/API/Resources/CallToActionResourceResult.cs index cce9d37d..134730e1 100644 --- a/API/Resources/CallToActionResourceResult.cs +++ b/API/Resources/CallToActionResourceResult.cs @@ -19,13 +19,14 @@ namespace API.Resources { /// - /// Resource Result for call to action + /// Resource Result for call to action /// public class CallToActionResourceResult : CallToActionResource { + /// - /// Gets or sets the id of the call to action. + /// Gets or sets the id of the call to action. /// public int Id { get; set; } diff --git a/API/Resources/CollaboratorResource.cs b/API/Resources/CollaboratorResource.cs index cc42bd21..79f38130 100644 --- a/API/Resources/CollaboratorResource.cs +++ b/API/Resources/CollaboratorResource.cs @@ -17,11 +17,13 @@ namespace API.Resources { + /// /// The view model of a collaborator /// public class CollaboratorResource { + /// /// Get or Set the Fullname of a collaborator /// @@ -31,5 +33,7 @@ public class CollaboratorResource /// Get or Set the Role of a collaborator /// public string Role { get; set; } + } + } diff --git a/API/Resources/DataSourceResource.cs b/API/Resources/DataSourceResource.cs new file mode 100644 index 00000000..9208c16a --- /dev/null +++ b/API/Resources/DataSourceResource.cs @@ -0,0 +1,56 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +using System.Collections.Generic; + +namespace API.Resources +{ + + /// + /// The view model of a data source. + /// + public class DataSourceResource + { + + /// + /// Gets or Set the Title of the data source. + /// + public string Title { get; set; } + + /// + /// Gets or sets the Description of the data source. + /// + public string Description { get; set; } + + /// + /// Gets or sets the visibility of the data source. + /// + public bool IsVisible { get; set; } + + /// + /// Gets or sets the icon id of the data source. + /// + public int IconId { get; set; } + + /// + /// Gets or sets the wizard page resources for the data source. + /// + public IEnumerable WizardPageResources { get; set; } + + } + +} diff --git a/API/Resources/DataSourceResourceResult.cs b/API/Resources/DataSourceResourceResult.cs new file mode 100644 index 00000000..279d05f0 --- /dev/null +++ b/API/Resources/DataSourceResourceResult.cs @@ -0,0 +1,62 @@ +/* +* 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 Models; +using System.Collections.Generic; + +namespace API.Resources +{ + + /// + /// Resource Result for a data source. + /// + public class DataSourceResourceResult + { + + /// + /// Get or set the guid of a data source. + /// + public string Guid { get; set; } + + /// + /// Gets or Set the Title of the data source. + /// + public string Title { get; set; } + + /// + /// Gets or sets the Description of the data source. + /// + public string Description { get; set; } + + /// + /// Gets or sets the visibility of the data source. + /// + public bool IsVisible { get; set; } + + /// + /// Gets or sets the icon of the data source. + /// + public File Icon { get; set; } + + /// + /// Gets or sets the wizard pages of the data source. + /// + public IEnumerable WizardPages { get; set; } + + } + +} diff --git a/API/Resources/DataSourceWizardPageResource.cs b/API/Resources/DataSourceWizardPageResource.cs new file mode 100644 index 00000000..2dee57d2 --- /dev/null +++ b/API/Resources/DataSourceWizardPageResource.cs @@ -0,0 +1,46 @@ +/* +* 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 +*/ + +namespace API.Resources +{ + + /// + /// The view model for the wizard page in the data source. This model differs from the wizard page resource model + /// by having the order index and auth flow property and the wizard page id refers to the model of the wizard page + /// resource model. + /// + public class DataSourceWizardPageResource + { + + /// + /// Gets or sets the wizard page id. This references a wizard page model. + /// + public int WizardPageId { get; set; } + + /// + /// Gets or sets the order index of the wizard page in this data source. + /// + public int OrderIndex { get; set; } + + /// + /// Gets or sets the auth flow of the wizard page in this data source. + /// + public bool AuthFlow { get; set; } + + } + +} diff --git a/API/Resources/DataSourceWizardPageResourceResult.cs b/API/Resources/DataSourceWizardPageResourceResult.cs new file mode 100644 index 00000000..286a7511 --- /dev/null +++ b/API/Resources/DataSourceWizardPageResourceResult.cs @@ -0,0 +1,39 @@ +/* +* 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 +*/ + +namespace API.Resources +{ + + /// + /// The view model of a wizard page from a data source. + /// + public class DataSourceWizardPageResourceResult : WizardPageResourceResult + { + + /// + /// Gets or sets a value for the AuthFlow property. + /// + public bool AuthFlow { get; set; } + + /// + /// Gets or sets a value for the OrderIndex property. + /// + public int OrderIndex { get; set; } + + } + +} diff --git a/API/Resources/EmbeddedProjectResource.cs b/API/Resources/EmbeddedProjectResource.cs index e0e0d923..173c0a15 100644 --- a/API/Resources/EmbeddedProjectResource.cs +++ b/API/Resources/EmbeddedProjectResource.cs @@ -15,18 +15,20 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System; - namespace API.Resources { + /// /// The view model of a highlight /// public class EmbeddedProjectResource { + /// /// This gets or sets the id of the project that this highlight is associated with /// public int ProjectId { get; set; } + } + } diff --git a/API/Resources/EmbeddedProjectResourceResult.cs b/API/Resources/EmbeddedProjectResourceResult.cs index 2dbc5b1d..c470471d 100644 --- a/API/Resources/EmbeddedProjectResourceResult.cs +++ b/API/Resources/EmbeddedProjectResourceResult.cs @@ -15,48 +15,48 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System; using Models; +using System; namespace API.Resources { /// - /// Embedded project resource result + /// Embedded project resource result /// /// - public class EmbeddedProjectResourceResult: EmbeddedProjectResource + public class EmbeddedProjectResourceResult : EmbeddedProjectResource { /// - /// Gets or sets the identifier. + /// Gets or sets the identifier. /// /// - /// The identifier. + /// The identifier. /// public int Id { get; set; } /// - /// Gets or sets the project. + /// Gets or sets the project. /// /// - /// The project. + /// The project. /// public Project Project { get; set; } /// - /// Gets or sets the unique identifier. + /// Gets or sets the unique identifier. /// /// - /// The unique identifier. + /// The unique identifier. /// public Guid Guid { get; set; } /// - /// Gets or sets the user. + /// Gets or sets the user. /// /// - /// The user. + /// The user. /// public User User { get; set; } diff --git a/API/Resources/FileResource.cs b/API/Resources/FileResource.cs index cb4feb44..862e40a1 100644 --- a/API/Resources/FileResource.cs +++ b/API/Resources/FileResource.cs @@ -14,8 +14,6 @@ * 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 API.Extensions; using Microsoft.AspNetCore.Http; using System.ComponentModel.DataAnnotations; @@ -23,18 +21,18 @@ namespace API.Resources { /// - /// Embedded project resource result + /// Embedded project resource result /// /// public class FileResource { + /// - /// IFormFile + /// IFormFile /// [Required(ErrorMessage = "Please add a file")] - [AllowedExtensions(new [] { ".jpg", ".png", ".jpeg"})] - [MaxFileSize(2097152)] public IFormFile File { get; set; } - + } + } diff --git a/API/Resources/FileResourceResult.cs b/API/Resources/FileResourceResult.cs index 71b9e435..260e2571 100644 --- a/API/Resources/FileResourceResult.cs +++ b/API/Resources/FileResourceResult.cs @@ -19,32 +19,39 @@ namespace API.Resources { + /// - /// File resource resource result + /// File resource resource result /// /// public class FileResourceResult { + /// - /// Id of File + /// Id of File /// public int Id { get; set; } + /// - /// Path of file + /// Path of file /// public string Path { get; set; } + /// - /// Upload Date and time + /// Upload Date and time /// public DateTime UploadDateTime { get; set; } + /// - /// File name + /// File name /// public string Name { get; set; } + /// - /// User Id that uploaded the file + /// User Id that uploaded the file /// public int UploaderUserId { get; set; } } + } diff --git a/API/Resources/HighlightResource.cs b/API/Resources/HighlightResource.cs index ea9ed438..99fcee71 100644 --- a/API/Resources/HighlightResource.cs +++ b/API/Resources/HighlightResource.cs @@ -19,11 +19,13 @@ namespace API.Resources { + /// /// The view model of a highlight /// public class HighlightResource { + /// /// This gets or sets the id of the project that this highlight is associated with /// @@ -34,7 +36,6 @@ public class HighlightResource /// public string Description { get; set; } - /// /// This gets or sets the start date that the highlight should start /// @@ -44,5 +45,7 @@ public class HighlightResource /// This gets or sets the end date that highlight should end /// public DateTime? EndDate { get; set; } + } + } diff --git a/API/Resources/HighlightResourceResult.cs b/API/Resources/HighlightResourceResult.cs index dec9d7cf..084bed53 100644 --- a/API/Resources/HighlightResourceResult.cs +++ b/API/Resources/HighlightResourceResult.cs @@ -15,7 +15,6 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using Models; using System; namespace API.Resources @@ -31,14 +30,17 @@ public class HighlightResourceResult /// This gets or sets the the id of highlight /// public int Id { get; set; } + /// /// This gets or sets the id of the project that this highlight is associated with /// public int ProjectId { get; set; } + /// /// This gets or sets the description of the project that this highlight is associated with /// public string Description { get; set; } + /// /// This gets or sets the project of this highlight /// @@ -53,6 +55,7 @@ public class HighlightResourceResult /// This gets or sets the end date that highlight should end /// public DateTime? EndDate { get; set; } + } } diff --git a/API/Resources/InstitutionResource.cs b/API/Resources/InstitutionResource.cs index 5a452532..bf31b0f6 100644 --- a/API/Resources/InstitutionResource.cs +++ b/API/Resources/InstitutionResource.cs @@ -19,25 +19,26 @@ namespace API.Resources { /// - /// The viewmodel of a Institution model + /// The viewmodel of a Institution model /// public class InstitutionResource { - + /// - /// Gets or sets a value for the name of the institution. + /// Gets or sets a value for the name of the institution. /// public string Name { get; set; } /// - /// Gets or sets a value for the description of the institution. + /// Gets or sets a value for the description of the institution. /// public string Description { get; set; } /// - /// Gets or sets a value for the identity id for the institution. + /// Gets or sets a value for the identity id for the institution. /// public string IdentityId { get; set; } + } } diff --git a/API/Resources/InstitutionResourceResult.cs b/API/Resources/InstitutionResourceResult.cs index d94eee5a..9d8599a8 100644 --- a/API/Resources/InstitutionResourceResult.cs +++ b/API/Resources/InstitutionResourceResult.cs @@ -19,13 +19,13 @@ namespace API.Resources { /// - /// Resource Result of the Institution model + /// Resource Result of the Institution model /// public class InstitutionResourceResult : InstitutionResource { /// - /// Gets or sets a the id of the institution. + /// Gets or sets a the id of the institution. /// public int Id { get; set; } diff --git a/API/Resources/LimitedUserResourceResult.cs b/API/Resources/LimitedUserResourceResult.cs index c9da8b44..29640c60 100644 --- a/API/Resources/LimitedUserResourceResult.cs +++ b/API/Resources/LimitedUserResourceResult.cs @@ -15,10 +15,9 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using Models; - namespace API.Resources { + /// /// the view model result of user. /// diff --git a/API/Resources/OauthTokensResourceResult.cs b/API/Resources/OauthTokensResourceResult.cs new file mode 100644 index 00000000..f556516a --- /dev/null +++ b/API/Resources/OauthTokensResourceResult.cs @@ -0,0 +1,49 @@ +/* +* 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 +*/ + +namespace API.Resources +{ + + /// + /// The resource result of the Oauth tokens. + /// + public class OauthTokensResourceResult + { + + /// + /// Gets or sets the access token property. + /// + public string AccessToken { get; set; } + + /// + /// Gets or sets the token type property. + /// + public string TokenType { get; set; } + + /// + /// Gets or sets the expire in property. + /// + public string ExpiresIn { get; set; } + + /// + /// Gets or sets the refresh token property + /// + public string RefreshToken { get; set; } + + } + +} diff --git a/API/Resources/ProjectFilterParamsResource.cs b/API/Resources/ProjectFilterParamsResource.cs index 31c5b753..451d3abc 100644 --- a/API/Resources/ProjectFilterParamsResource.cs +++ b/API/Resources/ProjectFilterParamsResource.cs @@ -21,7 +21,7 @@ namespace API.Resources { /// - /// This resource contains all the query parameters used to filter, sort and paginate projects + /// This resource contains all the query parameters used to filter, sort and paginate projects /// public class ProjectFilterParamsResource { @@ -53,11 +53,11 @@ public class ProjectFilterParamsResource public string SortDirection { get; set; } /// - /// This property filter the projects on the highlighted state - /// Possible value: - /// - null (Return all results) - /// - true (Only return highlighted results) - /// - false (Only return not highlighted results) + /// This property filter the projects on the highlighted state + /// Possible value: + /// - null (Return all results) + /// - true (Only return highlighted results) + /// - false (Only return not highlighted results) /// [FromQuery(Name = "highlighted")] public bool? Highlighted { get; set; } diff --git a/API/Resources/ProjectHighlightResourceResult.cs b/API/Resources/ProjectHighlightResourceResult.cs index b24dc21c..b899e874 100644 --- a/API/Resources/ProjectHighlightResourceResult.cs +++ b/API/Resources/ProjectHighlightResourceResult.cs @@ -1,13 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 +*/ namespace API.Resources { + /// /// The view model result for a project that is returned when requesting highlights - /// Only information that the highlight needs is returned + /// Only information that the highlight needs is returned /// public class ProjectHighlightResourceResult { @@ -23,8 +36,10 @@ public class ProjectHighlightResourceResult public string ShortDescription { get; set; } /// - /// This gets or set the file of the project + /// This gets or set the file of the project /// public FileResourceResult ProjectIcon { get; set; } + } + } diff --git a/API/Resources/ProjectInstitutionResourceResult.cs b/API/Resources/ProjectInstitutionResourceResult.cs new file mode 100644 index 00000000..9f02a63a --- /dev/null +++ b/API/Resources/ProjectInstitutionResourceResult.cs @@ -0,0 +1,42 @@ +/* +* 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 +*/ + +namespace API.Resources +{ + /// + /// The viewmodel returned when a project and institution are linked + /// + public class ProjectInstitutionResourceResult + { + /// + /// This gets or sets the InstitutionId + /// + public int InstitutionId { get; set; } + /// + /// This gets or sets the InstititutionName + /// + public string InstititutionName { get; set; } + /// + /// This gets or sets the ProjectId + /// + public int ProjectId { get; set; } + /// + /// This gets or sets the ProjectName + /// + public string ProjectName { get; set; } + } +} diff --git a/API/Resources/ProjectLikesResourceResult.cs b/API/Resources/ProjectLikesResourceResult.cs index 3c2d0ab4..3cf1cab5 100644 --- a/API/Resources/ProjectLikesResourceResult.cs +++ b/API/Resources/ProjectLikesResourceResult.cs @@ -1,22 +1,44 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + using System; namespace API.Resources { + /// /// The view model result of UserProjectLike /// public class ProjectLikesResourceResult { + /// /// Gets or sets the id of the user who liked the project. /// /// - /// The User identifier + /// The User identifier /// public int UserId { get; set; } + /// - /// Gets or sets the date of when the user has liked the project. + /// Gets or sets the date of when the user has liked the project. /// - public DateTime Date { get; set; } + public DateTime Date { get; set; } + } + } diff --git a/API/Resources/ProjectResource.cs b/API/Resources/ProjectResource.cs index 8f97e574..3af6d3ff 100644 --- a/API/Resources/ProjectResource.cs +++ b/API/Resources/ProjectResource.cs @@ -15,16 +15,17 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System; using System.Collections.Generic; namespace API.Resources { + /// /// The view model of a project /// public class ProjectResource { + /// /// This gets or sets the Title /// @@ -51,18 +52,20 @@ public class ProjectResource public ICollection Collaborators { get; set; } /// - /// This gets or sets the file id + /// This gets or sets the file id /// public int FileId { get; set; } /// - /// This gets or sets the call to action + /// This gets or sets the call to action /// public CallToActionResource CallToAction { get; set; } /// - /// Sets or gets if project is visible to institute members only or not + /// This gets or sets the institute private property /// public bool InstitutePrivate { get; set; } + } + } diff --git a/API/Resources/ProjectResourceResult.cs b/API/Resources/ProjectResourceResult.cs index 7c23b564..4d8768fd 100644 --- a/API/Resources/ProjectResourceResult.cs +++ b/API/Resources/ProjectResourceResult.cs @@ -15,19 +15,20 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using Models; using System; using System.Collections.Generic; namespace API.Resources { + /// /// The view model result of project. /// public class ProjectResourceResult { + /// - /// Get or Set Id of a Project Resource Result. + /// Get or Set Id of a Project Resource Result /// public int Id { get; set; } @@ -66,6 +67,11 @@ public class ProjectResourceResult /// public ICollection Collaborators { get; set; } + /// + /// This gets or sets the linkedInstitutions + /// + public ICollection LinkedInstitutions { get; set; } + /// /// This gets or sets the Created time of the project. /// @@ -75,22 +81,27 @@ public class ProjectResourceResult /// This gets or sets the Updated time of the project. /// public DateTime Updated { get; set; } + /// - /// This gets or set the file of the project. + /// This gets or set the file of the project. /// public FileResourceResult ProjectIcon { get; set; } + /// - /// This gets or sets the call to action. + /// This gets or sets the call to action. /// public CallToActionResourceResult CallToAction { get; set; } + /// - /// This gets or sets the likes of the project. + /// This gets or sets the likes of the project. /// public List Likes { get; set; } /// - /// Sets or gets if project is visible to institute members only or not + /// Sets or gets if project is visible to institute members only or not /// public bool InstitutePrivate { get; set; } + } + } diff --git a/API/Resources/ProjectResultResource.cs b/API/Resources/ProjectResultResource.cs index 6c78f010..b91b6212 100644 --- a/API/Resources/ProjectResultResource.cs +++ b/API/Resources/ProjectResultResource.cs @@ -15,7 +15,6 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using Models; using System; using System.Collections.Generic; @@ -62,18 +61,22 @@ public class ProjectResultResource /// Get or Set the owner of the project /// public LimitedUserResourceResult User { get; set; } + /// - /// This gets or set the file of the project + /// This gets or set the file of the project /// public FileResourceResult ProjectIcon { get; set; } + /// - /// This gets or sets the call to action of the project. + /// This gets or sets the call to action of the project. /// public CallToActionResourceResult CallToAction { get; set; } + /// - /// This gets or sets the likes of the project. + /// This gets or sets the likes of the project. /// public List Likes { get; set; } + } } diff --git a/API/Resources/RoleResource.cs b/API/Resources/RoleResource.cs index 2a67644a..3bd34870 100644 --- a/API/Resources/RoleResource.cs +++ b/API/Resources/RoleResource.cs @@ -15,30 +15,33 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System; using System.Collections.Generic; -using Models; namespace API.Resources { + /// /// The view model of a role /// public class RoleResource { + /// - /// Gets or sets the name. + /// Gets or sets the name. /// /// - /// The name. + /// The name. /// public string Name { get; set; } + /// - /// Gets or sets the scopes. + /// Gets or sets the scopes. /// /// - /// The scopes. + /// The scopes. /// public List Scopes { get; set; } + } + } diff --git a/API/Resources/RoleResourceResult.cs b/API/Resources/RoleResourceResult.cs index 2b566717..1d87c4c5 100644 --- a/API/Resources/RoleResourceResult.cs +++ b/API/Resources/RoleResourceResult.cs @@ -15,19 +15,20 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System; -using System.Collections.Generic; - namespace API.Resources { + /// /// The view model result of role /// public class RoleResourceResult : RoleResource { + /// /// Get or Set the Id of a Role /// public int Id { get; set; } + } + } diff --git a/API/Resources/RoleScopeResource.cs b/API/Resources/RoleScopeResource.cs index 35c6cfbf..c1e56430 100644 --- a/API/Resources/RoleScopeResource.cs +++ b/API/Resources/RoleScopeResource.cs @@ -1,21 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 +*/ namespace API.Resources { + /// - /// RoleScopeResource + /// RoleScopeResource /// public class RoleScopeResource { + /// - /// Gets or sets the scope. + /// Gets or sets the scope. /// /// - /// The scope. + /// The scope. /// public string Scope { get; set; } + } + } diff --git a/API/Resources/UserProjectLikeResourceResult.cs b/API/Resources/UserProjectLikeResourceResult.cs index 1103fb3f..f1cae7a3 100644 --- a/API/Resources/UserProjectLikeResourceResult.cs +++ b/API/Resources/UserProjectLikeResourceResult.cs @@ -1,4 +1,19 @@ -using Models; +/* +* 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 +*/ namespace API.Resources { @@ -10,6 +25,7 @@ namespace API.Resources /// public class UserProjectLikeResourceResult { + /// /// Gets or sets the id of the project that being liked. /// @@ -38,7 +54,7 @@ public class UserProjectLikeResourceResult /// Gets or sets the uri of project that being liked. /// /// - /// The string representation of the Project's Uri + /// The string representation of the Project's Uri /// public string Uri { get; set; } @@ -51,4 +67,5 @@ public class UserProjectLikeResourceResult public string Description { get; set; } } + } diff --git a/API/Resources/UserProjectResourceResult.cs b/API/Resources/UserProjectResourceResult.cs index 17659fbd..9e706c71 100644 --- a/API/Resources/UserProjectResourceResult.cs +++ b/API/Resources/UserProjectResourceResult.cs @@ -1,36 +1,54 @@ -using Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 +*/ namespace API.Resources { + /// - /// Object to return to frontend with the UserProject + /// Object to return to frontend with the UserProject /// public class UserProjectResourceResult { + /// - /// gets or sets Id of the followed project + /// gets or sets Id of the followed project /// public int Id { get; set; } + /// - /// Set or get Project + /// Set or get Project /// - public string Name{ get; set; } + public string Name { get; set; } + /// - /// set or get User + /// set or get User /// public string ShortDescription { get; set; } + /// - /// set or get userId + /// set or get userId /// - public string Description{ get; set; } + public string Description { get; set; } /// - /// Uri project + /// Uri project /// - public string Uri { get; set; } + public string Uri { get; set; } + } + } diff --git a/API/Resources/UserResource.cs b/API/Resources/UserResource.cs index 95b8ddc0..3c868fab 100644 --- a/API/Resources/UserResource.cs +++ b/API/Resources/UserResource.cs @@ -15,6 +15,8 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ +using System; + namespace API.Resources { @@ -45,10 +47,15 @@ public class UserResource public string ProfileUrl { get; set; } /// - /// This gets or sets the Institution Id. + /// This gets or sets the Institution Id. /// public int? InstitutionId { get; set; } + /// + /// This gets or sets the Expected Graduation DateTime. + /// + public DateTime? ExpectedGraduationDateTime { get; set; } + } } diff --git a/API/Resources/UserResourceResult.cs b/API/Resources/UserResourceResult.cs index 715087cc..ac363f46 100644 --- a/API/Resources/UserResourceResult.cs +++ b/API/Resources/UserResourceResult.cs @@ -16,6 +16,7 @@ */ using Models; +using System; using System.Collections.Generic; namespace API.Resources @@ -53,27 +54,38 @@ public class UserResourceResult public string ProfileUrl { get; set; } /// - /// Gets or sets the role. + /// Gets or sets the role. /// /// - /// The role. + /// The role. /// public Role Role { get; set; } /// - /// Gets or sets the institution where the user is registered. + /// Gets or sets the institution where the user is registered. /// public Institution Institution { get; set; } - + /// - /// Gets or sets the followed projects for user + /// Gets or sets the followed projects for user /// public List UserProject { get; set; } - + /// - /// Gets or sets the liked projects by registered users. + /// Gets or sets the liked projects by registered users. /// public List LikedProjectsByUsers { get; set; } + + /// + /// Gets or set the tasks the user should follow up. + /// + public List UserTask { get; set; } + + /// + /// This gets or sets the Expected Graduation DateTime. + /// + public DateTime? ExpectedGraduationDateTime { get; set; } + } } diff --git a/API/Resources/UserTaskResourceResult.cs b/API/Resources/UserTaskResourceResult.cs new file mode 100644 index 00000000..a5d1439c --- /dev/null +++ b/API/Resources/UserTaskResourceResult.cs @@ -0,0 +1,32 @@ +namespace API.Resources +{ + + /// + /// Object used to return to the frontend that shows a user task + /// + public class UserTaskResourceResult + { + + /// + /// Gets or sets the id of the user task + /// + public int Id { get; set; } + + /// + /// Get or sets the user + /// + public UserResourceResult UserResourceResult { get; set; } + + /// + /// Gets or sets the status of the user task + /// + public string Status { get; set; } + + /// + /// Gets or sets the type of the user task + /// + public string Type { get; set; } + + } + +} diff --git a/API/Resources/UserUserResourceResult.cs b/API/Resources/UserUserResourceResult.cs index 36754aea..95caeb48 100644 --- a/API/Resources/UserUserResourceResult.cs +++ b/API/Resources/UserUserResourceResult.cs @@ -1,24 +1,39 @@ -using Models; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 +*/ namespace API.Resources { + /// - /// Object to return to frontend with the UserUser + /// Object to return to frontend with the UserUser /// public class UserUserResourceResult { + /// - /// Set or gets id + /// Set or gets id /// public int Id { get; set; } /// - /// User object to follow + /// User object to follow /// public string Name { get; set; } + } + } diff --git a/API/Resources/WizardPageResource.cs b/API/Resources/WizardPageResource.cs new file mode 100644 index 00000000..376ba2a0 --- /dev/null +++ b/API/Resources/WizardPageResource.cs @@ -0,0 +1,39 @@ +/* +* 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 +*/ + +namespace API.Resources +{ + + /// + /// The view model of a wizard page. + /// + public class WizardPageResource + { + + /// + /// Gets or sets a value for the Name property. + /// + public string Name { get; set; } + + /// + /// Gets or sets a value for the Description property. + /// + public string Description { get; set; } + + } + +} diff --git a/API/Resources/WizardPageResourceResult.cs b/API/Resources/WizardPageResourceResult.cs new file mode 100644 index 00000000..03ee1282 --- /dev/null +++ b/API/Resources/WizardPageResourceResult.cs @@ -0,0 +1,34 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +namespace API.Resources +{ + + /// + /// The view model of a wizard page result. + /// + public class WizardPageResourceResult : WizardPageResource + { + + /// + /// Gets or sets a value for the Id property. + /// + public int Id { get; set; } + + } + +} diff --git a/API/Resources/WizardProjectResourceResult.cs b/API/Resources/WizardProjectResourceResult.cs new file mode 100644 index 00000000..ef57097e --- /dev/null +++ b/API/Resources/WizardProjectResourceResult.cs @@ -0,0 +1,66 @@ +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + +using System.Collections.Generic; + +namespace API.Resources +{ + + /// + /// The view model of the project resource results from the wizard + /// + public class WizardProjectResourceResult + { + + /// + /// Get or Set Id of a Project Resource Result + /// + public int Id { get; set; } + + /// + /// This gets or sets the Name + /// + public string Name { get; set; } + + /// + /// This gets or sets the Description + /// + public string Description { get; set; } + + /// + /// This gets or sets the Short Description + /// + public string ShortDescription { get; set; } + + /// + /// This gets or sets the Uri + /// + public string Uri { get; set; } + + /// + /// This gets or sets the collaborators + /// + public ICollection Collaborators { get; set; } + + /// + /// This gets or set the file of the project + /// + public FileResourceResult ProjectIcon { get; set; } + + } + +} diff --git a/API/Startup.cs b/API/Startup.cs index d6968942..d43843cc 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -18,11 +18,13 @@ using API.Configuration; using API.Extensions; using API.Filters; +using API.HelperClasses; using API.InternalResources; using Data; using Data.Helpers; using FluentValidation.AspNetCore; using Hellang.Middleware.ProblemDetails; +using IdentityModel.Client; using MessageBrokerPublisher.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; @@ -36,6 +38,8 @@ using Microsoft.OpenApi.Models; using Models; using Models.Defaults; +using Newtonsoft.Json; +using Polly; using Serilog; using Services.Services; using Swashbuckle.AspNetCore.SwaggerUI; @@ -56,7 +60,7 @@ public class Startup { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. /// The environment. @@ -79,7 +83,7 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment) public IWebHostEnvironment Environment { get; } /// - /// Configures the services. + /// Configures the services. /// /// The services. public void ConfigureServices(IServiceCollection services) @@ -90,7 +94,10 @@ public void ConfigureServices(IServiceCollection services) o.UseSqlServer(Config.OriginalConfiguration.GetConnectionString("DefaultConnection"), sqlOptions => sqlOptions.EnableRetryOnFailure(50, TimeSpan.FromSeconds(30), null)); }); - services.AddScoped(c => new RabbitMQConnectionFactory(Config.RabbitMQ.Hostname, Config.RabbitMQ.Username, Config.RabbitMQ.Password)); + services.AddScoped( + c => new RabbitMQConnectionFactory(Config.RabbitMQ.Hostname, + Config.RabbitMQ.Username, + Config.RabbitMQ.Password)); services.AddAutoMapper(); services.UseConfigurationValidation(); @@ -108,8 +115,12 @@ public void ConfigureServices(IServiceCollection services) options.ApiSecret = Config.Frontend.ClientSecret; options.EnableCaching = true; }); + services.AddAuthorization(o => { + o.AddPolicy(nameof(Defaults.Roles.BackendApplication), + policy => policy.RequireClaim("client_role", nameof(Defaults.Roles.BackendApplication))); + o.AddPolicy(nameof(Defaults.Scopes.HighlightRead), policy => policy.Requirements.Add( new ScopeRequirement(nameof(Defaults.Scopes.HighlightRead)))); @@ -120,6 +131,9 @@ public void ConfigureServices(IServiceCollection services) o.AddPolicy(nameof(Defaults.Scopes.ProjectRead), policy => policy.Requirements.Add( new ScopeRequirement(nameof(Defaults.Scopes.ProjectRead)))); + o.AddPolicy(nameof(Defaults.Scopes.AdminProjectWrite), + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.AdminProjectWrite)))); o.AddPolicy(nameof(Defaults.Scopes.ProjectWrite), policy => policy.Requirements.Add( new ScopeRequirement(nameof(Defaults.Scopes.ProjectWrite)))); @@ -137,35 +151,50 @@ public void ConfigureServices(IServiceCollection services) o.AddPolicy(nameof(Defaults.Scopes.EmbedRead), policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.EmbedRead)))); o.AddPolicy(nameof(Defaults.Scopes.EmbedWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.EmbedWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.EmbedWrite)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionEmbedWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionEmbedWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionEmbedWrite)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionProjectWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionProjectWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionProjectWrite)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionUserRead), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionUserRead)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionUserRead)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionUserWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionUserWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionUserWrite)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionWrite)))); o.AddPolicy(nameof(Defaults.Scopes.InstitutionRead), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.InstitutionRead)))); - + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.InstitutionRead)))); + + o.AddPolicy(nameof(Defaults.Scopes.DataSourceWrite), + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.DataSourceWrite)))); + o.AddPolicy(nameof(Defaults.Scopes.FileWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.FileWrite)))); + policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.FileWrite)))); o.AddPolicy(nameof(Defaults.Scopes.CallToActionOptionWrite), - policy => policy.Requirements.Add(new ScopeRequirement(nameof(Defaults.Scopes.CallToActionOptionWrite)))); + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.CallToActionOptionWrite)))); + + o.AddPolicy(nameof(Defaults.Scopes.UserTaskWrite), + policy => policy.Requirements.Add( + new ScopeRequirement(nameof(Defaults.Scopes.UserTaskWrite)))); }); services.AddCors(); services.AddControllersWithViews() .AddFluentValidation(c => c.RegisterValidatorsFromAssemblyContaining()) .AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = - Newtonsoft.Json.ReferenceLoopHandling.Ignore) - ; + ReferenceLoopHandling.Ignore); services.AddSwaggerGen(o => { @@ -196,7 +225,7 @@ public void ConfigureServices(IServiceCollection services) AuthorizationUrl = GetAuthorizationUrl(), Scopes = new Dictionary { - {"dex-api", "Resource scope"}, + {"dex-api", "Resource scope"} } } } @@ -217,6 +246,29 @@ public void ConfigureServices(IServiceCollection services) }); }); + services.AddAccessTokenManagement(options => + { + options.Client.Clients.Add("identityserver", + new ClientCredentialsTokenRequest + { + Address = Config.IdentityServer.IdentityUrl + "/connect/token", + ClientId = Config.IdentityServer.ClientId, + ClientSecret = Config.IdentityServer.ClientSecret + }); + }) + .ConfigureBackchannelHttpClient() + .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + })); + services.AddClientAccessTokenClient("identityclient", + configureClient: client => + { + client.BaseAddress = + new Uri(string.Concat(Config.IdentityServer.IdentityUrl + "/")); + }); + // Add application services. services.AddSingleton(Config); services.AddServicesAndRepositories(); @@ -232,8 +284,8 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { env.WebRootPath = Path.Combine(Directory.GetCurrentDirectory(), "Uploads"); - Defaults.Path.filePath = Path.Combine(env.WebRootPath, "Images"); - + Defaults.Path.FilePath = Path.Combine(env.WebRootPath, "Images"); + UpdateDatabase(app, env); if(env.IsDevelopment()) @@ -261,12 +313,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseProblemDetails(); app.UseStaticFiles(); - app.UseStaticFiles(new StaticFileOptions() + app.UseStaticFiles(new StaticFileOptions { - FileProvider = new PhysicalFileProvider( - Path.Combine(env.ContentRootPath, "Uploads", "Images")), - RequestPath = "/Uploads/Images" - }); + FileProvider = new PhysicalFileProvider( + Path.Combine(env.ContentRootPath, "Uploads", "Images")), + RequestPath = "/Uploads/Images" + }); app.UseRouting(); app.UseCors(c => @@ -315,13 +367,13 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // Then it probably belongs swagger so we set the username as developer. User newUser = new User - { - Name = "Developer", - Email = "Developer@DEX.com", - IdentityId = identityId, - Role = registeredUserRole, - InstitutionId = 1 - }; + { + Name = "Developer", + Email = "Developer@DEX.com", + IdentityId = identityId, + Role = registeredUserRole, + InstitutionId = 1 + }; userService.Add(newUser); } else { @@ -330,12 +382,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) Name = userInformation.Name, Email = userInformation.Email, IdentityId = userInformation.IdentityId, - Role = registeredUserRole, + Role = registeredUserRole }; Institution institution = await institutionService.GetInstitutionByInstitutionIdentityId( userInformation.IdentityInstitutionId); - if( institution != null) + if(institution != null) { newUser.InstitutionId = institution.Id; } @@ -350,11 +402,11 @@ await dbContext.SaveChangesAsync() { if(userInformation != null) { - Institution institution = await institutionService.GetInstitutionByInstitutionIdentityId( - userInformation.IdentityInstitutionId); - if(institution != null) - user.InstitutionId = institution.Id; - + Institution institution = + await institutionService.GetInstitutionByInstitutionIdentityId( + userInformation.IdentityInstitutionId); + if(institution != null) user.InstitutionId = institution.Id; + userService.Update(user); await dbContext.SaveChangesAsync() .ConfigureAwait(false); @@ -379,7 +431,7 @@ await next() } /// - /// Updates the database. + /// Updates the database. /// /// The application. /// The env. @@ -390,23 +442,24 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment .CreateScope(); using ApplicationDbContext context = serviceScope.ServiceProvider.GetService(); context.Database.Migrate(); - if(!context.Role.Any()) - { - // seed roles - context.AddRange(Seed.SeedRoles()); - context.SaveChanges(); - } + + // Check if Roles and RoleScopes in DB matches seed, if it doesn't match: database is updated. + SeedHelper.InsertRoles(Seed.SeedRoles(), context); List roles = context.Role.ToList(); - if(!context.User.Any()) - { - // seed admin - context.User.Add(Seed.SeedAdminUser(roles)); - context.SaveChanges(); - if(!env.IsProduction()) + if(!env.IsProduction()) + { + if(!context.Institution.Any()) { // Seed institutions - context.Institution.Add(Seed.SeedInstitution()); + context.Institution.AddRange(Seed.SeedInstitution()); + context.SaveChanges(); + } + + if(!context.User.Any()) + { + // seed admin + context.User.Add(Seed.SeedAdminUser(roles)); context.SaveChanges(); //Seed random users @@ -415,10 +468,7 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment context.User.Add(Seed.SeedDataOfficerUser(roles)); context.SaveChanges(); } - } - if(!env.IsProduction()) - { if(!context.Project.Any()) { //Seed projects @@ -440,13 +490,16 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment context.SaveChanges(); } + // TODO seed embedded projects } + // Seed call to action options List options = Seed.SeedCallToActionOptions(); foreach(CallToActionOption callToActionOption in options) { - if(!context.CallToActionOption.Any(s => s.Type == callToActionOption.Type && s.Value == callToActionOption.Value)) + if(!context.CallToActionOption.Any(s => s.Type == callToActionOption.Type && + s.Value == callToActionOption.Value)) { context.CallToActionOption.Add(callToActionOption); context.SaveChanges(); diff --git a/API/appsettingsapi.Development.json b/API/appsettingsapi.Development.json index 6796b29e..2917cae3 100644 --- a/API/appsettingsapi.Development.json +++ b/API/appsettingsapi.Development.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(LocalDb)\\MSSQLLocalDB;Database=Dex;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": + "Server=(LocalDb)\\MSSQLLocalDB;Database=Dex;Trusted_Connection=True;MultipleActiveResultSets=true" }, "App": { "Frontend": { @@ -10,16 +11,30 @@ }, "IdentityServer": { "IdentityUrl": "https://localhost:5005", - "DevelopmentIdentityUrl": "https://localhost:5005" + "DevelopmentIdentityUrl": "https://localhost:5005", + "ClientId": "dex-api", + "ClientSecret": "kP+O2lU<.5Avx@MMe4b||_^l" }, "Swagger": { "ClientId": "Swagger-UI" }, + "DataSources": { + "Github": { + "ClientId": "", + "ClientSecret": "", + "RedirectUri": "https://localhost:5001/api/wizard/oauth/callback/de38e528-1d6d-40e7-83b9-4334c51c19be" + }, + "Gitlab": { + "ClientId": "", + "ClientSecret": "", + "RedirectUri": "https://localhost:5001/api/wizard/oauth/callback/66de59d4-5db0-4bf8-a9a5-06abe8d3443a" + } + }, "RabbitMQ": { "Hostname": "localhost", "Username": "guest", "Password": "guest" - } + } }, "Logging": { "LogLevel": { diff --git a/API/appsettingsapi.json b/API/appsettingsapi.json index f19fdc0f..af29dc99 100644 --- a/API/appsettingsapi.json +++ b/API/appsettingsapi.json @@ -1,5 +1,5 @@ { - "UpdateInstitutionOnLogin": true, + "UpdateInstitutionOnLogin": true, "ConnectionStrings": { "DefaultConnection": "" }, @@ -10,12 +10,26 @@ "ClientSecret": "" }, "IdentityServer": { - "IdentityUrl": "" + "IdentityUrl": "", + "ClientId": "", + "ClientSecret": "" }, "Swagger": { "ClientId": "Swagger-UI" }, + "DataSources": { + "Github": { + "ClientId": "", + "ClientSecret": "", + "RedirectUri": "" + }, + "Gitlab": { + "ClientId": "", + "ClientSecret": "", + "RedirectUri": "" + } + }, "RabbitMQ": { "Hostname": "localhost", "Username": "guest", diff --git a/CHANGELOG.md b/CHANGELOG.md index 691f2e7b..3a4d41ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] - - ### Added @@ -28,6 +25,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security +## Release v.1.1.0-beta - 18-03-2021 + +### Added + +- Graduation system for users whose FHICT account becomes unavailable. - [#287](https://github.com/DigitalExcellence/dex-backend/issues/287) +- Added wizard to automatically import projects with a public and an oauth flow. - [#326](https://github.com/DigitalExcellence/dex-backend/issues/326) +- Added feature to link multiple institutions to a project. - [#281](https://github.com/DigitalExcellence/dex-backend/issues/281) +- Added action annotations for max allowed file size and allowed extensions. - [#272](https://github.com/DigitalExcellence/dex-backend/issues/272) + +### Fixed +- Fixed inefficient use of entity framework for the fetching-all-projects endpoint. - [#376](https://github.com/DigitalExcellence/dex-backend/issues/376) +- An issue where the JobScheduler did not start in staging/production. - [#382](https://github.com/DigitalExcellence/dex-backend/issues/382) +- Fixed an issue where getting all projects was very slow. - [#394](https://github.com/DigitalExcellence/dex-backend/issues/394) + + ## Release v.1.0.1-beta - 31-01-2021 @@ -35,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added ability to make projects private to a specific institution - [#310](https://github.com/DigitalExcellence/dex-backend/issues/310) +- Added ability to retrieve all the user's projects - [#313](https://github.com/DigitalExcellence/dex-backend/issues/313) ### Changed diff --git a/Data/06_Data.csproj b/Data/06_Data.csproj new file mode 100644 index 00000000..d5872299 --- /dev/null +++ b/Data/06_Data.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp3.1 + 8 + 1.1.0-beta + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/Data/4_Data.csproj b/Data/4_Data.csproj deleted file mode 100644 index a861dde3..00000000 --- a/Data/4_Data.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - netcoreapp3.1 - 8 - 1.0.1-beta - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 29bef28f..fbf1b7b5 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -21,8 +21,9 @@ namespace Data { + /// - /// ApplicationDatabaseContext + /// ApplicationDatabaseContext /// /// public class ApplicationDbContext : DbContext @@ -31,115 +32,155 @@ public class ApplicationDbContext : DbContext public ApplicationDbContext(DbContextOptions options) : base(options) { } /// - /// Gets or sets the user. + /// Gets or sets the user. /// /// - /// The user. + /// The user. /// public DbSet User { get; set; } - + /// - /// Gets or sets the Call to Action. + /// Gets or sets the Call to Action. /// /// - /// The call to action. + /// The call to action. /// public DbSet CallToAction { get; set; } - + /// - /// Gets or sets the project. + /// Gets or sets the project. /// /// - /// The project. + /// The project. /// public DbSet File { get; set; } /// - /// Gets or sets the Call To Action options. + /// Gets or sets the Call To Action options. /// /// - /// The call to action options. + /// The call to action options. /// public DbSet Project { get; set; } - + /// - /// Gets or sets the collaborators. + /// Gets or sets the collaborators. /// /// - /// The collaborators. + /// The collaborators. /// public DbSet Collaborators { get; set; } - + /// - /// Gets or sets the highlight. + /// Gets or sets the highlight. /// /// - /// The highlight. + /// The highlight. /// public DbSet Highlight { get; set; } - + /// - /// Gets or sets the embedded project. + /// Gets or sets the embedded project. /// /// - /// The embedded project. + /// The embedded project. /// public DbSet EmbeddedProject { get; set; } - + /// - /// Gets or sets the role. + /// Gets or sets the role. /// /// - /// The role. + /// The role. /// public DbSet Role { get; set; } - + /// - /// Gets or sets the institution. + /// Gets or sets the institution. /// /// - /// The institution. + /// The institution. /// public DbSet Institution { get; set; } /// - /// Gets or sets the projects liked by users. + /// Gets or sets the projects liked by users. /// /// - /// The like by the user. + /// The like by the user. /// public DbSet ProjectLike { get; set; } /// - /// Gets or sets the call to action option. + /// Gets or sets the data source. /// /// - /// The call to action option. + /// The data source. + /// + public DbSet DataSource { get; set; } + + /// + /// Gets or sets the call to action option. + /// + /// + /// The call to action option. /// public DbSet CallToActionOption { get; set; } /// - /// Gets or sets the user follwoing the project. + /// Gets or sets the user following the project. /// /// - /// The user following the project. + /// The user following the project. /// public DbSet UserProject { get; set; } /// - /// Gets or sets user following the user. + /// Gets or sets user following the user. /// /// - /// The user following the user. + /// The user following the user. /// public DbSet UserUser { get; set; } + /// + /// Gets or sets the Call to Action + /// + /// /// + /// + /// The call to action. + /// + public DbSet UserTask { get; set; } + + /// + /// Gets or sets the RoleScope + /// + /// /// + /// + /// The role scope. + /// + public DbSet RoleScope { get; set; } + + /// + /// Gets or sets the wizard page. + /// + public DbSet WizardPage { get; set; } + + /// + /// Gets or sets the DataSourceWizardPage which represents the link between a + /// data source and a wizard page. + /// + public DbSet DataSourceWizardPage { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new UserConfiguration()); modelBuilder.ApplyConfiguration(new ProjectConfiguration()); + modelBuilder.ApplyConfiguration(new DataSourceWizardPageConfiguration()); + modelBuilder.ApplyConfiguration(new ProjectInstitutionConfiguration()); } + } } diff --git a/Data/Configurations/DataSourceWizardPageConfiguration.cs b/Data/Configurations/DataSourceWizardPageConfiguration.cs new file mode 100644 index 00000000..b5d595f0 --- /dev/null +++ b/Data/Configurations/DataSourceWizardPageConfiguration.cs @@ -0,0 +1,48 @@ +/* +* 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 Microsoft.EntityFrameworkCore.Metadata.Builders; +using Models; + +namespace Data.Configurations +{ + + public class DataSourceWizardPageConfiguration : IEntityTypeConfiguration + { + + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(dw => new + { + dw.DataSourceId, + dw.WizardPageId, + dw.AuthFlow + }); + + builder.HasOne(dw => dw.DataSource) + .WithMany(d => d.DataSourceWizardPages) + .HasForeignKey(dw => dw.DataSourceId); + + builder.HasOne(dw => dw.WizardPage) + .WithMany(w => w.DataSourceWizardPages) + .HasForeignKey(dw => dw.WizardPageId); + } + + } + +} diff --git a/Data/Configurations/ProjectConfiguration.cs b/Data/Configurations/ProjectConfiguration.cs index 4f3fd7b6..78325777 100644 --- a/Data/Configurations/ProjectConfiguration.cs +++ b/Data/Configurations/ProjectConfiguration.cs @@ -1,4 +1,4 @@ -/* +/* * Digital Excellence Copyright (C) 2020 Brend Smits * * This program is free software: you can redistribute it and/or modify diff --git a/Data/Configurations/ProjectInstitutionConfiguration.cs b/Data/Configurations/ProjectInstitutionConfiguration.cs new file mode 100644 index 00000000..ac94198a --- /dev/null +++ b/Data/Configurations/ProjectInstitutionConfiguration.cs @@ -0,0 +1,31 @@ +/* +* 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 Microsoft.EntityFrameworkCore.Metadata.Builders; +using Models; + +namespace Data.Configurations +{ + public class ProjectInstitutionConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(pi => pi.Id); + } + } +} diff --git a/Data/Configurations/RoleConfiguration.cs b/Data/Configurations/RoleConfiguration.cs index 088d2eed..fc29cd06 100644 --- a/Data/Configurations/RoleConfiguration.cs +++ b/Data/Configurations/RoleConfiguration.cs @@ -24,10 +24,9 @@ namespace Data.Configurations internal class RoleConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) - { - } + public void Configure(EntityTypeBuilder builder) { } + } } diff --git a/Data/Helpers/Seed.cs b/Data/Helpers/Seed.cs index f11e1661..203a4af4 100644 --- a/Data/Helpers/Seed.cs +++ b/Data/Helpers/Seed.cs @@ -20,14 +20,56 @@ using Models.Defaults; using System; using System.Collections.Generic; +using System.Linq; namespace Data.Helpers { + /// /// Class for helpers to seed data into the database /// public static class Seed { + + /// + /// Temporary method which is meant to update the rolescopes in staging / production after user graduation feature is + /// implemented. + /// + /// List of new rolescopes. + public static List UpdateRoleScopes() + { + List roleScopes = new List(); + for(int i = 1; i < 5; i++) + { + RoleScope roleScope = new RoleScope("ProjectWrite", i) + { + RoleId = i + }; + roleScopes.Add(roleScope); + } + + RoleScope adminRoleScope = new RoleScope("AdminProjectWrite", 4); + roleScopes.Add(adminRoleScope); + + return roleScopes; + } + + /// + /// Temporary method which is meant to add the alumni role in staging / production after user graduation feature is + /// implemented. + /// + /// Alumni role. + public static Role SeedAlumniRole() + { + Role alumniRole = new Role + { + Name = Defaults.Roles.Alumni + }; + + return alumniRole; + } + + /// /// Seed random users into the database using fake date from Bogus /// @@ -49,72 +91,89 @@ public static List SeedUsers(List roles) } return users; } + /// - /// Seeds the roles. + /// Seeds the roles. /// /// The list of roles that will be seeded. public static List SeedRoles() { List roles = new List(); - Role registeredUserRole = new Role() + Role registeredUserRole = new Role { Name = nameof(Defaults.Roles.RegisteredUser), - Scopes = new List() + + Scopes = new List + { + new RoleScope(nameof(Defaults.Scopes.ProjectWrite)) + } }; roles.Add(registeredUserRole); - Role prRole = new Role() + Role prRole = new Role { Name = nameof(Defaults.Roles.PrUser), - Scopes = new List() - { - new RoleScope(nameof(Defaults.Scopes.EmbedRead)), - new RoleScope(nameof(Defaults.Scopes.EmbedWrite)), - new RoleScope(nameof(Defaults.Scopes.HighlightRead)), - new RoleScope(nameof(Defaults.Scopes.HighlightWrite)), - } + Scopes = new List + { + new RoleScope(nameof(Defaults.Scopes.EmbedRead)), + new RoleScope(nameof(Defaults.Scopes.EmbedWrite)), + new RoleScope(nameof(Defaults.Scopes.HighlightRead)), + new RoleScope(nameof(Defaults.Scopes.HighlightWrite)), + new RoleScope(nameof(Defaults.Scopes.ProjectWrite)) + } }; roles.Add(prRole); Role dataOfficerRole = new Role - { - Name = nameof(Defaults.Roles.DataOfficer), - Scopes = new List - { - new RoleScope(nameof(Defaults.Scopes.InstitutionUserRead)), - new RoleScope(nameof(Defaults.Scopes.InstitutionUserWrite)), - new RoleScope(nameof(Defaults.Scopes.InstitutionEmbedWrite)), - new RoleScope(nameof(Defaults.Scopes.InstitutionProjectWrite)), - } - }; + { + Name = nameof(Defaults.Roles.DataOfficer), + Scopes = new List + { + new RoleScope(nameof(Defaults.Scopes.InstitutionUserRead)), + new RoleScope(nameof(Defaults.Scopes.InstitutionUserWrite)), + new RoleScope(nameof(Defaults.Scopes.InstitutionEmbedWrite)), + new RoleScope(nameof(Defaults.Scopes.InstitutionProjectWrite)), + new RoleScope(nameof(Defaults.Scopes.ProjectWrite)) + } + }; roles.Add(dataOfficerRole); - Role administratorRole = new Role() + Role administratorRole = new Role { Name = nameof(Defaults.Roles.Administrator), Scopes = new List - { - new RoleScope(nameof(Defaults.Scopes.ProjectWrite)), - new RoleScope(nameof(Defaults.Scopes.UserWrite)), - new RoleScope(nameof(Defaults.Scopes.UserRead)), - new RoleScope(nameof(Defaults.Scopes.RoleRead)), - new RoleScope(nameof(Defaults.Scopes.RoleWrite)), - new RoleScope(nameof(Defaults.Scopes.HighlightRead)), - new RoleScope(nameof(Defaults.Scopes.HighlightWrite)), - new RoleScope(nameof(Defaults.Scopes.EmbedRead)), - new RoleScope(nameof(Defaults.Scopes.EmbedWrite)), - new RoleScope(nameof(Defaults.Scopes.InstitutionRead)), - new RoleScope(nameof(Defaults.Scopes.InstitutionWrite)), - new RoleScope(nameof(Defaults.Scopes.FileWrite)), - new RoleScope(nameof(Defaults.Scopes.CallToActionOptionWrite)) - } + { + new RoleScope(nameof(Defaults.Scopes.AdminProjectWrite)), + new RoleScope(nameof(Defaults.Scopes.UserWrite)), + new RoleScope(nameof(Defaults.Scopes.UserRead)), + new RoleScope(nameof(Defaults.Scopes.RoleRead)), + new RoleScope(nameof(Defaults.Scopes.RoleWrite)), + new RoleScope(nameof(Defaults.Scopes.HighlightRead)), + new RoleScope(nameof(Defaults.Scopes.HighlightWrite)), + new RoleScope(nameof(Defaults.Scopes.EmbedRead)), + new RoleScope(nameof(Defaults.Scopes.EmbedWrite)), + new RoleScope(nameof(Defaults.Scopes.InstitutionRead)), + new RoleScope(nameof(Defaults.Scopes.InstitutionWrite)), + new RoleScope(nameof(Defaults.Scopes.FileWrite)), + new RoleScope(nameof(Defaults.Scopes.CallToActionOptionWrite)), + new RoleScope(nameof(Defaults.Scopes.ProjectWrite)), + new RoleScope(nameof(Defaults.Scopes.DataSourceWrite)) + } }; roles.Add(administratorRole); + Role alumniRole = new Role + { + Name = nameof(Defaults.Roles.Alumni), + Scopes = new List() + }; + roles.Add(alumniRole); + return roles; } + /// - /// Seeds the admin user. + /// Seeds the admin user. /// /// The roles. /// Returns the admin user that will be seeded. @@ -127,27 +186,62 @@ public static User SeedAdminUser(List roles) Role = adminRole, IdentityId = "88421113", Email = "Administrator@dex.software", - Name = "Administrator bob" + Name = "Administrator bob", }; return user; } + /// - /// This method seeds a test institution in the database. + /// This method seeds a test institution in the database. /// /// Returns the institution that will be seeded in the database. public static Institution SeedInstitution() { Institution institution = new Institution - { - Name = "Fontys", - Description = "Description for Fontys", - IdentityId = "https://identity.fhict.nl" + { + Name = "Fontys", + Description = "Description for Fontys", + IdentityId = "https://identity.fhict.nl" }; return institution; } + + public static List SeedInstitutions() + { + return new List + { + new Institution + { + Name = "Fontys", + Description = "Description for Fontys", + IdentityId = "https://identity.fhict.nl" + }, + new Institution() + { + Name = "Fontys2", + Description = "Description for Fontys2", + IdentityId = "https://identity.fhict.nl" + }, + new Institution() + { + Name = "Fontys3", + Description = "Description for Fontys3", + IdentityId = "https://identity.fhict.nl" + } + }; + } + + public static IEnumerable SeedProjectLikes(List projects) + { + foreach(Project project in projects) + { + yield return new ProjectLike { Date = DateTime.Now, LikedProject = project, UserId = 1 }; + } + } + /// - /// Seeds the pr user. + /// Seeds the pr user. /// /// The roles. /// Returns the PR user that will be seeded. @@ -164,8 +258,9 @@ public static User SeedPrUser(List roles) return user; } + /// - /// This method seeds the data officer user. + /// This method seeds the data officer user. /// /// This variable contains the roles that exist. /// This method returns the data officer role that will be seeded. @@ -184,6 +279,20 @@ public static User SeedDataOfficerUser(List roles) return user; } + public static User SeedAlumniUser(List roles) + { + Role alumniRole = roles.Find(i => i.Name == nameof(Defaults.Roles.Alumni)); + User user = new User + { + IdentityId = "123456789", + Email = "Alumni@dex.software", + Name = "Alumni test", + Role = alumniRole + }; + + return user; + } + /// /// Seed random projects into the database using fake date from Bogus /// @@ -205,6 +314,7 @@ public static List SeedProjects(List users) project.Created = DateTime.Now.AddDays(-2); project.Updated = DateTime.Now; + projects.Add(project); } @@ -228,13 +338,15 @@ public static List SeedCollaborators(List projects) collaborator.ProjectId = project.Id; collaborator2.ProjectId = project.Id; + collaborators.Add(collaborator); collaborators.Add(collaborator2); } return collaborators; } + /// - /// Seeds the highlights. + /// Seeds the highlights. /// /// The projects. /// Returns a list of project highlights that wil be seeded. @@ -259,44 +371,46 @@ public static List SeedHighlights(List projects) } /// - /// This method seeds call to action options. + /// This method seeds call to action options. /// /// Returns a list of call to actions options that can be seeded into the database. public static List SeedCallToActionOptions() { return new List - { - new CallToActionOption - { - Type = "title", - Value = "Join today" - }, - new CallToActionOption - { - Type = "title", - Value = "Provide feedback" - }, - new CallToActionOption - { - Type = "title", - Value = "Apply now" - }, - new CallToActionOption - { - Type = "title", - Value = "Collaborate with us" - }, - new CallToActionOption - { - Type = "title", - Value = "More information" - }, - new CallToActionOption - { - Type = "title", - Value = "Get in touch" - }, - }; + { + new CallToActionOption + { + Type = "title", + Value = "Join today" + }, + new CallToActionOption + { + Type = "title", + Value = "Provide feedback" + }, + new CallToActionOption + { + Type = "title", + Value = "Apply now" + }, + new CallToActionOption + { + Type = "title", + Value = "Collaborate with us" + }, + new CallToActionOption + { + Type = "title", + Value = "More information" + }, + new CallToActionOption + { + Type = "title", + Value = "Get in touch" + } + }; } + } + } diff --git a/Data/IdentityDbContext.cs b/Data/IdentityDbContext.cs index 82c5848e..a173f1ee 100644 --- a/Data/IdentityDbContext.cs +++ b/Data/IdentityDbContext.cs @@ -15,28 +15,30 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using System.Collections.Generic; -using Data.Configurations; -using Data.Helpers; using Microsoft.EntityFrameworkCore; using Models; namespace Data { + /// - /// ApplicationDatabaseContext + /// ApplicationDatabaseContext /// - /// + /// public class IdentityDbContext : DbContext { + /// + /// The identityDBContext constructor + /// + /// public IdentityDbContext(DbContextOptions options) : base(options) { } /// - /// Gets or sets the user. + /// Gets or sets the user. /// /// - /// The user. + /// The user. /// public DbSet IdentityUser { get; set; } diff --git a/Data/IdentityMigrations/20200619015019_initial.cs b/Data/IdentityMigrations/20200619015019_initial.cs index 516295e7..f4f6c8ec 100644 --- a/Data/IdentityMigrations/20200619015019_initial.cs +++ b/Data/IdentityMigrations/20200619015019_initial.cs @@ -2,33 +2,32 @@ namespace _4_Data.IdentityMigrations { + public partial class initial : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.CreateTable( - name: "IdentityUser", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Password = table.Column(nullable: true), - UserId = table.Column(nullable: true), - Email = table.Column(nullable: true), - Lastname = table.Column(nullable: true), - Firstname = table.Column(nullable: true), - IsActive = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_IdentityUser", x => x.Id); - }); + migrationBuilder.CreateTable("IdentityUser", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Password = table.Column(nullable: true), + UserId = table.Column(nullable: true), + Email = table.Column(nullable: true), + Lastname = table.Column(nullable: true), + Firstname = table.Column(nullable: true), + IsActive = table.Column(nullable: false) + }, + constraints: table => { table.PrimaryKey("PK_IdentityUser", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "IdentityUser"); + migrationBuilder.DropTable("IdentityUser"); } + } + } diff --git a/Data/IdentityMigrations/20200619025636_Updated_user_model.cs b/Data/IdentityMigrations/20200619025636_Updated_user_model.cs index 42ed594b..f06a4379 100644 --- a/Data/IdentityMigrations/20200619025636_Updated_user_model.cs +++ b/Data/IdentityMigrations/20200619025636_Updated_user_model.cs @@ -2,76 +2,66 @@ namespace _4_Data.IdentityMigrations { + public partial class Updated_user_model : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "UserId", - table: "IdentityUser"); - - migrationBuilder.AddColumn( - name: "ExternalPicture", - table: "IdentityUser", - nullable: true); - - migrationBuilder.AddColumn( - name: "ExternalProfileUrl", - table: "IdentityUser", - nullable: true); - - migrationBuilder.AddColumn( - name: "Name", - table: "IdentityUser", - nullable: true); - - migrationBuilder.AddColumn( - name: "ProviderId", - table: "IdentityUser", - nullable: true); - - migrationBuilder.AddColumn( - name: "SubjectId", - table: "IdentityUser", - nullable: true); - - migrationBuilder.AddColumn( - name: "Username", - table: "IdentityUser", - nullable: true); + migrationBuilder.DropColumn("UserId", + "IdentityUser"); + + migrationBuilder.AddColumn("ExternalPicture", + "IdentityUser", + nullable: true); + + migrationBuilder.AddColumn("ExternalProfileUrl", + "IdentityUser", + nullable: true); + + migrationBuilder.AddColumn("Name", + "IdentityUser", + nullable: true); + + migrationBuilder.AddColumn("ProviderId", + "IdentityUser", + nullable: true); + + migrationBuilder.AddColumn("SubjectId", + "IdentityUser", + nullable: true); + + migrationBuilder.AddColumn("Username", + "IdentityUser", + nullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "ExternalPicture", - table: "IdentityUser"); - - migrationBuilder.DropColumn( - name: "ExternalProfileUrl", - table: "IdentityUser"); - - migrationBuilder.DropColumn( - name: "Name", - table: "IdentityUser"); - - migrationBuilder.DropColumn( - name: "ProviderId", - table: "IdentityUser"); - - migrationBuilder.DropColumn( - name: "SubjectId", - table: "IdentityUser"); - - migrationBuilder.DropColumn( - name: "Username", - table: "IdentityUser"); - - migrationBuilder.AddColumn( - name: "UserId", - table: "IdentityUser", - type: "nvarchar(max)", - nullable: true); + migrationBuilder.DropColumn("ExternalPicture", + "IdentityUser"); + + migrationBuilder.DropColumn("ExternalProfileUrl", + "IdentityUser"); + + migrationBuilder.DropColumn("Name", + "IdentityUser"); + + migrationBuilder.DropColumn("ProviderId", + "IdentityUser"); + + migrationBuilder.DropColumn("SubjectId", + "IdentityUser"); + + migrationBuilder.DropColumn("Username", + "IdentityUser"); + + migrationBuilder.AddColumn("UserId", + "IdentityUser", + "nvarchar(max)", + nullable: true); } + } + } diff --git a/Data/IdentityMigrations/20200619032919_Updated_user_model2.cs b/Data/IdentityMigrations/20200619032919_Updated_user_model2.cs index a0bcd4fb..7e05b835 100644 --- a/Data/IdentityMigrations/20200619032919_Updated_user_model2.cs +++ b/Data/IdentityMigrations/20200619032919_Updated_user_model2.cs @@ -2,21 +2,23 @@ namespace _4_Data.IdentityMigrations { + public partial class Updated_user_model2 : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "ExternalSubjectId", - table: "IdentityUser", - nullable: true); + migrationBuilder.AddColumn("ExternalSubjectId", + "IdentityUser", + nullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "ExternalSubjectId", - table: "IdentityUser"); + migrationBuilder.DropColumn("ExternalSubjectId", + "IdentityUser"); } + } + } diff --git a/Data/IdentityMigrations/IdentityDbContextModelSnapshot.cs b/Data/IdentityMigrations/IdentityDbContextModelSnapshot.cs index 912c00dd..b2516162 100644 --- a/Data/IdentityMigrations/IdentityDbContextModelSnapshot.cs +++ b/Data/IdentityMigrations/IdentityDbContextModelSnapshot.cs @@ -1,71 +1,77 @@ // + using Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace _4_Data.IdentityMigrations { + [DbContext(typeof(IdentityDbContext))] - partial class IdentityDbContextModelSnapshot : ModelSnapshot + internal class IdentityDbContextModelSnapshot : ModelSnapshot { + protected override void BuildModel(ModelBuilder modelBuilder) { -#pragma warning disable 612, 618 + #pragma warning disable 612, 618 modelBuilder .HasAnnotation("ProductVersion", "3.1.5") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("Models.IdentityUser", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Models.IdentityUser", + b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", + SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Email") - .HasColumnType("nvarchar(max)"); + b.Property("Email") + .HasColumnType("nvarchar(max)"); - b.Property("ExternalPicture") - .HasColumnType("nvarchar(max)"); + b.Property("ExternalPicture") + .HasColumnType("nvarchar(max)"); - b.Property("ExternalProfileUrl") - .HasColumnType("nvarchar(max)"); + b.Property("ExternalProfileUrl") + .HasColumnType("nvarchar(max)"); - b.Property("ExternalSubjectId") - .HasColumnType("nvarchar(max)"); + b.Property("ExternalSubjectId") + .HasColumnType("nvarchar(max)"); - b.Property("Firstname") - .HasColumnType("nvarchar(max)"); + b.Property("Firstname") + .HasColumnType("nvarchar(max)"); - b.Property("IsActive") - .HasColumnType("bit"); + b.Property("IsActive") + .HasColumnType("bit"); - b.Property("Lastname") - .HasColumnType("nvarchar(max)"); + b.Property("Lastname") + .HasColumnType("nvarchar(max)"); - b.Property("Name") - .HasColumnType("nvarchar(max)"); + b.Property("Name") + .HasColumnType("nvarchar(max)"); - b.Property("Password") - .HasColumnType("nvarchar(max)"); + b.Property("Password") + .HasColumnType("nvarchar(max)"); - b.Property("ProviderId") - .HasColumnType("nvarchar(max)"); + b.Property("ProviderId") + .HasColumnType("nvarchar(max)"); - b.Property("SubjectId") - .HasColumnType("nvarchar(max)"); + b.Property("SubjectId") + .HasColumnType("nvarchar(max)"); - b.Property("Username") - .HasColumnType("nvarchar(max)"); + b.Property("Username") + .HasColumnType("nvarchar(max)"); - b.HasKey("Id"); + b.HasKey("Id"); - b.ToTable("IdentityUser"); - }); -#pragma warning restore 612, 618 + b.ToTable("IdentityUser"); + }); + #pragma warning restore 612, 618 } + } + } diff --git a/Data/Migrations/20200526082115_initial.cs b/Data/Migrations/20200526082115_initial.cs index 5146cf61..d847c6be 100644 --- a/Data/Migrations/20200526082115_initial.cs +++ b/Data/Migrations/20200526082115_initial.cs @@ -1,220 +1,193 @@ -using System; using Microsoft.EntityFrameworkCore.Migrations; +using System; namespace _4_Data.Migrations { + public partial class initial : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.CreateTable( - name: "Role", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Role", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "RoleScope", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Scope = table.Column(nullable: false), - RoleId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_RoleScope", x => x.Id); - table.ForeignKey( - name: "FK_RoleScope_Role_RoleId", - column: x => x.RoleId, - principalTable: "Role", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "User", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(nullable: false), - RoleId = table.Column(nullable: false), - Email = table.Column(nullable: false), - IdentityId = table.Column(nullable: false), - ProfileUrl = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_User", x => x.Id); - table.ForeignKey( - name: "FK_User_Role_RoleId", - column: x => x.RoleId, - principalTable: "Role", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "Project", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(nullable: false), - Name = table.Column(nullable: false), - Description = table.Column(nullable: true), - ShortDescription = table.Column(nullable: false), - Uri = table.Column(nullable: false), - Created = table.Column(nullable: false), - Updated = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Project", x => x.Id); - table.ForeignKey( - name: "FK_Project_User_UserId", - column: x => x.UserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Collaborators", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - FullName = table.Column(nullable: true), - Role = table.Column(nullable: true), - ProjectId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Collaborators", x => x.Id); - table.ForeignKey( - name: "FK_Collaborators_Project_ProjectId", - column: x => x.ProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "EmbeddedProject", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(nullable: false), - ProjectId = table.Column(nullable: false), - Guid = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_EmbeddedProject", x => x.Id); - table.ForeignKey( - name: "FK_EmbeddedProject_Project_ProjectId", - column: x => x.ProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_EmbeddedProject_User_UserId", - column: x => x.UserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.NoAction); - }); - - migrationBuilder.CreateTable( - name: "Highlight", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ProjectId = table.Column(nullable: false), - StartDate = table.Column(nullable: true), - EndDate = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Highlight", x => x.Id); - table.ForeignKey( - name: "FK_Highlight_Project_ProjectId", - column: x => x.ProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_Collaborators_ProjectId", - table: "Collaborators", - column: "ProjectId"); - - migrationBuilder.CreateIndex( - name: "IX_EmbeddedProject_ProjectId", - table: "EmbeddedProject", - column: "ProjectId"); - - migrationBuilder.CreateIndex( - name: "IX_EmbeddedProject_UserId", - table: "EmbeddedProject", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_Highlight_ProjectId", - table: "Highlight", - column: "ProjectId"); - - migrationBuilder.CreateIndex( - name: "IX_Project_UserId", - table: "Project", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_RoleScope_RoleId", - table: "RoleScope", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "IX_User_RoleId", - table: "User", - column: "RoleId"); + migrationBuilder.CreateTable("Role", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: true) + }, + constraints: table => { table.PrimaryKey("PK_Role", x => x.Id); }); + + migrationBuilder.CreateTable("RoleScope", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Scope = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RoleScope", x => x.Id); + table.ForeignKey("FK_RoleScope_Role_RoleId", + x => x.RoleId, + "Role", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("User", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: false), + RoleId = table.Column(nullable: false), + Email = table.Column(nullable: false), + IdentityId = table.Column(nullable: false), + ProfileUrl = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.Id); + table.ForeignKey("FK_User_Role_RoleId", + x => x.RoleId, + "Role", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable("Project", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(nullable: false), + Name = table.Column(nullable: false), + Description = table.Column(nullable: true), + ShortDescription = table.Column(nullable: false), + Uri = table.Column(nullable: false), + Created = table.Column(nullable: false), + Updated = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Project", x => x.Id); + table.ForeignKey("FK_Project_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("Collaborators", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + FullName = table.Column(nullable: true), + Role = table.Column(nullable: true), + ProjectId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Collaborators", x => x.Id); + table.ForeignKey("FK_Collaborators_Project_ProjectId", + x => x.ProjectId, + "Project", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("EmbeddedProject", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(nullable: false), + ProjectId = table.Column(nullable: false), + Guid = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmbeddedProject", x => x.Id); + table.ForeignKey("FK_EmbeddedProject_Project_ProjectId", + x => x.ProjectId, + "Project", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey("FK_EmbeddedProject_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.NoAction); + }); + + migrationBuilder.CreateTable("Highlight", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ProjectId = table.Column(nullable: false), + StartDate = table.Column(nullable: true), + EndDate = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Highlight", x => x.Id); + table.ForeignKey("FK_Highlight_Project_ProjectId", + x => x.ProjectId, + "Project", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_Collaborators_ProjectId", + "Collaborators", + "ProjectId"); + + migrationBuilder.CreateIndex("IX_EmbeddedProject_ProjectId", + "EmbeddedProject", + "ProjectId"); + + migrationBuilder.CreateIndex("IX_EmbeddedProject_UserId", + "EmbeddedProject", + "UserId"); + + migrationBuilder.CreateIndex("IX_Highlight_ProjectId", + "Highlight", + "ProjectId"); + + migrationBuilder.CreateIndex("IX_Project_UserId", + "Project", + "UserId"); + + migrationBuilder.CreateIndex("IX_RoleScope_RoleId", + "RoleScope", + "RoleId"); + + migrationBuilder.CreateIndex("IX_User_RoleId", + "User", + "RoleId"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "Collaborators"); + migrationBuilder.DropTable("Collaborators"); - migrationBuilder.DropTable( - name: "EmbeddedProject"); + migrationBuilder.DropTable("EmbeddedProject"); - migrationBuilder.DropTable( - name: "Highlight"); + migrationBuilder.DropTable("Highlight"); - migrationBuilder.DropTable( - name: "RoleScope"); + migrationBuilder.DropTable("RoleScope"); - migrationBuilder.DropTable( - name: "Project"); + migrationBuilder.DropTable("Project"); - migrationBuilder.DropTable( - name: "User"); + migrationBuilder.DropTable("User"); - migrationBuilder.DropTable( - name: "Role"); + migrationBuilder.DropTable("Role"); } + } + } diff --git a/Data/Migrations/20200527095846_addIsPublicFlagToUser.cs b/Data/Migrations/20200527095846_addIsPublicFlagToUser.cs index a9db03e9..a113b8fd 100644 --- a/Data/Migrations/20200527095846_addIsPublicFlagToUser.cs +++ b/Data/Migrations/20200527095846_addIsPublicFlagToUser.cs @@ -2,22 +2,24 @@ namespace _4_Data.Migrations { + public partial class addIsPublicFlagToUser : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "IsPublic", - table: "User", - nullable: false, - defaultValue: false); + migrationBuilder.AddColumn("IsPublic", + "User", + nullable: false, + defaultValue: false); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "IsPublic", - table: "User"); + migrationBuilder.DropColumn("IsPublic", + "User"); } + } + } diff --git a/Data/Migrations/20200904113526_DecriptionAddedForHighlightTable.cs b/Data/Migrations/20200904113526_DecriptionAddedForHighlightTable.cs index 1b01526f..5cc0b628 100644 --- a/Data/Migrations/20200904113526_DecriptionAddedForHighlightTable.cs +++ b/Data/Migrations/20200904113526_DecriptionAddedForHighlightTable.cs @@ -2,61 +2,57 @@ namespace _4_Data.Migrations { + public partial class DecriptionAddedForHighlightTable : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_RoleScope_Role_RoleId", - table: "RoleScope"); - - migrationBuilder.AlterColumn( - name: "RoleId", - table: "RoleScope", - nullable: false, - oldClrType: typeof(int), - oldType: "int", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "Description", - table: "Highlight", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddForeignKey( - name: "FK_RoleScope_Role_RoleId", - table: "RoleScope", - column: "RoleId", - principalTable: "Role", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + migrationBuilder.DropForeignKey("FK_RoleScope_Role_RoleId", + "RoleScope"); + + migrationBuilder.AlterColumn("RoleId", + "RoleScope", + nullable: false, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AddColumn("Description", + "Highlight", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddForeignKey("FK_RoleScope_Role_RoleId", + "RoleScope", + "RoleId", + "Role", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_RoleScope_Role_RoleId", - table: "RoleScope"); - - migrationBuilder.DropColumn( - name: "Description", - table: "Highlight"); - - migrationBuilder.AlterColumn( - name: "RoleId", - table: "RoleScope", - type: "int", - nullable: true, - oldClrType: typeof(int)); - - migrationBuilder.AddForeignKey( - name: "FK_RoleScope_Role_RoleId", - table: "RoleScope", - column: "RoleId", - principalTable: "Role", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + migrationBuilder.DropForeignKey("FK_RoleScope_Role_RoleId", + "RoleScope"); + + migrationBuilder.DropColumn("Description", + "Highlight"); + + migrationBuilder.AlterColumn("RoleId", + "RoleScope", + "int", + nullable: true, + oldClrType: typeof(int)); + + migrationBuilder.AddForeignKey("FK_RoleScope_Role_RoleId", + "RoleScope", + "RoleId", + "Role", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); } + } + } diff --git a/Data/Migrations/20201006000639_InitLikedProjectByUserTable.cs b/Data/Migrations/20201006000639_InitLikedProjectByUserTable.cs index 76633f9e..d5a77c8a 100644 --- a/Data/Migrations/20201006000639_InitLikedProjectByUserTable.cs +++ b/Data/Migrations/20201006000639_InitLikedProjectByUserTable.cs @@ -2,51 +2,49 @@ namespace _4_Data.Migrations { + public partial class InitLikedProjectByUserTable : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.CreateTable( - name: "ProjectLike", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - LikedProjectId = table.Column(nullable: true), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_LikedProjectByUser", x => x.Id); - table.ForeignKey( - name: "FK_LikedProjectByUser_Project_LikedProjectId", - column: x => x.LikedProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_LikedProjectByUser_User_UserId", - column: x => x.UserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_LikedProjectByUser_LikedProjectId", - table: "ProjectLike", - column: "LikedProjectId"); - - migrationBuilder.CreateIndex( - name: "IX_LikedProjectByUser_UserId", - table: "ProjectLike", - column: "UserId"); + migrationBuilder.CreateTable("ProjectLike", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + LikedProjectId = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LikedProjectByUser", x => x.Id); + table.ForeignKey("FK_LikedProjectByUser_Project_LikedProjectId", + x => x.LikedProjectId, + "Project", + "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey("FK_LikedProjectByUser_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_LikedProjectByUser_LikedProjectId", + "ProjectLike", + "LikedProjectId"); + + migrationBuilder.CreateIndex("IX_LikedProjectByUser_UserId", + "ProjectLike", + "UserId"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "ProjectLike"); + migrationBuilder.DropTable("ProjectLike"); } + } + } diff --git a/Data/Migrations/20201014124545_AddFollowUsersAndProjects.cs b/Data/Migrations/20201014124545_AddFollowUsersAndProjects.cs index af4ff07a..f000ce34 100644 --- a/Data/Migrations/20201014124545_AddFollowUsersAndProjects.cs +++ b/Data/Migrations/20201014124545_AddFollowUsersAndProjects.cs @@ -2,90 +2,82 @@ namespace _4_Data.Migrations { + public partial class AddFollowUsersAndProjects : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.CreateTable( - name: "UserProject", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - ProjectId = table.Column(nullable: true), - UserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserProject", x => x.Id); - table.ForeignKey( - name: "FK_UserProject_Project_ProjectId", - column: x => x.ProjectId, - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_UserProject_User_UserId", - column: x => x.UserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable("UserProject", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + ProjectId = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserProject", x => x.Id); + table.ForeignKey("FK_UserProject_Project_ProjectId", + x => x.ProjectId, + "Project", + "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey("FK_UserProject_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "UserUser", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(nullable: true), - FollowedUserId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserUser", x => x.Id); - table.ForeignKey( - name: "FK_UserUser_User_FollowedUserId", - column: x => x.FollowedUserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_UserUser_User_UserId", - column: x => x.UserId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - }); + migrationBuilder.CreateTable("UserUser", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(nullable: true), + FollowedUserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserUser", x => x.Id); + table.ForeignKey("FK_UserUser_User_FollowedUserId", + x => x.FollowedUserId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey("FK_UserUser_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.Restrict); + }); - migrationBuilder.CreateIndex( - name: "IX_UserProject_ProjectId", - table: "UserProject", - column: "ProjectId"); + migrationBuilder.CreateIndex("IX_UserProject_ProjectId", + "UserProject", + "ProjectId"); - migrationBuilder.CreateIndex( - name: "IX_UserProject_UserId", - table: "UserProject", - column: "UserId"); + migrationBuilder.CreateIndex("IX_UserProject_UserId", + "UserProject", + "UserId"); - migrationBuilder.CreateIndex( - name: "IX_UserUser_FollowedUserId", - table: "UserUser", - column: "FollowedUserId"); + migrationBuilder.CreateIndex("IX_UserUser_FollowedUserId", + "UserUser", + "FollowedUserId"); - migrationBuilder.CreateIndex( - name: "IX_UserUser_UserId", - table: "UserUser", - column: "UserId"); + migrationBuilder.CreateIndex("IX_UserUser_UserId", + "UserUser", + "UserId"); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropTable( - name: "UserProject"); + migrationBuilder.DropTable("UserProject"); - migrationBuilder.DropTable( - name: "UserUser"); + migrationBuilder.DropTable("UserUser"); } + } + } diff --git a/Data/Migrations/20201016093942_AddFilesAndProjectIconReferences.cs b/Data/Migrations/20201016093942_AddFilesAndProjectIconReferences.cs index 45e14ef4..8ed7da5c 100644 --- a/Data/Migrations/20201016093942_AddFilesAndProjectIconReferences.cs +++ b/Data/Migrations/20201016093942_AddFilesAndProjectIconReferences.cs @@ -1,74 +1,68 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; +using System; namespace _4_Data.Migrations { + public partial class AddFilesAndProjectIconReferences : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "ProjectIconId", - table: "Project", - nullable: true); + migrationBuilder.AddColumn("ProjectIconId", + "Project", + nullable: true); - migrationBuilder.CreateTable( - name: "File", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Path = table.Column(nullable: false), - UploadDateTime = table.Column(nullable: false), - Name = table.Column(nullable: true), - UploaderId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_File", x => x.Id); - table.ForeignKey( - name: "FK_File_User_UploaderId", - column: x => x.UploaderId, - principalTable: "User", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable("File", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Path = table.Column(nullable: false), + UploadDateTime = table.Column(nullable: false), + Name = table.Column(nullable: true), + UploaderId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_File", x => x.Id); + table.ForeignKey("FK_File_User_UploaderId", + x => x.UploaderId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_Project_ProjectIconId", - table: "Project", - column: "ProjectIconId"); + migrationBuilder.CreateIndex("IX_Project_ProjectIconId", + "Project", + "ProjectIconId"); - migrationBuilder.CreateIndex( - name: "IX_File_UploaderId", - table: "File", - column: "UploaderId"); + migrationBuilder.CreateIndex("IX_File_UploaderId", + "File", + "UploaderId"); - migrationBuilder.AddForeignKey( - name: "FK_Project_File_ProjectIconId", - table: "Project", - column: "ProjectIconId", - principalTable: "File", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + migrationBuilder.AddForeignKey("FK_Project_File_ProjectIconId", + "Project", + "ProjectIconId", + "File", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_Project_File_ProjectIconId", - table: "Project"); + migrationBuilder.DropForeignKey("FK_Project_File_ProjectIconId", + "Project"); - migrationBuilder.DropTable( - name: "File"); + migrationBuilder.DropTable("File"); - migrationBuilder.DropIndex( - name: "IX_Project_ProjectIconId", - table: "Project"); + migrationBuilder.DropIndex("IX_Project_ProjectIconId", + "Project"); - migrationBuilder.DropColumn( - name: "ProjectIconId", - table: "Project"); + migrationBuilder.DropColumn("ProjectIconId", + "Project"); } + } + } diff --git a/Data/Migrations/20201026125836_InstitutionAddedForUser.cs b/Data/Migrations/20201026125836_InstitutionAddedForUser.cs index 340d84ff..0309533d 100644 --- a/Data/Migrations/20201026125836_InstitutionAddedForUser.cs +++ b/Data/Migrations/20201026125836_InstitutionAddedForUser.cs @@ -2,59 +2,52 @@ namespace _4_Data.Migrations { + public partial class InstitutionAddedForUser : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "InstitutionId", - table: "User", - nullable: true); - - migrationBuilder.CreateTable( - name: "Institution", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(nullable: true), - Description = table.Column(nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Institution", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_User_InstitutionId", - table: "User", - column: "InstitutionId"); - - migrationBuilder.AddForeignKey( - name: "FK_User_Institution_InstitutionId", - table: "User", - column: "InstitutionId", - principalTable: "Institution", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + migrationBuilder.AddColumn("InstitutionId", + "User", + nullable: true); + + migrationBuilder.CreateTable("Institution", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: true), + Description = table.Column(nullable: true) + }, + constraints: table => { table.PrimaryKey("PK_Institution", x => x.Id); }); + + migrationBuilder.CreateIndex("IX_User_InstitutionId", + "User", + "InstitutionId"); + + migrationBuilder.AddForeignKey("FK_User_Institution_InstitutionId", + "User", + "InstitutionId", + "Institution", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_User_Institution_InstitutionId", - table: "User"); + migrationBuilder.DropForeignKey("FK_User_Institution_InstitutionId", + "User"); - migrationBuilder.DropTable( - name: "Institution"); + migrationBuilder.DropTable("Institution"); - migrationBuilder.DropIndex( - name: "IX_User_InstitutionId", - table: "User"); + migrationBuilder.DropIndex("IX_User_InstitutionId", + "User"); - migrationBuilder.DropColumn( - name: "InstitutionId", - table: "User"); + migrationBuilder.DropColumn("InstitutionId", + "User"); } + } + } diff --git a/Data/Migrations/20201119130835_AddedIdentityIdToInstitution.cs b/Data/Migrations/20201119130835_AddedIdentityIdToInstitution.cs index 2c34d09f..4b7435e3 100644 --- a/Data/Migrations/20201119130835_AddedIdentityIdToInstitution.cs +++ b/Data/Migrations/20201119130835_AddedIdentityIdToInstitution.cs @@ -2,21 +2,23 @@ namespace _4_Data.Migrations { + public partial class AddedIdentityIdToInstitution : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "IdentityId", - table: "Institution", - nullable: true); + migrationBuilder.AddColumn("IdentityId", + "Institution", + nullable: true); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "IdentityId", - table: "Institution"); + migrationBuilder.DropColumn("IdentityId", + "Institution"); } + } + } diff --git a/Data/Migrations/20201129133208_AddedCallToActions.cs b/Data/Migrations/20201129133208_AddedCallToActions.cs index dfddce2e..728a40a1 100644 --- a/Data/Migrations/20201129133208_AddedCallToActions.cs +++ b/Data/Migrations/20201129133208_AddedCallToActions.cs @@ -2,76 +2,67 @@ namespace _4_Data.Migrations { + public partial class AddedCallToActions : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "CallToActionId", - table: "Project", - nullable: true); + migrationBuilder.AddColumn("CallToActionId", + "Project", + nullable: true); - migrationBuilder.CreateTable( - name: "CallToAction", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - OptionValue = table.Column(nullable: false), - Value = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CallToAction", x => x.Id); - }); + migrationBuilder.CreateTable("CallToAction", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + OptionValue = table.Column(nullable: false), + Value = table.Column(nullable: false) + }, + constraints: table => { table.PrimaryKey("PK_CallToAction", x => x.Id); }); - migrationBuilder.CreateTable( - name: "CallToActionOption", - columns: table => new - { - Id = table.Column(nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Type = table.Column(nullable: false), - Value = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CallToActionOption", x => x.Id); - }); + migrationBuilder.CreateTable("CallToActionOption", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Type = table.Column(nullable: false), + Value = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CallToActionOption", x => x.Id); + }); - migrationBuilder.CreateIndex( - name: "IX_Project_CallToActionId", - table: "Project", - column: "CallToActionId"); + migrationBuilder.CreateIndex("IX_Project_CallToActionId", + "Project", + "CallToActionId"); - migrationBuilder.AddForeignKey( - name: "FK_Project_CallToAction_CallToActionId", - table: "Project", - column: "CallToActionId", - principalTable: "CallToAction", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + migrationBuilder.AddForeignKey("FK_Project_CallToAction_CallToActionId", + "Project", + "CallToActionId", + "CallToAction", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropForeignKey( - name: "FK_Project_CallToAction_CallToActionId", - table: "Project"); + migrationBuilder.DropForeignKey("FK_Project_CallToAction_CallToActionId", + "Project"); - migrationBuilder.DropTable( - name: "CallToAction"); + migrationBuilder.DropTable("CallToAction"); - migrationBuilder.DropTable( - name: "CallToActionOption"); + migrationBuilder.DropTable("CallToActionOption"); - migrationBuilder.DropIndex( - name: "IX_Project_CallToActionId", - table: "Project"); + migrationBuilder.DropIndex("IX_Project_CallToActionId", + "Project"); - migrationBuilder.DropColumn( - name: "CallToActionId", - table: "Project"); + migrationBuilder.DropColumn("CallToActionId", + "Project"); } + } + } diff --git a/Data/Migrations/20201205133647_AddDateToLikes.cs b/Data/Migrations/20201205133647_AddDateToLikes.cs index 8fb4f282..1fa5266c 100644 --- a/Data/Migrations/20201205133647_AddDateToLikes.cs +++ b/Data/Migrations/20201205133647_AddDateToLikes.cs @@ -1,24 +1,34 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; +using System; namespace _4_Data.Migrations { + public partial class AddDateToLikes : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "Date", - table: "ProjectLike", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + migrationBuilder.AddColumn("Date", + "ProjectLike", + nullable: false, + defaultValue: new DateTime( + 1, + 1, + 1, + 0, + 0, + 0, + 0, + DateTimeKind.Unspecified)); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "Date", - table: "ProjectLike"); + migrationBuilder.DropColumn("Date", + "ProjectLike"); } + } + } diff --git a/Data/Migrations/20210104155334_CreatedDataSource.Designer.cs b/Data/Migrations/20210104155334_CreatedDataSource.Designer.cs new file mode 100644 index 00000000..4ce1a0fe --- /dev/null +++ b/Data/Migrations/20210104155334_CreatedDataSource.Designer.cs @@ -0,0 +1,445 @@ +// +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("20210104155334_CreatedDataSource")] + partial class CreatedDataSource + { + 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.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.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + 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("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("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.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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.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.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.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.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.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.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/20210104155334_CreatedDataSource.cs b/Data/Migrations/20210104155334_CreatedDataSource.cs new file mode 100644 index 00000000..7d6f52a9 --- /dev/null +++ b/Data/Migrations/20210104155334_CreatedDataSource.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + + public partial class CreatedDataSource : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("DataSource", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Guid = table.Column(nullable: true), + Title = table.Column(nullable: true), + Description = table.Column(nullable: true), + IconId = table.Column(nullable: true), + IsVisible = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DataSource", x => x.Id); + table.ForeignKey("FK_DataSource_File_IconId", + x => x.IconId, + "File", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex("IX_DataSource_IconId", + "DataSource", + "IconId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("DataSource"); + } + + } + +} diff --git a/Data/Migrations/20210112081848_AddedIntitutePrivateToProject.cs b/Data/Migrations/20210112081848_AddedIntitutePrivateToProject.cs index b4958e75..9eb4fe37 100644 --- a/Data/Migrations/20210112081848_AddedIntitutePrivateToProject.cs +++ b/Data/Migrations/20210112081848_AddedIntitutePrivateToProject.cs @@ -2,22 +2,24 @@ namespace _4_Data.Migrations { + public partial class AddedIntitutePrivateToProject : Migration { + protected override void Up(MigrationBuilder migrationBuilder) { - migrationBuilder.AddColumn( - name: "InstitutePrivate", - table: "Project", - nullable: false, - defaultValue: false); + migrationBuilder.AddColumn("InstitutePrivate", + "Project", + nullable: false, + defaultValue: false); } protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.DropColumn( - name: "InstitutePrivate", - table: "Project"); + migrationBuilder.DropColumn("InstitutePrivate", + "Project"); } + } + } diff --git a/Data/Migrations/20210114090647_AddedWizardPages.Designer.cs b/Data/Migrations/20210114090647_AddedWizardPages.Designer.cs new file mode 100644 index 00000000..f22c4935 --- /dev/null +++ b/Data/Migrations/20210114090647_AddedWizardPages.Designer.cs @@ -0,0 +1,598 @@ +// +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("20210114090647_AddedWizardPages")] + partial class AddedWizardPages + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("DataSourceId"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany() + .HasForeignKey("DataSourceId"); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany() + .HasForeignKey("WizardPageId"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.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/20210114090647_AddedWizardPages.cs b/Data/Migrations/20210114090647_AddedWizardPages.cs new file mode 100644 index 00000000..9b04e693 --- /dev/null +++ b/Data/Migrations/20210114090647_AddedWizardPages.cs @@ -0,0 +1,67 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +namespace _4_Data.Migrations +{ + + public partial class AddedWizardPages : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("WizardPage", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: true), + Description = table.Column(nullable: true), + CreatedAt = table.Column(nullable: false), + UpdatedAt = table.Column(nullable: false) + }, + constraints: table => { table.PrimaryKey("PK_WizardPage", x => x.Id); }); + + migrationBuilder.CreateTable("DataSourceWizardPage", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DataSourceId = table.Column(nullable: true), + WizardPageId = table.Column(nullable: true), + AuthFlow = table.Column(nullable: false), + OrderIndex = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DataSourceWizardPage", x => x.Id); + table.ForeignKey("FK_DataSourceWizardPage_DataSource_DataSourceId", + x => x.DataSourceId, + "DataSource", + "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey("FK_DataSourceWizardPage_WizardPage_WizardPageId", + x => x.WizardPageId, + "WizardPage", + "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex("IX_DataSourceWizardPage_DataSourceId", + "DataSourceWizardPage", + "DataSourceId"); + + migrationBuilder.CreateIndex("IX_DataSourceWizardPage_WizardPageId", + "DataSourceWizardPage", + "WizardPageId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("DataSourceWizardPage"); + + migrationBuilder.DropTable("WizardPage"); + } + + } + +} diff --git a/Data/Migrations/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.Designer.cs b/Data/Migrations/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.Designer.cs new file mode 100644 index 00000000..4f881c87 --- /dev/null +++ b/Data/Migrations/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.Designer.cs @@ -0,0 +1,595 @@ +// +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("20210115145332_ManyToManyRelationForDataSourceAndWizardPage")] + partial class ManyToManyRelationForDataSourceAndWizardPage + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.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"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.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/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.cs b/Data/Migrations/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.cs new file mode 100644 index 00000000..1d886bad --- /dev/null +++ b/Data/Migrations/20210115145332_ManyToManyRelationForDataSourceAndWizardPage.cs @@ -0,0 +1,114 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + + public partial class ManyToManyRelationForDataSourceAndWizardPage : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey("FK_DataSourceWizardPage_DataSource_DataSourceId", + "DataSourceWizardPage"); + + migrationBuilder.DropForeignKey("FK_DataSourceWizardPage_WizardPage_WizardPageId", + "DataSourceWizardPage"); + + migrationBuilder.DropPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage"); + + migrationBuilder.DropIndex("IX_DataSourceWizardPage_DataSourceId", + "DataSourceWizardPage"); + + migrationBuilder.DropColumn("Id", + "DataSourceWizardPage"); + + migrationBuilder.AlterColumn("WizardPageId", + "DataSourceWizardPage", + nullable: false, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AlterColumn("DataSourceId", + "DataSourceWizardPage", + nullable: false, + oldClrType: typeof(int), + oldType: "int", + oldNullable: true); + + migrationBuilder.AddPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage", + new[] {"DataSourceId", "WizardPageId"}); + + migrationBuilder.AddForeignKey("FK_DataSourceWizardPage_DataSource_DataSourceId", + "DataSourceWizardPage", + "DataSourceId", + "DataSource", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey("FK_DataSourceWizardPage_WizardPage_WizardPageId", + "DataSourceWizardPage", + "WizardPageId", + "WizardPage", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey("FK_DataSourceWizardPage_DataSource_DataSourceId", + "DataSourceWizardPage"); + + migrationBuilder.DropForeignKey("FK_DataSourceWizardPage_WizardPage_WizardPageId", + "DataSourceWizardPage"); + + migrationBuilder.DropPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage"); + + migrationBuilder.AlterColumn("WizardPageId", + "DataSourceWizardPage", + "int", + nullable: true, + oldClrType: typeof(int)); + + migrationBuilder.AlterColumn("DataSourceId", + "DataSourceWizardPage", + "int", + nullable: true, + oldClrType: typeof(int)); + + migrationBuilder.AddColumn("Id", + "DataSourceWizardPage", + "int", + nullable: false, + defaultValue: 0) + .Annotation("SqlServer:Identity", "1, 1"); + + migrationBuilder.AddPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage", + "Id"); + + migrationBuilder.CreateIndex("IX_DataSourceWizardPage_DataSourceId", + "DataSourceWizardPage", + "DataSourceId"); + + migrationBuilder.AddForeignKey("FK_DataSourceWizardPage_DataSource_DataSourceId", + "DataSourceWizardPage", + "DataSourceId", + "DataSource", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey("FK_DataSourceWizardPage_WizardPage_WizardPageId", + "DataSourceWizardPage", + "WizardPageId", + "WizardPage", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + } + +} diff --git a/Data/Migrations/20210128180958_Merge.Designer.cs b/Data/Migrations/20210128180958_Merge.Designer.cs new file mode 100644 index 00000000..a59a6489 --- /dev/null +++ b/Data/Migrations/20210128180958_Merge.Designer.cs @@ -0,0 +1,538 @@ +// +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("20210128180958_Merge")] + partial class Merge + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.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.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutePrivate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccountCreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedGraduationDate") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProfileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("RoleId"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProject"); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTask"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.HasOne("Models.User", "User") + .WithMany("UserTasks") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.HasOne("Models.User", "FollowedUser") + .WithMany("FollowedUsers") + .HasForeignKey("FollowedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20210128180958_Merge.cs b/Data/Migrations/20210128180958_Merge.cs new file mode 100644 index 00000000..8183fe3f --- /dev/null +++ b/Data/Migrations/20210128180958_Merge.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; + +namespace _4_Data.Migrations +{ + + public partial class Merge : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("AccountCreationDate", + "User", + nullable: true); + + migrationBuilder.AddColumn("ExpectedGraduationDate", + "User", + nullable: true); + + migrationBuilder.CreateTable("UserTask", + table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(nullable: true), + Status = table.Column(nullable: false), + Type = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserTask", x => x.Id); + table.ForeignKey("FK_UserTask_User_UserId", + x => x.UserId, + "User", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_UserTask_UserId", + "UserTask", + "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("UserTask"); + + migrationBuilder.DropColumn("AccountCreationDate", + "User"); + + migrationBuilder.DropColumn("ExpectedGraduationDate", + "User"); + } + + } + +} diff --git a/Data/Migrations/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.Designer.cs b/Data/Migrations/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.Designer.cs new file mode 100644 index 00000000..7f9c9814 --- /dev/null +++ b/Data/Migrations/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.Designer.cs @@ -0,0 +1,595 @@ +// +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("20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable")] + partial class AddedAuthFlowAsKeyToDataSourceWizardPageTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Collaborators"); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Guid") + .HasColumnType("nvarchar(max)"); + + b.Property("IconId") + .HasColumnType("int"); + + b.Property("IsVisible") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IconId"); + + b.ToTable("DataSource"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.HasKey("DataSourceId", "WizardPageId", "AuthFlow"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.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/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.cs b/Data/Migrations/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.cs new file mode 100644 index 00000000..9e047fe5 --- /dev/null +++ b/Data/Migrations/20210203132026_AddedAuthFlowAsKeyToDataSourceWizardPageTable.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + + public partial class AddedAuthFlowAsKeyToDataSourceWizardPageTable : Migration + { + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage"); + + migrationBuilder.AddPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage", + new[] {"DataSourceId", "WizardPageId", "AuthFlow"}); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage"); + + migrationBuilder.AddPrimaryKey("PK_DataSourceWizardPage", + "DataSourceWizardPage", + new[] {"DataSourceId", "WizardPageId"}); + } + + } + +} diff --git a/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.Designer.cs b/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.Designer.cs new file mode 100644 index 00000000..a7edf617 --- /dev/null +++ b/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.Designer.cs @@ -0,0 +1,664 @@ +// +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("20210223115146_AddLinkedInstitutionsToProject")] + partial class AddLinkedInstitutionsToProject + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Collaborators"); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Guid") + .HasColumnType("nvarchar(max)"); + + b.Property("IconId") + .HasColumnType("int"); + + b.Property("IsVisible") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IconId"); + + b.ToTable("DataSource"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.HasKey("DataSourceId", "WizardPageId", "AuthFlow"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutePrivate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.HasKey("ProjectId", "InstitutionId"); + + b.HasIndex("InstitutionId"); + + b.ToTable("ProjectInstitution"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccountCreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedGraduationDate") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProfileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("RoleId"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProject"); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTask"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("LinkedInstitutions") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.HasOne("Models.User", "User") + .WithMany("UserTasks") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.HasOne("Models.User", "FollowedUser") + .WithMany("FollowedUsers") + .HasForeignKey("FollowedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.cs b/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.cs new file mode 100644 index 00000000..06ab2b3b --- /dev/null +++ b/Data/Migrations/20210223115146_AddLinkedInstitutionsToProject.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class AddLinkedInstitutionsToProject : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ProjectInstitution", + columns: table => new + { + ProjectId = table.Column(nullable: false), + InstitutionId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProjectInstitution", x => new { x.ProjectId, x.InstitutionId }); + table.ForeignKey( + name: "FK_ProjectInstitution_Institution_InstitutionId", + column: x => x.InstitutionId, + principalTable: "Institution", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ProjectInstitution_Project_ProjectId", + column: x => x.ProjectId, + principalTable: "Project", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProjectInstitution_InstitutionId", + table: "ProjectInstitution", + column: "InstitutionId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ProjectInstitution"); + } + } +} diff --git a/Data/Migrations/20210224144128_AddIdToProjectInstitution.Designer.cs b/Data/Migrations/20210224144128_AddIdToProjectInstitution.Designer.cs new file mode 100644 index 00000000..80d8ea9c --- /dev/null +++ b/Data/Migrations/20210224144128_AddIdToProjectInstitution.Designer.cs @@ -0,0 +1,671 @@ +// +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("20210224144128_AddIdToProjectInstitution")] + partial class AddIdToProjectInstitution + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Models.CallToAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("OptionValue") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToAction"); + }); + + modelBuilder.Entity("Models.CallToActionOption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CallToActionOption"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FullName") + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Role") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Collaborators"); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Guid") + .HasColumnType("nvarchar(max)"); + + b.Property("IconId") + .HasColumnType("int"); + + b.Property("IsVisible") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IconId"); + + b.ToTable("DataSource"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.Property("DataSourceId") + .HasColumnType("int"); + + b.Property("WizardPageId") + .HasColumnType("int"); + + b.Property("AuthFlow") + .HasColumnType("bit"); + + b.Property("OrderIndex") + .HasColumnType("int"); + + b.HasKey("DataSourceId", "WizardPageId", "AuthFlow"); + + b.HasIndex("WizardPageId"); + + b.ToTable("DataSourceWizardPage"); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Guid") + .HasColumnType("uniqueidentifier"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("EmbeddedProject"); + }); + + modelBuilder.Entity("Models.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UploadDateTime") + .HasColumnType("datetime2"); + + b.Property("UploaderId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UploaderId"); + + b.ToTable("File"); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.ToTable("Highlight"); + }); + + modelBuilder.Entity("Models.Institution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IdentityId") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Institution"); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CallToActionId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutePrivate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProjectIconId") + .HasColumnType("int"); + + b.Property("ShortDescription") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Updated") + .HasColumnType("datetime2"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CallToActionId"); + + b.HasIndex("ProjectIconId"); + + b.HasIndex("UserId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("ProjectId"); + + b.ToTable("ProjectInstitution"); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("LikedProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("LikedProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("ProjectLike"); + }); + + modelBuilder.Entity("Models.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RoleId") + .HasColumnType("int"); + + b.Property("Scope") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("RoleScope"); + }); + + modelBuilder.Entity("Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccountCreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ExpectedGraduationDate") + .HasColumnType("datetime2"); + + b.Property("IdentityId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("InstitutionId") + .HasColumnType("int"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProfileUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("InstitutionId"); + + b.HasIndex("RoleId"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId"); + + b.HasIndex("UserId"); + + b.ToTable("UserProject"); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserTask"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FollowedUserId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("FollowedUserId"); + + b.HasIndex("UserId"); + + b.ToTable("UserUser"); + }); + + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + + modelBuilder.Entity("Models.Collaborator", b => + { + b.HasOne("Models.Project", null) + .WithMany("Collaborators") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.DataSource", b => + { + b.HasOne("Models.File", "Icon") + .WithMany() + .HasForeignKey("IconId"); + }); + + modelBuilder.Entity("Models.DataSourceWizardPage", b => + { + b.HasOne("Models.DataSource", "DataSource") + .WithMany("DataSourceWizardPages") + .HasForeignKey("DataSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.WizardPage", "WizardPage") + .WithMany("DataSourceWizardPages") + .HasForeignKey("WizardPageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.EmbeddedProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.File", b => + { + b.HasOne("Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Highlight", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.Project", b => + { + b.HasOne("Models.CallToAction", "CallToAction") + .WithMany() + .HasForeignKey("CallToActionId"); + + b.HasOne("Models.File", "ProjectIcon") + .WithMany() + .HasForeignKey("ProjectIconId"); + + b.HasOne("Models.User", "User") + .WithMany("Projects") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectInstitution", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.Project", "Project") + .WithMany("LinkedInstitutions") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.ProjectLike", b => + { + b.HasOne("Models.Project", "LikedProject") + .WithMany("Likes") + .HasForeignKey("LikedProjectId"); + + b.HasOne("Models.User", "ProjectLiker") + .WithMany("LikedProjectsByUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.RoleScope", b => + { + b.HasOne("Models.Role", null) + .WithMany("Scopes") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.User", b => + { + b.HasOne("Models.Institution", "Institution") + .WithMany() + .HasForeignKey("InstitutionId"); + + b.HasOne("Models.Role", "Role") + .WithMany() + .HasForeignKey("RoleId"); + }); + + modelBuilder.Entity("Models.UserProject", b => + { + b.HasOne("Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + + b.HasOne("Models.User", "User") + .WithMany("UserProject") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Models.UserTask", b => + { + b.HasOne("Models.User", "User") + .WithMany("UserTasks") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Models.UserUser", b => + { + b.HasOne("Models.User", "FollowedUser") + .WithMany("FollowedUsers") + .HasForeignKey("FollowedUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20210224144128_AddIdToProjectInstitution.cs b/Data/Migrations/20210224144128_AddIdToProjectInstitution.cs new file mode 100644 index 00000000..40cde351 --- /dev/null +++ b/Data/Migrations/20210224144128_AddIdToProjectInstitution.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace _4_Data.Migrations +{ + public partial class AddIdToProjectInstitution : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_ProjectInstitution", + table: "ProjectInstitution"); + + migrationBuilder.AddColumn( + name: "Id", + table: "ProjectInstitution", + nullable: false, + defaultValue: 0) + .Annotation("SqlServer:Identity", "1, 1"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ProjectInstitution", + table: "ProjectInstitution", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_ProjectInstitution_ProjectId", + table: "ProjectInstitution", + column: "ProjectId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_ProjectInstitution", + table: "ProjectInstitution"); + + migrationBuilder.DropIndex( + name: "IX_ProjectInstitution_ProjectId", + table: "ProjectInstitution"); + + migrationBuilder.DropColumn( + name: "Id", + table: "ProjectInstitution"); + + migrationBuilder.AddPrimaryKey( + name: "PK_ProjectInstitution", + table: "ProjectInstitution", + columns: new[] { "ProjectId", "InstitutionId" }); + } + } +} diff --git a/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 4f566c57..75d5ee5c 100644 --- a/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -82,6 +82,56 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -233,6 +283,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); + 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") @@ -300,10 +372,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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)"); @@ -355,6 +433,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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") @@ -377,6 +478,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("UserUser"); }); + modelBuilder.Entity("Models.WizardPage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("WizardPage"); + }); + modelBuilder.Entity("Models.Collaborator", b => { b.HasOne("Models.Project", null) @@ -386,6 +511,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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") @@ -436,6 +583,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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") @@ -482,6 +644,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .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") diff --git a/Digital_Excellence.sln b/Digital_Excellence.sln index 83926181..c4bcb15f 100644 --- a/Digital_Excellence.sln +++ b/Digital_Excellence.sln @@ -3,34 +3,36 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29806.167 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1_API", "API\1_API.csproj", "{F40BA6E7-D870-47A8-A672-DEA4D5FA96F5}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{445ECBDE-DF20-43B3-BD4B-4881F05203A2}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2_Services", "Services\2_Services.csproj", "{9940A638-7501-49EE-A816-560A2908A029}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "01_API", "API\01_API.csproj", "{F40BA6E7-D870-47A8-A672-DEA4D5FA96F5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3_Repositories", "Repositories\3_Repositories.csproj", "{860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "02_Services", "Services\02_Services.csproj", "{9940A638-7501-49EE-A816-560A2908A029}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "5_Models", "Models\5_Models.csproj", "{22BBD62E-AD77-46E5-86F8-900737BE19FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "03_Services.Tests", "Services.Tests\03_Services.Tests.csproj", "{D7ABD315-4B00-4A5F-8961-8147ED6602C0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "4_Data", "Data\4_Data.csproj", "{B626B71A-6C31-4921-896C-A8946092D159}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "04_Repositories", "Repositories\04_Repositories.csproj", "{860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "6_IdentityServer", "IdentityServer\6_IdentityServer.csproj", "{823A7672-639A-4C68-AD71-04C9C62E1468}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "05_Repositories.Tests", "Repositories.Tests\05_Repositories.Tests.csproj", "{471B1E44-E308-4C6D-B595-15AD939820DB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2_Services.Tests", "Services.Tests\2_Services.Tests.csproj", "{D7ABD315-4B00-4A5F-8961-8147ED6602C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "06_Data", "Data\06_Data.csproj", "{B626B71A-6C31-4921-896C-A8946092D159}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "3_Repositories.Tests", "Repositories.Tests\3_Repositories.Tests.csproj", "{471B1E44-E308-4C6D-B595-15AD939820DB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "07_Models", "Models\07_Models.csproj", "{22BBD62E-AD77-46E5-86F8-900737BE19FB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{445ECBDE-DF20-43B3-BD4B-4881F05203A2}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "6_IdentityServer", "IdentityServer\6_IdentityServer.csproj", "{823A7672-639A-4C68-AD71-04C9C62E1468}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "09_MessageBrokerPublisher", "MessageBrokerPublisher\09_MessageBrokerPublisher.csproj", "{3A0B6EED-3F6B-43F7-9C1E-0311EC9871CB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationSystem", "NotificationSystem\NotificationSystem.csproj", "{A0F1BB66-1749-47B7-940F-2EA6D004F33B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "10_MessageBrokerPublisher.Tests", "MessageBrokerPublisher.Tests\10_MessageBrokerPublisher.Tests.csproj", "{4F2E803B-9C59-4A7F-9830-6627381851EA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessageBrokerPublisher", "MessageBrokerPublisher\MessageBrokerPublisher.csproj", "{3A0B6EED-3F6B-43F7-9C1E-0311EC9871CB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "11_NotificationSystem", "NotificationSystem\11_NotificationSystem.csproj", "{A0F1BB66-1749-47B7-940F-2EA6D004F33B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessageBrokerPublisher.Tests", "MessageBrokerPublisher.Tests\MessageBrokerPublisher.Tests.csproj", "{4F2E803B-9C59-4A7F-9830-6627381851EA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "12_NotificationSystem.Tests", "NotificationSystem.Tests\12_NotificationSystem.Tests.csproj", "{C85546CD-EFA2-42C5-A698-175BAE85515E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NotificationSystem.Tests", "NotificationSystem.Tests\NotificationSystem.Tests.csproj", "{C85546CD-EFA2-42C5-A698-175BAE85515E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "13_JobScheduler", "JobScheduler\13_JobScheduler.csproj", "{E4BFCDFE-A85E-451E-8D02-7B55052FFB6E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -46,34 +48,30 @@ Global {9940A638-7501-49EE-A816-560A2908A029}.Debug|Any CPU.Build.0 = Debug|Any CPU {9940A638-7501-49EE-A816-560A2908A029}.Release|Any CPU.ActiveCfg = Release|Any CPU {9940A638-7501-49EE-A816-560A2908A029}.Release|Any CPU.Build.0 = Release|Any CPU + {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Release|Any CPU.Build.0 = Release|Any CPU {860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {860D05B3-46CB-4EA9-9F83-63BBF44AFB2F}.Release|Any CPU.Build.0 = Release|Any CPU - {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Release|Any CPU.Build.0 = Release|Any CPU + {471B1E44-E308-4C6D-B595-15AD939820DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {471B1E44-E308-4C6D-B595-15AD939820DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {471B1E44-E308-4C6D-B595-15AD939820DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {471B1E44-E308-4C6D-B595-15AD939820DB}.Release|Any CPU.Build.0 = Release|Any CPU {B626B71A-6C31-4921-896C-A8946092D159}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B626B71A-6C31-4921-896C-A8946092D159}.Debug|Any CPU.Build.0 = Debug|Any CPU {B626B71A-6C31-4921-896C-A8946092D159}.Release|Any CPU.ActiveCfg = Release|Any CPU {B626B71A-6C31-4921-896C-A8946092D159}.Release|Any CPU.Build.0 = Release|Any CPU + {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22BBD62E-AD77-46E5-86F8-900737BE19FB}.Release|Any CPU.Build.0 = Release|Any CPU {823A7672-639A-4C68-AD71-04C9C62E1468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {823A7672-639A-4C68-AD71-04C9C62E1468}.Debug|Any CPU.Build.0 = Debug|Any CPU {823A7672-639A-4C68-AD71-04C9C62E1468}.Release|Any CPU.ActiveCfg = Release|Any CPU {823A7672-639A-4C68-AD71-04C9C62E1468}.Release|Any CPU.Build.0 = Release|Any CPU - {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7ABD315-4B00-4A5F-8961-8147ED6602C0}.Release|Any CPU.Build.0 = Release|Any CPU - {471B1E44-E308-4C6D-B595-15AD939820DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {471B1E44-E308-4C6D-B595-15AD939820DB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {471B1E44-E308-4C6D-B595-15AD939820DB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {471B1E44-E308-4C6D-B595-15AD939820DB}.Release|Any CPU.Build.0 = Release|Any CPU - {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Release|Any CPU.Build.0 = Release|Any CPU {3A0B6EED-3F6B-43F7-9C1E-0311EC9871CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3A0B6EED-3F6B-43F7-9C1E-0311EC9871CB}.Debug|Any CPU.Build.0 = Debug|Any CPU {3A0B6EED-3F6B-43F7-9C1E-0311EC9871CB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -82,10 +80,18 @@ Global {4F2E803B-9C59-4A7F-9830-6627381851EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F2E803B-9C59-4A7F-9830-6627381851EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F2E803B-9C59-4A7F-9830-6627381851EA}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0F1BB66-1749-47B7-940F-2EA6D004F33B}.Release|Any CPU.Build.0 = Release|Any CPU {C85546CD-EFA2-42C5-A698-175BAE85515E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C85546CD-EFA2-42C5-A698-175BAE85515E}.Debug|Any CPU.Build.0 = Debug|Any CPU {C85546CD-EFA2-42C5-A698-175BAE85515E}.Release|Any CPU.ActiveCfg = Release|Any CPU {C85546CD-EFA2-42C5-A698-175BAE85515E}.Release|Any CPU.Build.0 = Release|Any CPU + {E4BFCDFE-A85E-451E-8D02-7B55052FFB6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4BFCDFE-A85E-451E-8D02-7B55052FFB6E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4BFCDFE-A85E-451E-8D02-7B55052FFB6E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4BFCDFE-A85E-451E-8D02-7B55052FFB6E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/IdentityServer/6_IdentityServer.csproj b/IdentityServer/6_IdentityServer.csproj index e25d881a..659aead9 100644 --- a/IdentityServer/6_IdentityServer.csproj +++ b/IdentityServer/6_IdentityServer.csproj @@ -1,9 +1,9 @@ - + netcoreapp3.1 8 - 1.0.1-beta + 1.1.0-beta @@ -23,9 +23,9 @@ - - - - + + + + diff --git a/IdentityServer/Configuration/Config.cs b/IdentityServer/Configuration/Config.cs index 24151189..a0a147e1 100644 --- a/IdentityServer/Configuration/Config.cs +++ b/IdentityServer/Configuration/Config.cs @@ -16,7 +16,6 @@ */ using NetEscapades.Configuration.Validation; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Configuration @@ -52,12 +51,17 @@ public class Config : IValidatable public SwaggerConfig Swagger { get; set; } /// - /// Gets or sets the fhict oidc. + /// Gets or sets the fhict oidc. /// /// - /// The ffhict oidc. + /// The ffhict oidc. /// public FhictOIDConfig FfhictOIDC { get; set; } + + public JobSchedulerConfig JobScheduler { get; set; } + + public ApiAuthentication ApiAuthentication { get; set; } + /// /// Validates this instance. /// @@ -67,6 +71,8 @@ public void Validate() Validator.ValidateObject(Api, new ValidationContext(Api), true); Validator.ValidateObject(Swagger, new ValidationContext(Swagger), true); Validator.ValidateObject(FfhictOIDC, new ValidationContext(FfhictOIDC), true); + Validator.ValidateObject(JobScheduler, new ValidationContext(JobScheduler), true); + Validator.ValidateObject(ApiAuthentication, new ValidationContext(ApiAuthentication), true); } /// @@ -110,6 +116,7 @@ public class SelfConfig /// The public origin. /// public string PublicOrigin { get; set; } + } /// @@ -144,32 +151,39 @@ public class ApiConfig /// [Required] public string ClientSecret { get; set; } + } /// /// public class FrontendConfig { + /// /// Gets or sets the redirect uri of the frontend. /// public string RedirectUriFrontend { get; set; } + /// /// Gets or sets the refresh uri of the frontend. /// public string RefreshUriFrontend { get; set; } + /// /// Gets or sets the redirect uri for Postman. /// public string RedirectUriPostman { get; set; } + /// /// Gets or sets the post logouts uri of the Frontend. /// public string PostLogoutUriFrontend { get; set; } + /// /// Gets or sets the client identifier. /// public string ClientId { get; set; } + /// /// Gets or sets the client secret. /// @@ -178,75 +192,131 @@ public class FrontendConfig } /// - /// Contains the swagger configuration. + /// Contains the swagger configuration. /// public class SwaggerConfig { + /// - /// Gets or sets the redirect uris swagger. + /// Gets or sets the redirect uris swagger. /// /// - /// The redirect uris swagger. + /// The redirect uris swagger. /// public string RedirectUrisSwagger { get; set; } /// - /// Gets or sets the post logout uris swagger. + /// Gets or sets the post logout uris swagger. /// /// - /// The post logout uris swagger. + /// The post logout uris swagger. /// public string PostLogoutUrisSwagger { get; set; } + } /// - /// Contains the Fontys SSO configuration. + /// Contains the Fontys SSO configuration. /// public class FhictOIDConfig { + /// - /// Gets or sets the fhict identity URL. + /// Gets or sets the fhict identity URL. /// /// - /// The fhict identity URL. + /// The fhict identity URL. /// - [Required, Url] + [Required] + [Url] public string Authority { get; set; } + /// - /// Gets or sets the fhict client identifier. + /// Gets or sets the fhict client identifier. /// /// - /// The fhict client identifier. + /// The fhict client identifier. /// [Required] public string ClientId { get; set; } + /// - /// Gets or sets the fhict client secret. + /// Gets or sets the fhict client secret. /// /// - /// The fhict client secret. + /// The fhict client secret. /// [Required] public string ClientSecret { get; set; } + /// - /// Gets or sets the fhict scopes. + /// Gets or sets the fhict scopes. /// /// - /// The fhict scopes. + /// The fhict scopes. /// [Required] public string Scopes { get; set; } + /// - /// Gets or sets the fhict redirect URI. + /// Gets or sets the fhict redirect URI. /// /// - /// The fhict redirect URI. + /// The fhict redirect URI. /// - [Required, Url] + [Required] + [Url] public string RedirectUri { get; set; } } + + } + + public class JobSchedulerConfig + { + + /// + /// Gets or sets the job scheduler identifier. + /// + /// + /// The client identifier. + /// + [Required] + public string ClientId { get; set; } + + /// + /// Gets or sets the job scheduler secret. + /// + /// + /// The job scheduler secret. + /// + [Required] + public string ClientSecret { get; set; } + + } + + public class ApiAuthentication + { + + /// + /// Gets or sets the api authentication identifier. + /// + /// + /// The client identifier. + /// + [Required] + public string ClientId { get; set; } + + /// + /// Gets or sets the api authentication secret. + /// + /// + /// The api authentication secret. + /// + [Required] + public string ClientSecret { get; set; } + } } diff --git a/IdentityServer/Configuration/IdentityConfig.cs b/IdentityServer/Configuration/IdentityConfig.cs index 0365a291..89347bfc 100644 --- a/IdentityServer/Configuration/IdentityConfig.cs +++ b/IdentityServer/Configuration/IdentityConfig.cs @@ -21,7 +21,6 @@ using IdentityServer4.Models; using Models.Defaults; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; namespace IdentityServer.Configuration @@ -30,16 +29,6 @@ namespace IdentityServer.Configuration public static class IdentityConfig { - public static IEnumerable GetIdentityResources() - { - return new List - { - new IdentityResources.OpenId(), - new IdentityResources.Profile(), - new IdentityResources.Email() - }; - } - public static IEnumerable Apis => new[] { @@ -48,6 +37,7 @@ public static IEnumerable GetIdentityResources() Scopes = { new Scope(nameof(Defaults.Scopes.ProjectRead)), + new Scope(nameof(Defaults.Scopes.AdminProjectWrite)), new Scope(nameof(Defaults.Scopes.ProjectWrite)), new Scope(nameof(Defaults.Scopes.UserWrite)), new Scope(nameof(Defaults.Scopes.UserRead)), @@ -55,15 +45,46 @@ public static IEnumerable GetIdentityResources() new Scope(nameof(Defaults.Scopes.HighlightRead)), new Scope(nameof(Defaults.Scopes.EmbedWrite)), new Scope(nameof(Defaults.Scopes.EmbedRead)), - new Scope(nameof(Defaults.Scopes.FileWrite)) + new Scope(nameof(Defaults.Scopes.FileWrite)), + new Scope(nameof(Defaults.Scopes.UserTaskWrite)) } - } + }, + new ApiResource(IdentityServerConstants.LocalApi.ScopeName) }; + public static IEnumerable GetIdentityResources() + { + return new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + new IdentityResources.Email() + }; + } + public static IEnumerable Clients(Config config) { return new[] - { + { + // machine to machine client (API -> Identity) + new Client + { + ClientId = "dex-api", + AllowedGrantTypes = GrantTypes.ClientCredentials, + ClientSecrets = + { + new Secret(config.ApiAuthentication.ClientSecret.Sha256()) + }, + AllowedScopes = + { + IdentityServerConstants.LocalApi.ScopeName + }, + Claims = new List + { + new Claim(JwtClaimTypes.Role, Defaults.Roles.BackendApplication) + } + }, + // machine to machine client (Identity -> API) new Client { @@ -76,6 +97,7 @@ public static IEnumerable Clients(Config config) AllowedScopes = { nameof(Defaults.Scopes.ProjectRead), + nameof(Defaults.Scopes.AdminProjectWrite), nameof(Defaults.Scopes.ProjectWrite), nameof(Defaults.Scopes.UserWrite), nameof(Defaults.Scopes.UserRead), @@ -83,7 +105,8 @@ public static IEnumerable Clients(Config config) nameof(Defaults.Scopes.HighlightWrite), nameof(Defaults.Scopes.EmbedWrite), nameof(Defaults.Scopes.EmbedRead), - nameof(Defaults.Scopes.FileWrite) + nameof(Defaults.Scopes.FileWrite), + nameof(Defaults.Scopes.UserTaskWrite) }, Claims = new List { @@ -106,28 +129,29 @@ public static IEnumerable Clients(Config config) RequireConsent = false, // where to redirect to after login - RedirectUris = new List { - config.Frontend.RedirectUriFrontend, - config.Frontend.RedirectUriPostman, - config.Frontend.RefreshUriFrontend - }, + RedirectUris = new List + { + config.Frontend.RedirectUriFrontend, + config.Frontend.RedirectUriPostman, + config.Frontend.RefreshUriFrontend + }, // where to redirect to after logout - PostLogoutRedirectUris = new List { - config.Frontend.PostLogoutUriFrontend - }, + PostLogoutRedirectUris = new List + { + config.Frontend.PostLogoutUriFrontend + }, AllowedScopes = new List - { - IdentityServerConstants.StandardScopes.OpenId, - IdentityServerConstants.StandardScopes.Profile, - IdentityServerConstants.StandardScopes.Email, - "dex-api" - }, + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + "dex-api" + }, AllowAccessTokensViaBrowser = true, AllowOfflineAccess = true }, - new Client { ClientId = "Swagger-UI", @@ -135,18 +159,38 @@ public static IEnumerable Clients(Config config) AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, AlwaysIncludeUserClaimsInIdToken = true, - RedirectUris = new List { - config.Swagger.RedirectUrisSwagger - }, - PostLogoutRedirectUris = new List { - config.Swagger.PostLogoutUrisSwagger - }, + RedirectUris = new List + { + config.Swagger.RedirectUrisSwagger + }, + PostLogoutRedirectUris = new List + { + config.Swagger.PostLogoutUrisSwagger + }, AllowedScopes = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + "dex-api" + } + }, + new Client + { + ClientId = config.JobScheduler.ClientId, + ClientName = "Digital Excellence Job Scheduler", + AllowedGrantTypes = GrantTypes.ClientCredentials, + ClientSecrets = + { + new Secret(config.JobScheduler.ClientSecret.Sha256()) + }, + AllowedScopes = { - IdentityServerConstants.StandardScopes.OpenId, - IdentityServerConstants.StandardScopes.Profile, - "dex-api", - }, + "dex-api" + }, + Claims = new List + { + new Claim(JwtClaimTypes.Role, Defaults.Roles.BackendApplication) + } } }; } diff --git a/IdentityServer/Extensions/DependencyInjectionExtensions.cs b/IdentityServer/Extensions/DependencyInjectionExtensions.cs new file mode 100644 index 00000000..54f10b86 --- /dev/null +++ b/IdentityServer/Extensions/DependencyInjectionExtensions.cs @@ -0,0 +1,41 @@ +/* +* 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 Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Repositories; +using Services.Services; + +namespace IdentityServer +{ + + public static class DependencyInjectionExtensions + { + + public static IServiceCollection AddServicesAndRepositories(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + + } + +} diff --git a/IdentityServer/GlobalSuppressions.cs b/IdentityServer/GlobalSuppressions.cs index bd3fb9f2..f72f9e48 100644 --- a/IdentityServer/GlobalSuppressions.cs +++ b/IdentityServer/GlobalSuppressions.cs @@ -7,5 +7,19 @@ // Todo implement state validation on the authorization flow -[assembly: SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "This error message is suppressed because there was not enough time to implement the state checking, although not critical it would be better to have.", Scope = "member", Target = "~M:IdentityServer.ExternalController.Callback(System.String,System.String,System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] -[assembly: SuppressMessage("Redundancy", "RCS1163:Unused parameter.", Justification = "This error message is suppressed because there was not enough time to implement the state checking, although not critical it would be better to have.", Scope = "member", Target = "~M:IdentityServer.ExternalController.Callback(System.String,System.String,System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] +[assembly: + SuppressMessage("Style", + "IDE0060:Remove unused parameter", + Justification = + "This error message is suppressed because there was not enough time to implement the state checking, although not critical it would be better to have.", + Scope = "member", + Target = + "~M:IdentityServer.ExternalController.Callback(System.String,System.String,System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] +[assembly: + SuppressMessage("Redundancy", + "RCS1163:Unused parameter.", + Justification = + "This error message is suppressed because there was not enough time to implement the state checking, although not critical it would be better to have.", + Scope = "member", + Target = + "~M:IdentityServer.ExternalController.Callback(System.String,System.String,System.String)~System.Threading.Tasks.Task{Microsoft.AspNetCore.Mvc.IActionResult}")] diff --git a/IdentityServer/Quickstart/Account/AccountController.cs b/IdentityServer/Quickstart/Account/AccountController.cs index d07480a6..cc1174bc 100644 --- a/IdentityServer/Quickstart/Account/AccountController.cs +++ b/IdentityServer/Quickstart/Account/AccountController.cs @@ -16,20 +16,16 @@ */ using IdentityModel; -using IdentityServer.Quickstart; using IdentityServer4; using IdentityServer4.Events; using IdentityServer4.Extensions; using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Stores; -using IdentityServer4.Test; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Hosting; using Models; using Services.Services; using System; @@ -52,11 +48,12 @@ namespace IdentityServer [AllowAnonymous] public class AccountController : Controller { + private readonly IClientStore clientStore; private readonly IEventService events; + private readonly IIdentityUserService identityUserService; private readonly IIdentityServerInteractionService interaction; private readonly IAuthenticationSchemeProvider schemeProvider; - private readonly IIdentityUserService identityUserService; public AccountController( IIdentityServerInteractionService interaction, @@ -100,7 +97,8 @@ public async Task Login(string returnUrl) } int idx = vm.ReturnUrl.IndexOf('?'); string query = idx >= 0 ? vm.ReturnUrl.Substring(idx) : ""; - string providerSchema = HttpUtility.ParseQueryString(query).Get("provider"); + string providerSchema = HttpUtility.ParseQueryString(query) + .Get("provider"); if(vm.VisibleExternalProviders.FirstOrDefault(i => i.AuthenticationScheme == providerSchema) != null) { @@ -156,12 +154,11 @@ public async Task Login(LoginInputModel model, string button) // validate username/password against in-memory store if(await identityUserService.ValidateCredentialsAsync(model.Username, model.Password)) { - IdentityUser user = await identityUserService.FindByUsername(model.Username); await events.RaiseAsync(new UserLoginSuccessEvent(user.Username, - user.SubjectId, - user.Username, - clientId: context?.ClientId)); + user.SubjectId, + user.Username, + clientId: context?.ClientId)); // only set explicit expiration here if user chooses "remember me". // otherwise we rely upon expiration configured in cookie middleware. @@ -175,7 +172,7 @@ await events.RaiseAsync(new UserLoginSuccessEvent(user.Username, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; } - + // issue authentication cookie with subject ID and username IdentityServerUser isuser = new IdentityServerUser(user.SubjectId) { diff --git a/IdentityServer/Quickstart/Account/ExternalAccountController.cs b/IdentityServer/Quickstart/Account/ExternalAccountController.cs new file mode 100644 index 00000000..3ba8b157 --- /dev/null +++ b/IdentityServer/Quickstart/Account/ExternalAccountController.cs @@ -0,0 +1,65 @@ +/* +* 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 IdentityServer4; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Models; +using Services.Services; +using System.Linq; +using System.Threading.Tasks; + +namespace IdentityServer.Quickstart.Account +{ + + [Route("[controller]")] + [ApiController] + [Authorize(IdentityServerConstants.LocalApi.PolicyName)] + public class ExternalAccountController : ControllerBase + { + + private readonly IIdentityUserService identityUserService; + + public ExternalAccountController(IIdentityUserService identityUserService) + { + this.identityUserService = identityUserService; + } + + [HttpPut] + public async Task ChangeCredentials() + { + string pass = Request.Headers.FirstOrDefault(x => x.Key == "password") + .Value.FirstOrDefault(); + string email = Request.Headers.FirstOrDefault(x => x.Key == "email") + .Value.FirstOrDefault(); + string subjectId = Request.Headers.FirstOrDefault(x => x.Key == "subjectId") + .Value.FirstOrDefault(); + + IdentityUser identityUser = await identityUserService.FindBySubjectId(subjectId); + identityUser.Email = email; + identityUser.Username = email; + identityUser.Password = LoginHelper.GetHashPassword(pass); + + identityUserService.Update(identityUser); + identityUserService.Save(); + + return Ok(); + } + + } + +} diff --git a/IdentityServer/Quickstart/Account/ExternalConnectToken.cs b/IdentityServer/Quickstart/Account/ExternalConnectToken.cs index 9be578af..03e7a5ad 100644 --- a/IdentityServer/Quickstart/Account/ExternalConnectToken.cs +++ b/IdentityServer/Quickstart/Account/ExternalConnectToken.cs @@ -1,55 +1,76 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 Newtonsoft.Json; namespace IdentityServer.Quickstart.Account { + /// - /// The viewmodel for /connect/token + /// The viewmodel for /connect/token /// public class ExternalConnectToken { + /// - /// Gets or sets the identifier token. + /// Gets or sets the identifier token. /// /// - /// The identifier token. + /// The identifier token. /// [JsonProperty("id_token")] public string IdToken { get; set; } + /// - /// Gets or sets the access token. + /// Gets or sets the access token. /// /// - /// The access token. + /// The access token. /// [JsonProperty("access_token")] public string AccessToken { get; set; } + /// - /// Gets or sets the expires in. + /// Gets or sets the expires in. /// /// - /// The expires in. + /// The expires in. /// [JsonProperty("expires_in")] public int ExpiresIn { get; set; } + /// - /// Gets or sets the type of the token. + /// Gets or sets the type of the token. /// /// - /// The type of the token. + /// The type of the token. /// [JsonProperty("token_type")] public string TokenType { get; set; } + /// - /// Gets or sets the refresh token. + /// Gets or sets the refresh token. /// /// - /// The refresh token. + /// The refresh token. /// [JsonProperty("refresh_token")] public string RefreshToken { get; set; } + } + } diff --git a/IdentityServer/Quickstart/Account/ExternalController.cs b/IdentityServer/Quickstart/Account/ExternalController.cs index 0ecab5ee..c560e63a 100644 --- a/IdentityServer/Quickstart/Account/ExternalController.cs +++ b/IdentityServer/Quickstart/Account/ExternalController.cs @@ -1,16 +1,16 @@ /* * Digital Excellence Copyright (C) 2020 Brend Smits -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU Lesser General Public License as published +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty -* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. -* -* You can find a copy of the GNU Lesser General Public License +* +* You can find a copy of the GNU Lesser General Public License * along with this program, in the LICENSE.md file in the root project directory. * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ @@ -23,10 +23,8 @@ using IdentityServer4.Models; using IdentityServer4.Services; using IdentityServer4.Stores; -using IdentityServer4.Test; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -44,16 +42,18 @@ namespace IdentityServer { + [SecurityHeaders] [AllowAnonymous] public class ExternalController : Controller { + private readonly IClientStore clientStore; + private readonly Config config; private readonly IEventService events; + private readonly IIdentityUserService identityUserService; private readonly IIdentityServerInteractionService interaction; private readonly ILogger logger; - private readonly Config config; - private readonly IIdentityUserService identityUserService; public ExternalController( IIdentityServerInteractionService interaction, @@ -86,16 +86,17 @@ public IActionResult Challenge(string provider, string returnUrl) // user might have clicked on a malicious link - should be logged throw new Exception("invalid return URL"); } - // start challenge and roundtrip the return URL and scheme + + // start challenge and roundtrip the return URL and scheme AuthenticationProperties props = new AuthenticationProperties - { - RedirectUri = Url.Action(nameof(Callback)), - Items = + { + RedirectUri = Url.Action(nameof(Callback)), + Items = { {"returnUrl", returnUrl}, {"scheme", provider} } - }; + }; HttpContext.Response.Cookies.Append("returnUrl", returnUrl); @@ -103,16 +104,17 @@ public IActionResult Challenge(string provider, string returnUrl) } /// - /// The callback endpoint for the fontys single sign on. + /// The callback endpoint for the fontys single sign on. /// /// The authorization code. /// The authorization state. /// State of the session. /// Redirection to the callback url. /// - /// The FHICT didn't return a correct response. Is the FHICT server accessible? - new Exception("Content:\n" + response.Content + "\n\nError:\n" + response.ErrorMessage, response.ErrorException) - /// or - /// Content:\n" + response.Content + "\n\nError:\n" + response.ErrorMessage + /// The FHICT didn't return a correct response. Is the FHICT server accessible? - new Exception("Content:\n" + + /// response.Content + "\n\nError:\n" + response.ErrorMessage, response.ErrorException) + /// or + /// Content:\n" + response.Content + "\n\nError:\n" + response.ErrorMessage /// [HttpPost("/external/callback/fhict")] public async Task Callback(string code, string state, string session_state) @@ -142,23 +144,29 @@ public async Task Callback(string code, string state, string sess if(string.IsNullOrWhiteSpace(fhictToken.AccessToken)) { - throw new Exception("The FHICT didn't return a correct response. Is the FHICT server accessible?", new Exception("Content:\n" + response.Content + "\n\nError:\n" + response.ErrorMessage, response.ErrorException)); + throw new Exception("The FHICT didn't return a correct response. Is the FHICT server accessible?", + new Exception( + "Content:\n" + response.Content + "\n\nError:\n" + response.ErrorMessage, + response.ErrorException)); } JwtSecurityToken jwt = new JwtSecurityToken(fhictToken.AccessToken); - string idp = (string) jwt.Payload.FirstOrDefault(c => c.Key.Equals("idp")).Value; - string iss = (string) jwt.Payload.FirstOrDefault(c => c.Key.Equals("iss")).Value; + string idp = (string) jwt.Payload.FirstOrDefault(c => c.Key.Equals("idp")) + .Value; + string iss = (string) jwt.Payload.FirstOrDefault(c => c.Key.Equals("iss")) + .Value; ExternalResult result = new ExternalResult - { - Schema = iss, - Claims = jwt.Claims, - ReturnUrl = returnUrl, - IdToken = fhictToken.IdToken - }; + { + Schema = iss, + Claims = jwt.Claims, + ReturnUrl = returnUrl, + IdToken = fhictToken.IdToken + }; // lookup our user and external provider info - (IdentityUser user, string provider, string providerUserId, IEnumerable claims) = await FindUserFromExternalProvider(result); + (IdentityUser user, string provider, string providerUserId, IEnumerable claims) = + await FindUserFromExternalProvider(result); if(user == null) { @@ -168,23 +176,24 @@ public async Task Callback(string code, string state, string sess RestRequest informationRequest = new RestRequest(Method.GET); informationRequest.AddHeader("Authorization", $"Bearer {fhictToken.AccessToken}"); IRestResponse informationResponse = informationClient.Execute(informationRequest); - ExternalUserInfo userinfo = JsonConvert.DeserializeObject(informationResponse.Content); + ExternalUserInfo userinfo = + JsonConvert.DeserializeObject(informationResponse.Content); List claimsList = claims.ToList(); claimsList.Add(new Claim("email", userinfo.PreferredUsername)); claimsList.Add(new Claim("idp", idp)); claimsList.Add(new Claim("name", userinfo.Name)); - IdentityUser toInsertuser = new IdentityUser() - { - ProviderId = provider, - ExternalSubjectId = providerUserId, - Email = userinfo.Email, - Firstname = userinfo.GivenName, - Lastname = userinfo.FamilyName, - Name = userinfo.Name, - Username = userinfo.PreferredUsername, - ExternalProfileUrl = userinfo.Profile - }; + IdentityUser toInsertuser = new IdentityUser + { + ProviderId = provider, + ExternalSubjectId = providerUserId, + Email = userinfo.Email, + Firstname = userinfo.GivenName, + Lastname = userinfo.FamilyName, + Name = userinfo.Name, + Username = userinfo.PreferredUsername, + ExternalProfileUrl = userinfo.Profile + }; // simply auto-provisions new external user user = await identityUserService.AutoProvisionUser(toInsertuser); @@ -199,28 +208,33 @@ public async Task Callback(string code, string state, string sess // issue authentication cookie for user IdentityServerUser isuser = new IdentityServerUser(user.SubjectId) - { - DisplayName = user.Name, - IdentityProvider = provider, - AdditionalClaims = additionalLocalClaims - }; + { + DisplayName = user.Name, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; - await HttpContext.SignInAsync(isuser, localSignInProps).ConfigureAwait(false); + await HttpContext.SignInAsync(isuser, localSignInProps) + .ConfigureAwait(false); // delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme).ConfigureAwait(false); + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme) + .ConfigureAwait(false); // check if external login is in the context of an OIDC request - AuthorizationRequest context = await interaction.GetAuthorizationContextAsync(returnUrl).ConfigureAwait(false); + AuthorizationRequest context = await interaction.GetAuthorizationContextAsync(returnUrl) + .ConfigureAwait(false); await events.RaiseAsync(new UserLoginSuccessEvent(provider, - providerUserId, - user.SubjectId, - user.Username, - true, - context?.ClientId)).ConfigureAwait(false); + providerUserId, + user.SubjectId, + user.Username, + true, + context?.ClientId)) + .ConfigureAwait(false); if(context != null) { - if(await clientStore.IsPkceClientAsync(context.ClientId).ConfigureAwait(false)) + if(await clientStore.IsPkceClientAsync(context.ClientId) + .ConfigureAwait(false)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. @@ -232,7 +246,7 @@ await events.RaiseAsync(new UserLoginSuccessEvent(provider, } /// - /// Default callback function. + /// Default callback function. /// /// Redirection to the callback url. /// External authentication error @@ -241,7 +255,8 @@ public async Task DefaultCallback() { // read external identity from the temporary cookie AuthenticateResult result = - await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme).ConfigureAwait(false); + await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme) + .ConfigureAwait(false); if(result?.Succeeded != true) { throw new Exception("External authentication error"); @@ -274,32 +289,37 @@ public async Task DefaultCallback() // issue authentication cookie for user IdentityServerUser isuser = new IdentityServerUser(user.SubjectId) - { - DisplayName = user.Username, - IdentityProvider = provider, - AdditionalClaims = additionalLocalClaims - }; + { + DisplayName = user.Username, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; - await HttpContext.SignInAsync(isuser, localSignInProps).ConfigureAwait(false); + await HttpContext.SignInAsync(isuser, localSignInProps) + .ConfigureAwait(false); // delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme).ConfigureAwait(false); + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme) + .ConfigureAwait(false); // retrieve return URL string returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; // check if external login is in the context of an OIDC request - AuthorizationRequest context = await interaction.GetAuthorizationContextAsync(returnUrl).ConfigureAwait(false); + AuthorizationRequest context = await interaction.GetAuthorizationContextAsync(returnUrl) + .ConfigureAwait(false); await events.RaiseAsync(new UserLoginSuccessEvent(provider, - providerUserId, - user.SubjectId, - user.Username, - true, - context?.ClientId)).ConfigureAwait(false); + providerUserId, + user.SubjectId, + user.Username, + true, + context?.ClientId)) + .ConfigureAwait(false); if(context != null) { - if(await clientStore.IsPkceClientAsync(context.ClientId).ConfigureAwait(false)) + if(await clientStore.IsPkceClientAsync(context.ClientId) + .ConfigureAwait(false)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. @@ -309,13 +329,15 @@ await events.RaiseAsync(new UserLoginSuccessEvent(provider, return Redirect(returnUrl); } + /// - /// Finds the user from external provider. + /// Finds the user from external provider. /// /// The ExternalResult information. /// The user from the identity server, the external provider uri, The external user id, the claims /// Unknown userid - private async Task<(IdentityUser user, string provider, string providerUserId, IEnumerable claims)> FindUserFromExternalProvider(ExternalResult result) + private async Task<(IdentityUser user, string provider, string providerUserId, IEnumerable claims)> + FindUserFromExternalProvider(ExternalResult result) { // try to determine the unique id of the external user (issued by the provider) // the most common claim type for that are the sub claim and the NameIdentifier @@ -338,7 +360,7 @@ await events.RaiseAsync(new UserLoginSuccessEvent(provider, } /// - /// Processes the login callback for oidc. + /// Processes the login callback for oidc. /// /// The result model got from the external identity server. /// The extra claims our identity server can add. @@ -369,5 +391,7 @@ private void ProcessLoginCallbackForOidc(ExternalResult externalResult, }); } } + } + } diff --git a/IdentityServer/Quickstart/Account/ExternalResult.cs b/IdentityServer/Quickstart/Account/ExternalResult.cs index abd4b801..cecccb33 100644 --- a/IdentityServer/Quickstart/Account/ExternalResult.cs +++ b/IdentityServer/Quickstart/Account/ExternalResult.cs @@ -1,43 +1,64 @@ -using System; +/* +* Digital Excellence Copyright (C) 2020 Brend Smits +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published +* by the Free Software Foundation version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You can find a copy of the GNU Lesser General Public License +* along with this program, in the LICENSE.md file in the root project directory. +* If not, see https://www.gnu.org/licenses/lgpl-3.0.txt +*/ + using System.Collections.Generic; -using System.Linq; using System.Security.Claims; -using System.Threading.Tasks; namespace IdentityServer.Quickstart.Account { + /// - /// The ExternalResult model, this contains all the information needed to authenticate an external user. + /// The ExternalResult model, this contains all the information needed to authenticate an external user. /// public class ExternalResult { + /// - /// Gets or sets the return URL. + /// Gets or sets the return URL. /// /// - /// The return URL. + /// The return URL. /// public string ReturnUrl { get; set; } + /// - /// Gets or sets the claims. + /// Gets or sets the claims. /// /// - /// The claims. + /// The claims. /// public IEnumerable Claims { get; set; } + /// - /// Gets or sets the schema. + /// Gets or sets the schema. /// /// - /// The schema. + /// The schema. /// public string Schema { get; set; } + /// - /// Gets or sets the identifier token. + /// Gets or sets the identifier token. /// /// - /// The identifier token. + /// The identifier token. /// public string IdToken { get; set; } + } + } diff --git a/IdentityServer/Quickstart/Account/ExternalUserInfo.cs b/IdentityServer/Quickstart/Account/ExternalUserInfo.cs index bf090d61..01372e5d 100644 --- a/IdentityServer/Quickstart/Account/ExternalUserInfo.cs +++ b/IdentityServer/Quickstart/Account/ExternalUserInfo.cs @@ -1,88 +1,113 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +/* +* 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 Newtonsoft.Json; namespace IdentityServer.Quickstart.Account { + /// - /// The viewmodel for /connect/userinfo + /// The viewmodel for /connect/userinfo /// public class ExternalUserInfo { + /// - /// Gets or sets the subject id. - /// This is the token identifier + /// Gets or sets the subject id. + /// This is the token identifier /// /// - /// The sub. + /// The sub. /// [JsonProperty("sub")] public string sub { get; set; } + /// - /// Gets or sets the name. + /// Gets or sets the name. /// /// - /// The name. + /// The name. /// [JsonProperty("name")] public string Name { get; set; } + /// - /// Gets or sets the name of the family. + /// Gets or sets the name of the family. /// /// - /// The name of the family. + /// The name of the family. /// [JsonProperty("family_name")] public string FamilyName { get; set; } + /// - /// Gets or sets the given name of the external user. + /// Gets or sets the given name of the external user. /// /// - /// The given name of the external user. + /// The given name of the external user. /// [JsonProperty("given_name")] public string GivenName { get; set; } + /// - /// Gets or sets the preferred username. + /// Gets or sets the preferred username. /// /// - /// The preferred username. + /// The preferred username. /// [JsonProperty("preferred_username")] public string PreferredUsername { get; set; } + /// - /// Gets or sets the link to the external user profile. + /// Gets or sets the link to the external user profile. /// /// - /// The profile. + /// The profile. /// [JsonProperty("profile")] public string Profile { get; set; } + /// - /// Gets or sets the updated at. + /// Gets or sets the updated at. /// /// - /// The updated at. + /// The updated at. /// [JsonProperty("updated_at")] public string UpdatedAt { get; set; } + /// - /// Gets or sets the email. + /// Gets or sets the email. /// /// - /// The email. + /// The email. /// [JsonProperty("email")] public string Email { get; set; } + /// - /// Gets or sets the role. + /// Gets or sets the role. /// /// - /// The role. + /// The role. /// [JsonProperty("role")] public string[] Role { get; set; } + } + } diff --git a/IdentityServer/Quickstart/Consent/ConsentController.cs b/IdentityServer/Quickstart/Consent/ConsentController.cs index ea6752ba..47fea36e 100644 --- a/IdentityServer/Quickstart/Consent/ConsentController.cs +++ b/IdentityServer/Quickstart/Consent/ConsentController.cs @@ -60,7 +60,7 @@ public ConsentController( } /// - /// Indexes the specified return URL. + /// Indexes the specified return URL. /// /// The return URL. /// The index view. @@ -154,10 +154,10 @@ await events.RaiseAsync( // emit event await events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), - request.ClientId, - request.ScopesRequested, - grantedConsent.ScopesConsented, - grantedConsent.RememberConsent)); + request.ClientId, + request.ScopesRequested, + grantedConsent.ScopesConsented, + grantedConsent.RememberConsent)); } else { result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; @@ -200,7 +200,7 @@ private async Task BuildViewModelAsync(string returnUrl, Conse return CreateConsentViewModel(model, returnUrl, request, client, resources); } logger.LogError("No scopes matching: {0}", - request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); + request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); } else { logger.LogError("Invalid client id: {0}", request.ClientId); @@ -250,8 +250,8 @@ private ConsentViewModel CreateConsentViewModel( { GetOfflineAccessScope( vm.ScopesConsented.Contains( - IdentityServerConstants - .StandardScopes.OfflineAccess) || + IdentityServerConstants.StandardScopes + .OfflineAccess) || model == null) }); } diff --git a/IdentityServer/Quickstart/Device/DeviceController.cs b/IdentityServer/Quickstart/Device/DeviceController.cs index a2b4d9cd..b41a28bb 100644 --- a/IdentityServer/Quickstart/Device/DeviceController.cs +++ b/IdentityServer/Quickstart/Device/DeviceController.cs @@ -131,10 +131,10 @@ await events.RaiseAsync( // emit event await events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), - request.ClientId, - request.ScopesRequested, - grantedConsent.ScopesConsented, - grantedConsent.RememberConsent)); + request.ClientId, + request.ScopesRequested, + grantedConsent.ScopesConsented, + grantedConsent.RememberConsent)); } else { result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; @@ -179,7 +179,7 @@ private async Task BuildViewModelAsync( return CreateConsentViewModel(userCode, model, client, resources); } logger.LogError("No scopes matching: {0}", - request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); + request.ScopesRequested.Aggregate((x, y) => x + ", " + y)); } else { logger.LogError("Invalid client id: {0}", request.ClientId); @@ -225,8 +225,8 @@ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, { GetOfflineAccessScope( vm.ScopesConsented.Contains( - IdentityServerConstants - .StandardScopes.OfflineAccess) || + IdentityServerConstants.StandardScopes + .OfflineAccess) || model == null) }); } diff --git a/IdentityServer/Quickstart/ProfileService.cs b/IdentityServer/Quickstart/ProfileService.cs index bec89fba..200c9409 100644 --- a/IdentityServer/Quickstart/ProfileService.cs +++ b/IdentityServer/Quickstart/ProfileService.cs @@ -1,26 +1,41 @@ +/* +* 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 IdentityModel; -using IdentityServer.Quickstart; -using IdentityServer.Quickstart.Account; using IdentityServer4.Models; using IdentityServer4.Services; using Models; -using Repositories; using Serilog; using Services.Services; using System; -using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace IdentityServer.Quickstart { + /// - /// The Custom profile service. + /// The Custom profile service. /// /// public class ProfileService : IProfileService { + //services private readonly IIdentityUserService userService; @@ -30,7 +45,8 @@ public ProfileService(IIdentityUserService userService) } /// - /// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo endpoint) + /// This method is called whenever claims about the user are requested (e.g. during token creation or via the userinfo + /// endpoint) /// /// The request context. public async Task GetProfileDataAsync(ProfileDataRequestContext context) @@ -49,19 +65,20 @@ public async Task GetProfileDataAsync(ProfileDataRequestContext context) { Claim[] claims = GetUserClaims(user); - context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList(); + context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)) + .ToList(); } } - } catch(Exception ex) { - Log.Logger.Error("The exception stacktrace: {0}",ex.StackTrace); + Log.Logger.Error("The exception stacktrace: {0}", ex.StackTrace); } } /// - /// This method gets called whenever identity server needs to determine if the user is valid or active (e.g. if the user's account has been deactivated since they logged in). - /// (e.g. during token issuance or validation). + /// This method gets called whenever identity server needs to determine if the user is valid or active (e.g. if the + /// user's account has been deactivated since they logged in). + /// (e.g. during token issuance or validation). /// /// The request context. public async Task IsActiveAsync(IsActiveContext context) @@ -89,24 +106,25 @@ public async Task IsActiveAsync(IsActiveContext context) } /// - /// This function returns the claims that will be put in the /connect/userinfo. + /// This function returns the claims that will be put in the /connect/userinfo. /// /// The current user object. /// The list of created Claims. public static Claim[] GetUserClaims(IdentityUser user) { - return new Claim[] + return new[] { - new Claim("sub", user.SubjectId ?? ""), - new Claim(type: JwtClaimTypes.Name, - (!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) - ? (user.Firstname + " " + user.Lastname) - : ""), - new Claim(type: JwtClaimTypes.GivenName, user.Firstname ?? ""), - new Claim(type: JwtClaimTypes.FamilyName, user.Lastname ?? ""), - new Claim(type: JwtClaimTypes.Email, user.Email ?? ""), - + new Claim("sub", user.SubjectId ?? ""), new Claim(JwtClaimTypes.Name, + !string.IsNullOrEmpty(user.Firstname) && + !string.IsNullOrEmpty(user.Lastname) + ? user.Firstname + " " + user.Lastname + : ""), + new Claim(JwtClaimTypes.GivenName, user.Firstname ?? ""), + new Claim(JwtClaimTypes.FamilyName, user.Lastname ?? ""), + new Claim(JwtClaimTypes.Email, user.Email ?? "") }; } + } + } diff --git a/IdentityServer/Quickstart/TestUsers.cs b/IdentityServer/Quickstart/TestUsers.cs index 16755b2a..d0abaa51 100644 --- a/IdentityServer/Quickstart/TestUsers.cs +++ b/IdentityServer/Quickstart/TestUsers.cs @@ -15,100 +15,104 @@ * If not, see https://www.gnu.org/licenses/lgpl-3.0.txt */ -using IdentityModel; -using IdentityServer.Quickstart.Account; -using IdentityServer4; -using IdentityServer4.Test; using Models; -using Models.Defaults; using Serilog; -using Serilog.Core; using Services.Services; using System; using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; using System.Text; namespace IdentityServer { + public static class TestUsers { + /// - /// Gets the default users. + /// Gets the default users. /// /// The list of default identity users. public static List GetDefaultIdentityUsers() { - List users = new List() - { - new IdentityUser - { - SubjectId = "818727", - Username = "alice", - Password = LoginHelper.GetHashPassword("alice"), - Name = "Alice Smith", - Firstname = "Alice", - Lastname = "Smith", - Email = "AliceSmith@email.com" - }, - new IdentityUser - { - SubjectId = "88421113", - Username = "bob", - Password = LoginHelper.GetHashPassword("bob"), - Name = "Bob Smith", - Firstname = "Bob", - Lastname = "Smith", - Email = "BobSmith@email.com" - }, - new IdentityUser - { - SubjectId = "985632147", - Username = "jerry", - Password = LoginHelper.GetHashPassword("jerry"), - Name = "jerry Smith", - Firstname = "jerry", - Lastname = "Smith", - Email = "jerrySmith@email.com" - }, - new IdentityUser - { - SubjectId = "147852369", - Username = "berry", - Password = LoginHelper.GetHashPassword("berry"), - Name = "berry Smith", - Firstname = "berry", - Lastname = "Smith", - Email = "berrySmith@email.com" - }, - new IdentityUser - { - SubjectId = "14785236923", - Username = "dex", - Password = LoginHelper.GetHashPassword("dex"), - Name = "DeX User", - Firstname = "DeX", - Lastname = "User", - Email = "dex@dex.software" - }, - new IdentityUser - { - SubjectId = "954654861", - Username = "john", - Password = LoginHelper.GetHashPassword("john"), - Name = "John Smith", - Firstname = "John", - Lastname = "Smith", - Email = "johnSmith@email.com" - }, - }; + List users = new List + { + new IdentityUser + { + SubjectId = "818727", + Username = "alice", + Password = LoginHelper.GetHashPassword("alice"), + Name = "Alice Smith", + Firstname = "Alice", + Lastname = "Smith", + Email = "AliceSmith@email.com" + }, + new IdentityUser + { + SubjectId = "88421113", + Username = "bob", + Password = LoginHelper.GetHashPassword("bob"), + Name = "Bob Smith", + Firstname = "Bob", + Lastname = "Smith", + Email = "BobSmith@email.com" + }, + new IdentityUser + { + SubjectId = "985632147", + Username = "jerry", + Password = LoginHelper.GetHashPassword("jerry"), + Name = "jerry Smith", + Firstname = "jerry", + Lastname = "Smith", + Email = "jerrySmith@email.com" + }, + new IdentityUser + { + SubjectId = "147852369", + Username = "berry", + Password = LoginHelper.GetHashPassword("berry"), + Name = "berry Smith", + Firstname = "berry", + Lastname = "Smith", + Email = "berrySmith@email.com" + }, + new IdentityUser + { + SubjectId = "14785236923", + Username = "dex", + Password = LoginHelper.GetHashPassword("dex"), + Name = "DeX User", + Firstname = "DeX", + Lastname = "User", + Email = "dex@dex.software" + }, + new IdentityUser + { + SubjectId = "954654861", + Username = "john", + Password = LoginHelper.GetHashPassword("john"), + Name = "John Smith", + Firstname = "John", + Lastname = "Smith", + Email = "johnSmith@email.com" + }, + new IdentityUser + { + SubjectId = "123456789", + Username = "AlumniTest", + Password = LoginHelper.GetHashPassword("AlumniTest"), + Name = "Alumni Test", + Firstname = "Alumni", + Lastname = "Test", + Email = "alumnirole@email.com" + } + }; return users; } /// - /// Creates a password for a test user and logs it into the console. + /// Creates a password for a test user and logs it into the console. /// /// The hashed password/. public static string CreateTestUserPassword(string userName) @@ -125,12 +129,11 @@ public static string CreateTestUserPassword(string userName) } /// - /// Generates a secure password. + /// Generates a secure password. /// /// The generated password. private static string GenerateSecurePassword() { - const int requiredLength = 20; bool requireNonAlphanumeric = true; @@ -153,20 +156,17 @@ private static string GenerateSecurePassword() requireLowercase = false; else if(char.IsUpper(c)) requireUppercase = false; - else if(!char.IsLetterOrDigit(c)) - requireNonAlphanumeric = false; + else if(!char.IsLetterOrDigit(c)) requireNonAlphanumeric = false; } - if(requireNonAlphanumeric) - password.Append((char) random.Next(33, 48)); - if(requireDigit) - password.Append((char) random.Next(48, 58)); - if(requireLowercase) - password.Append((char) random.Next(97, 123)); - if(requireUppercase) - password.Append((char) random.Next(65, 91)); + if(requireNonAlphanumeric) password.Append((char) random.Next(33, 48)); + if(requireDigit) password.Append((char) random.Next(48, 58)); + if(requireLowercase) password.Append((char) random.Next(97, 123)); + if(requireUppercase) password.Append((char) random.Next(65, 91)); return password.ToString(); } + } + } diff --git a/IdentityServer/Startup.cs b/IdentityServer/Startup.cs index c5800d0b..b7ea960c 100644 --- a/IdentityServer/Startup.cs +++ b/IdentityServer/Startup.cs @@ -29,27 +29,23 @@ using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Models; -using Repositories; -using Services.Services; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - - - - namespace IdentityServer { + /// /// Startup file for Identity Server /// public class Startup { + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration from appsettings. /// The environment. @@ -72,15 +68,15 @@ public Startup(IConfiguration configuration, IWebHostEnvironment environment) public Config Config { get; } /// - /// Gets the environment. + /// Gets the environment. /// /// - /// The environment. + /// The environment. /// public IWebHostEnvironment Environment { get; } /// - /// Configures the services. + /// Configures the services. /// /// The services. public void ConfigureServices(IServiceCollection services) @@ -91,9 +87,7 @@ public void ConfigureServices(IServiceCollection services) sqlOptions => sqlOptions.EnableRetryOnFailure(50, TimeSpan.FromSeconds(30), null)); }); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddServicesAndRepositories(); // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache. services.AddOidcStateDataFormatterCache(); @@ -119,55 +113,62 @@ public void ConfigureServices(IServiceCollection services) builder.AddInMemoryClients(IdentityConfig.Clients(Config)); builder.Services.AddTransient(); services.AddSingleton(Config); + services.AddLocalApiAuthentication(); // sets the authentication schema. services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; - options.DefaultSignInScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; - options.DefaultChallengeScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; - }) - // Adds Fontys Single Sign On authentication. - .AddOpenIdConnect("FHICT", "Fontys", options => { - options.ClientId = Config.FfhictOIDC.ClientId; - options.ClientSecret = Config.FfhictOIDC.ClientSecret; - options.Authority = Config.FfhictOIDC.Authority; - options.ResponseType = "code"; - options.Scope.Clear(); - - string[] scopes = Config.FfhictOIDC.Scopes.Split(" "); - foreach(string scope in scopes) - { - options.Scope.Add(scope); - } - - // Set this flow to get the refresh token. - // options.Scope.Add("offline_access"); - - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - - // This sets the redirect uri, this is needed because the blackbox implementation does not implement fontys SSO. - options.Events.OnRedirectToIdentityProvider = async n => - { - n.ProtocolMessage.RedirectUri = Config.FfhictOIDC.RedirectUri; - await Task.FromResult(0); - }; - } - // Add jwt validation this is so that the DGS can authenticate. - ).AddJwtBearer(o => + options.DefaultAuthenticateScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; + options.DefaultSignInScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; + options.DefaultChallengeScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme; + }) + + // Adds Fontys Single Sign On authentication. + .AddOpenIdConnect("FHICT", + "Fontys", + options => + { + options.ClientId = Config.FfhictOIDC.ClientId; + options.ClientSecret = Config.FfhictOIDC.ClientSecret; + options.Authority = Config.FfhictOIDC.Authority; + options.ResponseType = "code"; + options.Scope.Clear(); + + string[] scopes = Config.FfhictOIDC.Scopes.Split(" "); + foreach(string scope in scopes) + { + options.Scope.Add(scope); + } + + // Set this flow to get the refresh token. + // options.Scope.Add("offline_access"); + + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + // This sets the redirect uri, this is needed because the blackbox implementation does not implement fontys SSO. + options.Events.OnRedirectToIdentityProvider = async n => + { + n.ProtocolMessage.RedirectUri = Config.FfhictOIDC.RedirectUri; + await Task.FromResult(0); + }; + } + + // Add jwt validation this is so that the DGS can authenticate. + ) + .AddJwtBearer(o => { o.SaveToken = true; o.Authority = Config.Self.JwtAuthority; o.RequireHttpsMetadata = false; - o.TokenValidationParameters = new TokenValidationParameters() - { - ValidateActor = false, - ValidateAudience = false, - NameClaimType = "name", - RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" - }; + o.TokenValidationParameters = new TokenValidationParameters + { + ValidateActor = false, + ValidateAudience = false, + NameClaimType = "name", + RoleClaimType = + "http://schemas.microsoft.com/ws/2008/06/identity/claims/role" + }; }) .AddCookie(); @@ -178,11 +179,9 @@ public void ConfigureServices(IServiceCollection services) { X509Store certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find( - X509FindType.FindByIssuerName, + X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByIssuerName, "Let's Encrypt Authority X3", - false - ); + false); if(certCollection.Count > 0) { X509Certificate2 certificate = certCollection[0]; @@ -204,7 +203,7 @@ public void ConfigureServices(IServiceCollection services) } /// - /// Configures the specified application. + /// Configures the specified application. /// /// The application builder instance. /// The environmental variable. @@ -225,7 +224,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } /// - /// Updates the database. + /// Updates the database. /// /// The application. /// The environmental variable. @@ -237,7 +236,10 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment using IdentityDbContext context = serviceScope.ServiceProvider.GetService(); context.Database.Migrate(); List identityUsers = TestUsers.GetDefaultIdentityUsers(); - foreach(IdentityUser identityUser in identityUsers.Where(identityUser => !context.IdentityUser.Any(e => e.SubjectId == identityUser.SubjectId))) + foreach(IdentityUser identityUser in identityUsers.Where(identityUser => + !context.IdentityUser.Any( + e => e.SubjectId == + identityUser.SubjectId))) { if(env.IsProduction()) { @@ -247,5 +249,7 @@ private static void UpdateDatabase(IApplicationBuilder app, IWebHostEnvironment } context.SaveChanges(); } + } + } diff --git a/IdentityServer/appsettings.Development.json b/IdentityServer/appsettings.Development.json index 795da23b..80deb80c 100644 --- a/IdentityServer/appsettings.Development.json +++ b/IdentityServer/appsettings.Development.json @@ -1,6 +1,7 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=(LocalDb)\\MSSQLLocalDB;Database=Dex;Trusted_Connection=True;MultipleActiveResultSets=true" + "DefaultConnection": + "Server=(LocalDb)\\MSSQLLocalDB;Database=Dex;Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { @@ -38,7 +39,16 @@ "swagger": { "RedirectUrisSwagger": "https://localhost:5001/oauth2-redirect.html", "PostLogoutUrisSwagger": "https://localhost:5001" + }, + "JobScheduler": { + "ClientId": "dex-jobscheduler", + "ClientSecret": "fGe34rE6F53dsT" + }, + "ApiAuthentication": { + "ClientId": "dex-api", + "ClientSecret": "kP+O2lU<.5Avx@MMe4b||_^l" } + }, "AllowedHosts": "*" } diff --git a/IdentityServer/appsettings.json b/IdentityServer/appsettings.json index 4a9f51f1..33091418 100644 --- a/IdentityServer/appsettings.json +++ b/IdentityServer/appsettings.json @@ -13,7 +13,7 @@ "Self": { "JwtAuthority": "", "DeleteTokenLifeTimeInDays": 90, - "PublicOrigin": "" + "PublicOrigin": "" }, "Api": { "DeXApiUrl": "", @@ -38,6 +38,14 @@ "swagger": { "RedirectUrisSwagger": "", "PostLogoutUrisSwagger": "" + }, + "JobScheduler": { + "ClientId": "", + "ClientSecret": "" + }, + "ApiAuthentication": { + "ClientId": "", + "ClientSecret": "" } }, "AllowedHosts": "*" diff --git a/IdentityServer/wwwroot/lib/jquery/jquery.min.js b/IdentityServer/wwwroot/lib/jquery/jquery.min.js index c50fe096..e6030745 100644 --- a/IdentityServer/wwwroot/lib/jquery/jquery.min.js +++ b/IdentityServer/wwwroot/lib/jquery/jquery.min.js @@ -1,4 +1,4 @@ !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("