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