diff --git a/API/Controllers/UserController.cs b/API/Controllers/UserController.cs index 99b659d1..92dc9af3 100644 --- a/API/Controllers/UserController.cs +++ b/API/Controllers/UserController.cs @@ -172,7 +172,9 @@ public async Task GetUser(int userId) [Authorize] [ProducesResponseType(typeof(UserOutput), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(ProblemDetails), (int) HttpStatusCode.NotFound)] - public async Task GetUserProjects() + public async Task GetUserProjects( + [FromQuery] ProjectFilterParamsInput projectFilterParamsResource + ) { User user = await HttpContext.GetContextUser(userService).ConfigureAwait(false); if(user == null) @@ -186,9 +188,23 @@ public async Task GetUserProjects() return NotFound(problem); } - IEnumerable userProjects = await projectService.GetUserProjects(user.Id); + ProjectFilterParams projectFilterParams = + mapper.Map(projectFilterParamsResource); - return Ok(mapper.Map, IEnumerable>(userProjects)); + IEnumerable userProjects = await projectService.GetUserProjects(user.Id, projectFilterParams); + IEnumerable results = + mapper.Map, IEnumerable>(userProjects); + + ProjectResultsInput resultsResource = new ProjectResultsInput + { + Results = results.ToArray(), + Count = results.Count(), + TotalCount = await projectService.ProjectsCount(projectFilterParams, user.Id), + Page = projectFilterParams.Page, + TotalPages = await projectService.GetProjectsTotalPages(projectFilterParams, user.Id) + }; + + return Ok(resultsResource); } /// diff --git a/Postman/dex.postman_collection.json b/Postman/dex.postman_collection.json index ab0ee7f0..b4ac0415 100644 --- a/Postman/dex.postman_collection.json +++ b/Postman/dex.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "45ee041e-e0a8-4433-bdd8-3517c9bf6a93", + "_postman_id": "cfd740d0-2cb3-4a6d-9ca6-25db86e7c9c7", "name": "DEV", "description": "Testing Digital Excellence API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" @@ -1640,6 +1640,76 @@ } ] }, + { + "name": "UserProjects", + "item": [ + { + "name": "UserProject-GetProject-Self-Administrator", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var projectName = pm.environment.get(\"projectName\");\r", + "\r", + "var jsonData = pm.response.json();\r", + "\r", + "eval(pm.environment.get(\"commonTests\"))();\r", + "\r", + "pm.test(\"Status code is 200\", function(){\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response must be valid and have a json body\", function() {\r", + " pm.response.to.be.ok;\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "pm.test(\"Project pagination displays correct amount of projects and all project count matches\", function() {\r", + " pm.expect(jsonData.results.length).to.eql(1);\r", + " pm.expect(jsonData.totalCount).to.eql(3);\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "IdentityId", + "value": "{{administratorUserIdentityId}}", + "type": "text" + } + ], + "url": { + "raw": "{{apiUrl}}/api/user/projects?page=1&amountOnPage=1", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "user", + "projects" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "amountOnPage", + "value": "1" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "Institution", "item": [ @@ -13067,7 +13137,8 @@ "});", "", "pm.test(\"Project count matches: 6\", function () {", - " pm.expect(jsonData.length).to.eql(6);", + " pm.expect(jsonData.results.length).to.eql(1);", + " pm.expect(jsonData.totalCount).to.eql(6);", "});" ], "type": "text/javascript" @@ -13084,7 +13155,7 @@ } ], "url": { - "raw": "{{apiUrl}}/api/user/projects", + "raw": "{{apiUrl}}/api/user/projects?page=1&amountOnPage=1", "host": [ "{{apiUrl}}" ], @@ -13092,6 +13163,16 @@ "api", "user", "projects" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "amountOnPage", + "value": "1" + } ] } }, @@ -29201,6 +29282,76 @@ } ] }, + { + "name": "UserProjects", + "item": [ + { + "name": "UserProject-GetProject-Self-Alumni", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "var projectName = pm.environment.get(\"projectName\");\r", + "\r", + "var jsonData = pm.response.json();\r", + "\r", + "eval(pm.environment.get(\"commonTests\"))();\r", + "\r", + "pm.test(\"Status code is 200\", function(){\r", + " pm.response.to.have.status(200);\r", + "});\r", + "\r", + "pm.test(\"Response must be valid and have a json body\", function() {\r", + " pm.response.to.be.ok;\r", + " pm.response.to.be.withBody;\r", + " pm.response.to.be.json;\r", + "});\r", + "\r", + "pm.test(\"Project pagination displays correct amount of projects and all project count matches\", function() {\r", + " pm.expect(jsonData.results.length).to.eql(1);\r", + " pm.expect(jsonData.totalCount).to.eql(3);\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "IdentityId", + "value": "{{alumniIdentityId}}", + "type": "text" + } + ], + "url": { + "raw": "{{apiUrl}}/api/user/projects?page=1&amountOnPage=1", + "host": [ + "{{apiUrl}}" + ], + "path": [ + "api", + "user", + "projects" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "amountOnPage", + "value": "1" + } + ] + } + }, + "response": [] + } + ] + }, { "name": "Project", "item": [ diff --git a/Repositories/ProjectRepository.cs b/Repositories/ProjectRepository.cs index 48ede9b4..85d59313 100644 --- a/Repositories/ProjectRepository.cs +++ b/Repositories/ProjectRepository.cs @@ -69,8 +69,9 @@ Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync( /// /// /// The categories parameter represents the categories the project needs to have + /// The user id whoms projects need to be retrieved /// number of projects found - Task CountAsync(bool? highlighted = null, ICollection categories = null); + Task CountAsync(bool? highlighted = null, ICollection categories = null, int? userId = null); /// /// This interface method searches the database for projects matching the search query and parameters. @@ -91,6 +92,7 @@ Task> SearchAsync( bool orderByAsc = true, bool? highlighted = null, ICollection categories = null + ); /// @@ -122,8 +124,20 @@ Task> SearchAsync( /// Get the user projects. /// /// The id of the user whoms projects need to be retrieved + /// The skip parameter represents the number of projects to skip. + /// The take parameter represents the number of projects to return. + /// The order by parameter represents the way how to order the projects. + /// The order by asc parameters represents the order direction (True: asc, False: desc) + /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// A enumerable of the users projects - Task> GetUserProjects(int userId); + Task> GetUserProjects(int userId, + int? skip = null, + int? take = null, + Expression> orderBy = null, + bool orderByAsc = true, + bool? highlighted = null, + ICollection categories = null); Task> GetLikedProjectsFromSimilarUser(int userId, int similarUserId); void CreateProjectIndex(); void DeleteIndex(); @@ -262,9 +276,16 @@ public virtual async Task> GetAllWithUsersCollaboratorsAndInstitut /// /// The highlighted parameter represents whether to filter highlighted projects. /// The categories parameter represents the categories the project needs to have + /// The user id whoms projects need to be retrieved /// This method returns the amount of projects matching the filters. - public virtual async Task CountAsync(bool? highlighted = null, ICollection categories = null) + public virtual async Task CountAsync(bool? highlighted = null, ICollection categories = null, int? userId = null) { + if (userId.HasValue) + return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories) + .Where(p => p.UserId == userId) + .CountAsync(); + + return await ApplyFilters(DbSet, null, null, null, true, highlighted, categories) .CountAsync(); } @@ -461,19 +482,57 @@ public override void Remove(Project entity) /// Get the user projects. /// /// The id of the user whoms projects need to be retrieved + /// The skip parameter represents the number of projects to skip. + /// The take parameter represents the number of projects to return. + /// The order by parameter represents the way how to order the projects. + /// The order by asc parameters represents the order direction (True: asc, False: desc) + /// The highlighted parameter represents the whether to filter highlighted projects. + /// The categories parameter represents the categories the project needs to have /// A enumerable of the users projects - public async Task> GetUserProjects(int userId) + public async Task> GetUserProjects( + int userId, + int? skip = null, + int? take = null, + Expression> orderBy = null, + bool orderByAsc = true, + bool? highlighted = null, + ICollection categories = null) { - IEnumerable projects = await GetDbSet() - .Include(p => p.Collaborators) - .Include(p => p.ProjectIcon) - .Include(p => p.Images) - .Include(p => p.Categories) - .ThenInclude(c => c.Category) - .Where(p => p.UserId == userId) - .ToListAsync(); + IQueryable projects = GetDbSet() + .Include(p => p.Collaborators) + .Include(p => p.ProjectIcon) + .Include(p => p.Images) + .Include(p => p.Categories) + .ThenInclude(c => c.Category) + .Where(p => p.UserId == userId); - return projects; + projects = ApplyFilters(projects, skip, take, orderBy, orderByAsc, highlighted, categories); + + List projectResults = await projects.Select(p => new Project + { + UserId = p.UserId, + User = p.User, + Id = p.Id, + ProjectIconId = p.ProjectIconId, + ProjectIcon = p.ProjectIcon, + CallToActions = p.CallToActions, + Collaborators = p.Collaborators, + Likes = p.Likes, + LinkedInstitutions = p.LinkedInstitutions, + Categories = p.Categories.Select(c => new ProjectCategory() + { + Category = c.Category, + Id = c.Id + }).ToList(), + Created = p.Created, + InstitutePrivate = p.InstitutePrivate, + Name = p.Name, + ShortDescription = p.ShortDescription, + Updated = p.Updated, + Uri = p.Uri + }) + .ToListAsync(); + return projectResults; } /// @@ -556,6 +615,7 @@ private List RedactUser(List projects) /// The order by asc parameters represents the order direction (True: asc, False: desc) /// The highlighted parameter represents the whether to filter highlighted projects. /// + /// The user id whoms projects need to be retrieved /// /// This method returns a IQueryable Projects collection based on the given filters. /// @@ -566,7 +626,8 @@ private IQueryable ApplyFilters( Expression> orderBy, bool orderByAsc, bool? highlighted, - ICollection categories + ICollection categories, + int? userId = null ) { if(highlighted.HasValue) @@ -600,6 +661,11 @@ ICollection categories } } + if(userId.HasValue) + { + queryable = queryable.Where(p => p.UserId == userId); + } + if(skip.HasValue && skip.Value > 0) queryable = queryable.Skip(skip.Value); if(take.HasValue) queryable = queryable.Take(take.Value); return queryable; @@ -771,6 +837,11 @@ public Task ProjectExistsAsync(int id) { return GetDbSet().AnyAsync(i => i.Id == id); } + + public Task CountAsync(bool? highlighted = null, ICollection categories = null) + { + throw new NotImplementedException(); + } } } diff --git a/Services/Services/ProjectService.cs b/Services/Services/ProjectService.cs index f2adbacb..d78116a7 100644 --- a/Services/Services/ProjectService.cs +++ b/Services/Services/ProjectService.cs @@ -53,15 +53,17 @@ public interface IProjectService : IService /// Get the number of projects /// /// The parameters to filter, sort and paginate the projects + /// The user id whoms projects need to be retrieved /// The number of projects - Task ProjectsCount(ProjectFilterParams projectFilterParams); + Task ProjectsCount(ProjectFilterParams projectFilterParams, int? userId = null); /// /// Get the total number of pages for the results /// /// The parameters to filter, sort and paginate the projects + /// The user id whoms projects need to be retrieved /// The total number of pages for the results - Task GetProjectsTotalPages(ProjectFilterParams projectFilterParams); + Task GetProjectsTotalPages(ProjectFilterParams projectFilterParams, int? userId = null); Task ProjectExistsAsync(int id); @@ -69,8 +71,9 @@ public interface IProjectService : IService /// Get the users projects /// /// The user id whoms projects need to be retrieved + /// The parameters to filter, sort and paginate the projects /// The total number of pages for the results - Task> GetUserProjects(int userId); + Task> GetUserProjects(int userId, ProjectFilterParams projectFilterParams); /// /// Registers all records of the current database to the message broker to be added to ElasticSearch. /// @@ -181,9 +184,14 @@ public Task> GetAllWithUsersCollaboratorsAndInstitutionsAsync(Proj /// Get the number of projects /// /// The parameters to filter, sort and paginate the projects + /// The user id whoms projects need to be retrieved /// The number of projects - public virtual async Task ProjectsCount(ProjectFilterParams projectFilterParams) + public virtual async Task ProjectsCount(ProjectFilterParams projectFilterParams, int? userId = null) { + if(userId.HasValue) + { + return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories, userId); + } return await Repository.CountAsync(projectFilterParams.Highlighted, projectFilterParams.Categories); } @@ -192,12 +200,12 @@ public virtual async Task ProjectsCount(ProjectFilterParams projectFilterPa /// /// The parameters to filter, sort and paginate the projects /// The total number of pages for the results - public virtual async Task GetProjectsTotalPages(ProjectFilterParams projectFilterParams) + public virtual async Task GetProjectsTotalPages(ProjectFilterParams projectFilterParams, int? userId = null) { if(projectFilterParams.AmountOnPage == null || projectFilterParams.AmountOnPage <= 0) projectFilterParams.AmountOnPage = 20; - int count = await ProjectsCount(projectFilterParams); + int count = await ProjectsCount(projectFilterParams, userId); return (int) Math.Ceiling(count / (decimal) projectFilterParams.AmountOnPage); } @@ -252,9 +260,44 @@ private void CreateProjectIndexElastic() } - public Task> GetUserProjects(int userId) + public Task> GetUserProjects(int userId, ProjectFilterParams projectFilterParams) { - return Repository.GetUserProjects(userId); + if(!projectFilterParams.AmountOnPage.HasValue || + projectFilterParams.AmountOnPage <= 0) + projectFilterParams.AmountOnPage = 20; + + int? skip = null; + int? take = projectFilterParams.AmountOnPage; + if(projectFilterParams.Page.HasValue && projectFilterParams.Page.Value > 1) + { + skip = projectFilterParams.AmountOnPage * (projectFilterParams.Page - 1); + take = projectFilterParams.AmountOnPage; + } + + Expression> orderBy; + switch(projectFilterParams.SortBy) + { + case "name": + orderBy = project => project.Name; + break; + case "created": + orderBy = project => project.Created; + break; + case "likes": + orderBy = project => project.Likes.Count; + break; + default: + orderBy = project => project.Updated; + break; + } + + bool orderByDirection = projectFilterParams.SortDirection == "asc"; + return Repository.GetUserProjects(userId, skip, + take, + orderBy, + orderByDirection, + projectFilterParams.Highlighted, + projectFilterParams.Categories); } public async Task> FindProjectsWhereTitleStartsWithQuery(string query) {