diff --git a/.gitignore b/.gitignore index 38ee8ff..b033101 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ app.db* .DS_Store app.db client/node_modules -Scripts/ +client/src/pages/TestPage.tsx \ No newline at end of file diff --git a/Controllers/AppUsersController.cs b/Controllers/AppUsersController.cs index d6bbc2b..d3ed49a 100644 --- a/Controllers/AppUsersController.cs +++ b/Controllers/AppUsersController.cs @@ -1,44 +1,42 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; using TicketManager.Models; +using Microsoft.AspNetCore.Authorization; namespace TicketManager.Controllers { - [Route("api/v1/[controller]")] + // [Authorize] + [Route("api/v1/users")] [ApiController] public class UsersController : ControllerBase { - private readonly AppDbContext _context; + private readonly IAppUserRepository _users; - public UsersController(AppDbContext context) + public UsersController(IAppUserRepository users) { - _context = context; + _users = users; } // GET: api/Users [HttpGet] - public async Task>> GetUsers() + public async Task> GetUsers() { - return await getAllAppUsersAsync(); + return await _users.List(); } // GET: api/Users/5 [HttpGet("{id}")] public async Task> GetUser(Guid id) { - var user = await getAppUserByIdAsync(id); - + var user = await _users.GetUser(id); if (user == null) { return NotFound(); } - return user; } @@ -52,16 +50,13 @@ namespace TicketManager.Controllers { return BadRequest(); } - - _context.Entry(user).State = EntityState.Modified; - try { - await _context.SaveChangesAsync(); + await _users.Update(user); } catch (DbUpdateConcurrencyException) { - if (!UserExists(id)) + if (!_users.Exists(id)) { return NotFound(); } @@ -70,7 +65,6 @@ namespace TicketManager.Controllers throw; } } - return NoContent(); } @@ -80,32 +74,27 @@ namespace TicketManager.Controllers [HttpPost] public async Task> PostUser(AppUser user) { - _context.AppUsers.Add(user); - await _context.SaveChangesAsync(); - + await _users.Add(user); return CreatedAtAction("GetUser", new { id = user.Id }, user); } // DELETE: api/Users/5 [HttpDelete("{id}")] - public async Task> DeleteUser(int id) + public async Task> DeleteUser(Guid id) { - var user = await _context.AppUsers.FindAsync(id); + var user = await _users.GetUser(id); if (user == null) { return NotFound(); } - - _context.AppUsers.Remove(user); - await _context.SaveChangesAsync(); - + await _users.Delete(user); return user; } [HttpGet("{id}/projects")] public async Task>> GetAppUserProjects(Guid id) { - AppUser user = await getAppUserByIdAsync(id); + AppUser user = await _users.GetUser(id); if (user == null) { return BadRequest(); @@ -116,36 +105,12 @@ namespace TicketManager.Controllers [HttpGet("{id}/tickets/")] public async Task>> GetAppUserTickets(Guid id) { - AppUser user = await getAppUserByIdAsync(id); + AppUser user = await _users.GetUser(id); if (user == null) { return BadRequest(); } return user.GetTickets(); } - - private bool UserExists(Guid id) - { - return _context.AppUsers.Any(e => e.Id == id); - } - - private IQueryable appUserQuery() - { - return _context.AppUsers - .Include(p => p.Assignments) - .ThenInclude(a => a.Project) - .ThenInclude(p => p.Tickets) - .Include(p => p.Edits); - } - - private async Task>> getAllAppUsersAsync() - { - return await appUserQuery().ToListAsync(); - } - - private async Task getAppUserByIdAsync(Guid id) - { - return await appUserQuery().FirstOrDefaultAsync(a => a.Id == id); - } } } diff --git a/Controllers/AssignmentsController.cs b/Controllers/AssignmentsController.cs index 400f0a7..67f39e1 100644 --- a/Controllers/AssignmentsController.cs +++ b/Controllers/AssignmentsController.cs @@ -1,8 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; @@ -10,6 +9,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { + [Authorize] [Route("api/[controller]")] [ApiController] public class AssignmentsController : ControllerBase diff --git a/Controllers/FilesController.cs b/Controllers/FilesController.cs index 6481b76..3b422d8 100644 --- a/Controllers/FilesController.cs +++ b/Controllers/FilesController.cs @@ -1,8 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; @@ -10,6 +9,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { + [Authorize] [Route("api/v1/[controller]")] [ApiController] public class FilesController : ControllerBase diff --git a/Controllers/HistoriesController.cs b/Controllers/HistoriesController.cs deleted file mode 100644 index 0934909..0000000 --- a/Controllers/HistoriesController.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using TicketManager.Data; -using TicketManager.Models; - -namespace TicketManager.Controllers -{ - [Route("api/v1/[controller]")] - [ApiController] - public class HistoriesController : ControllerBase - { - private readonly AppDbContext _context; - - public HistoriesController(AppDbContext context) - { - _context = context; - } - - // GET: api/Histories - [HttpGet] - public async Task>> GetEdits() - { - return await _context.Edits.ToListAsync(); - } - - // GET: api/Histories/5 - [HttpGet("{id}")] - public async Task> GetHistory(int id) - { - var history = await _context.Edits.FindAsync(id); - - if (history == null) - { - return NotFound(); - } - - return history; - } - - // PUT: api/Histories/5 - // To protect from overposting attacks, please enable the specific properties you want to bind to, for - // more details see https://aka.ms/RazorPagesCRUD. - [HttpPut("{id}")] - public async Task PutHistory(int id, History history) - { - if (id != history.Id) - { - return BadRequest(); - } - - _context.Entry(history).State = EntityState.Modified; - - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) - { - if (!HistoryExists(id)) - { - return NotFound(); - } - else - { - throw; - } - } - - return NoContent(); - } - - // POST: api/Histories - // To protect from overposting attacks, please enable the specific properties you want to bind to, for - // more details see https://aka.ms/RazorPagesCRUD. - [HttpPost] - public async Task> PostHistory(History history) - { - _context.Edits.Add(history); - await _context.SaveChangesAsync(); - - return CreatedAtAction("GetHistory", new { id = history.Id }, history); - } - - // DELETE: api/Histories/5 - [HttpDelete("{id}")] - public async Task> DeleteHistory(int id) - { - var history = await _context.Edits.FindAsync(id); - if (history == null) - { - return NotFound(); - } - - _context.Edits.Remove(history); - await _context.SaveChangesAsync(); - - return history; - } - - private bool HistoryExists(int id) - { - return _context.Edits.Any(e => e.Id == id); - } - } -} diff --git a/Controllers/NotesController.cs b/Controllers/NotesController.cs index f2a537b..20fe5dc 100644 --- a/Controllers/NotesController.cs +++ b/Controllers/NotesController.cs @@ -1,8 +1,7 @@ -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; @@ -10,6 +9,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { + [Authorize] [Route("api/v1/[controller]")] [ApiController] public class NotesController : ControllerBase diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index bfd87db..e3b52a5 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -1,53 +1,54 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Net.Mime; using System.Threading.Tasks; +using System.Linq; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; using TicketManager.Models; - +using TicketManager.DTO; namespace TicketManager.Controllers { + // [Authorize(Roles = "Admin")] + // [Authorize] [Produces("application/json")] [Route("api/v1/[controller]")] [ApiController] public class ProjectsController : ControllerBase { - private readonly IProjectRepository _projectRepo; - - public ProjectsController(IProjectRepository projectRepo) + private IProjectRepository _projects; + public ProjectsController(IProjectRepository context) { - _projectRepo = projectRepo; + _projects = context; } /// - /// Returns all existing projects. + /// Returns all projects stored in the database. /// /// /// Sample request: /// - /// GET: api/Projects + /// GET: api/v1/Projects /// /// - /// Returns all existing projects + /// Returns a list of projects [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetProjects() { - return await _projectRepo.List(); + + return await _projects.List(); } /// - /// Returns a specific project. + /// Locate a specific project stored in the database by its Id /// /// /// Sample request: /// - /// GET: api/Projects/2 + /// GET: api/v1/Projects/2 /// /// /// Identifier of the ressource @@ -56,177 +57,167 @@ namespace TicketManager.Controllers [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> GetProject(int id) + public async Task> GetProject(int id) { - Project project = await _projectRepo.Get(id); - if (project == null) { return NotFound(); } - return project; + Project project = await _projects.Get(id); + if (project == null) + { + return NotFound(); + } + return new ProjectDTO(project); } - // /// - // /// Updates a specific project. - // /// - // /// - // /// Sample request: - // /// - // /// PUT: api/Projects/3 - // /// { - // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", - // /// "firstName": "Thomas", - // /// "lastName": "Price", - // /// "presentation": "New Team?!", - // /// "email": "tp@mail.com", - // /// "phone": "0198237645" - // /// } - // /// - // /// - // /// Returns the modified project - // /// Request was succesful but no content is changed - // /// If the required project is null - // [HttpPut("{id}")] - // [ProducesResponseType(StatusCodes.Status200OK)] - // [ProducesResponseType(StatusCodes.Status204NoContent)] - // [ProducesResponseType(StatusCodes.Status404NotFound)] - // public async Task PutProject(int id, Project project) - // { - // if (id != project.Id) { return BadRequest(); } + /// + /// Updates the specific project with Id. + /// + /// + /// Sample request: + /// + /// PUT: api/v1/Projects/3 + /// { + /// "id": "357727fd-5262-4522-b8a3-38271d43de84", + /// "firstName": "Thomas", + /// "lastName": "Price", + /// "presentation": "New Team?!", + /// "email": "tp@mail.com", + /// "phone": "0198237645" + /// } + /// + /// + /// Request was succesful but no content is changed + /// If the required project is null + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutProject(int id, Project project) + { + if (id != project.Id) { return BadRequest(); } + try + { + await _projects.Update(project); + } + catch (DbUpdateConcurrencyException) + { + if (!_projects.Exists(id)) { return NotFound(); } + else { throw; } + } + return NoContent(); + } - // try - // { - // await _projectRepo.Update(project); - // } - // catch (DbUpdateConcurrencyException) - // { - // if (!_projectRepo.Exists(id)) - // { - // return NotFound(); - // } - // else - // { - // throw; - // } - // } + /// + /// Creates a project. + /// + /// + /// Sample request: + /// + /// POST: api/v1/Projects/ + /// { + /// "firstName": "Thomas", + /// "lastName": "Price", + /// "presentation": "New Team?!", + /// "email": "tp@mail.com", + /// "phone": "0198237645" + /// } + /// + /// + /// Returns the created project + [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> PostProject(Project project) + { + if (!ModelState.IsValid) { return BadRequest(); } + await _projects.Add(project); + return CreatedAtAction("GetProject", new { id = project.Id }, project); + } - // return NoContent(); - // } + /// + /// Deletes the project identified by its Id + /// + /// + /// Sample request: + /// + /// DELETE: api/v1/Projects/5 + /// + /// + /// Returns the deleted project + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [HttpDelete("{id}")] + public async Task DeleteProject(int id) + { + var project = await _projects.Get(id); + if (project == null) + { + return NotFound(); + } + await _projects.Delete(project); + return Ok(); + } - // /// - // /// Creates a project. - // /// - // /// - // /// Sample request: - // /// - // /// POST: api/Projects/ - // /// { - // /// "firstName": "Thomas", - // /// "lastName": "Price", - // /// "presentation": "New Team?!", - // /// "email": "tp@mail.com", - // /// "phone": "0198237645" - // /// } - // /// - // /// - // /// Returns the created project - // [HttpPost] - // [ProducesResponseType(StatusCodes.Status201Created)] - // [ProducesResponseType(StatusCodes.Status404NotFound)] - // public async Task> PostProject(Project project) - // { - // if (!ModelState.IsValid) { return BadRequest(); } - // await _projectRepo.AddAsync(project); + /// + /// Gets a project members. + /// + /// + /// Sample request: + /// + /// GET: api/v1/Projects/5/Members + /// + /// + /// Returns the project members as a list of users. + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [HttpGet("{id}/members")] + public async Task>> GetProjectMembers(int id) + { + var project = await _projects.Get(id); + if (project == null) + { return NotFound(); } + return project.GetMembers(); + } - // return CreatedAtAction("GetProject", new { id = project.Id }, project); - // } - - - - - // /// - // /// Deletes a project. - // /// - // /// - // /// Sample request: - // /// - // /// DELETE: api/Projects/5 - // /// - // /// - // /// Returns the deleted project - // [ProducesResponseType(StatusCodes.Status200OK)] - // [ProducesResponseType(StatusCodes.Status404NotFound)] - // [HttpDelete("{id}")] - // public async Task> DeleteProject(int id) - // { - // var project = await _projectRepo.GetByIdAsync(id); - // if (project == null) - // { - // return NotFound(); - // } - // await _projectRepo.DeleteAsync(id); - // return project; - // } - - // /// - // /// Gets a project members. - // /// - // /// - // /// Sample request: - // /// - // /// GET: api/Projects/5/Members - // /// - // /// - // /// Returns the project members - // [ProducesResponseType(StatusCodes.Status200OK)] - // [ProducesResponseType(StatusCodes.Status404NotFound)] - // [HttpGet("{id}/members")] - // public async Task>> GetProjectMembers(int id) - // { - // Project project = await _projectRepo.GetByIdAsync(id); - // if (project == null) - // { return NotFound(); } - // return project.GetMembers(); - // } - - // /// - // /// Updates a project members. - // /// - // /// - // /// Sample request: - // /// - // /// PUT: api/Projects/5/Members - // /// { - // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", - // /// "firstName": "Thomas", - // /// "lastName": "Price", - // /// "presentation": "New Team?!", - // /// "email": "tp@mail.com", - // /// "phone": "0198237645" - // /// } - // /// - // /// No content - // [ProducesResponseType(StatusCodes.Status204NoContent)] - // [ProducesResponseType(StatusCodes.Status404NotFound)] - // [HttpPut("{id}/members")] - // public async Task> SetProjectMembers(int id, List projectMembers) - // { - // Project project = await _projectRepo.GetByIdAsync(id); - // if (project == null) - // { - // return NotFound(); - // } - // project.SetMembers(projectMembers); - // try - // { - // await _projectRepo.UpdateAsync(project); - // } - // catch (DbUpdateException /* ex */) - // { - // //Log the error (uncomment ex variable name and write a log.) - // ModelState.AddModelError("", "Unable to save changes. " + - // "Try again, and if the problem persists, " + - // "see your system administrator."); - // } - // return NoContent(); - // } + /// + /// Updates a project members. + /// + /// + /// Sample request: + /// + /// PUT: api/v1/Projects/5/Members + /// { + /// "id": "357727fd-5262-4522-b8a3-38271d43de84", + /// "firstName": "Thomas", + /// "lastName": "Price", + /// "presentation": "New Team?!", + /// "email": "tp@mail.com", + /// "phone": "0198237645" + /// } + /// + /// No content + /// Not Found + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [HttpPut("{id}/members")] + public async Task> SetProjectMembers(int id, List projectMembers) + { + Project project = await _projects.Get(id); + if (project == null) + { + return NotFound(); + } + project.SetMembers(projectMembers); + try + { + await _projects.Update(project); + } + catch (DbUpdateException /* ex */) + { + //Log the error (uncomment ex variable name and write a log.) + ModelState.AddModelError("", "Unable to save changes. " + + "Try again, and if the problem persists, " + + "see your system administrator."); + } + return NoContent(); + } // // /// // // /// Assign a user to a project. @@ -234,7 +225,7 @@ namespace TicketManager.Controllers // // /// // // /// Sample request: // // /// - // // /// POST: api/Projects/addmembers + // // /// POST: api/v1/Projects/addmembers // // /// [{ // // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", // // /// "firstName": "Thomas", @@ -277,7 +268,7 @@ namespace TicketManager.Controllers // // /// // // /// Sample request: // // /// - // // /// PUT: api/Projects/removemembers + // // /// PUT: api/v1/Projects/removemembers // // /// [{ // // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", // // /// "firstName": "Thomas", @@ -309,9 +300,5 @@ namespace TicketManager.Controllers // // } // // return NoContent(); // // } - - - - } } diff --git a/Controllers/ProjectsController_working.cs b/Controllers/ProjectsController_working.cs deleted file mode 100644 index 3936677..0000000 --- a/Controllers/ProjectsController_working.cs +++ /dev/null @@ -1,317 +0,0 @@ -// using System; -// using System.Collections.Generic; -// using System.Linq; -// using System.Net.Mime; -// using System.Threading.Tasks; -// using Microsoft.AspNetCore.Http; -// using Microsoft.AspNetCore.Mvc; -// using Microsoft.EntityFrameworkCore; -// using TicketManager.Data; -// using TicketManager.Models; - - -// namespace TicketManager.Controllers -// { -// [Produces("application/json")] -// [Route("api/v1/[controller]")] -// [ApiController] -// public class ProjectsController : ControllerBase -// { -// private readonly IProjectRepository _projectRepo; - -// public ProjectsController(IProjectRepository projectRepo) -// { -// _projectRepo = projectRepo; -// } - -// /// -// /// Returns all existing projects. -// /// -// /// -// /// Sample request: -// /// -// /// GET: api/Projects -// /// -// /// -// /// Returns all existing projects -// [HttpGet] -// [ProducesResponseType(StatusCodes.Status200OK)] -// public async Task> GetProjects() -// { -// return await _projectRepo.ListAsync(); -// // GetAllProjectsAsync(); -// } - -// /// -// /// Returns a specific project. -// /// -// /// -// /// Sample request: -// /// -// /// GET: api/Projects/2 -// /// -// /// -// /// Returns a specific project -// /// If the required project is null -// [HttpGet("{id}")] -// [ProducesResponseType(StatusCodes.Status200OK)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// public async Task> GetProject(int id) -// { -// Project project = await _projectRepo.GetByIdAsync(id); -// if (project == null) { return NotFound(); } -// return project; -// } - -// /// -// /// Updates a specific project. -// /// -// /// -// /// Sample request: -// /// -// /// PUT: api/Projects/3 -// /// { -// /// "id": "357727fd-5262-4522-b8a3-38271d43de84", -// /// "firstName": "Thomas", -// /// "lastName": "Price", -// /// "presentation": "New Team?!", -// /// "email": "tp@mail.com", -// /// "phone": "0198237645" -// /// } -// /// -// /// -// /// Returns the modified project -// /// Request was succesful but no content is changed -// /// If the required project is null -// [HttpPut("{id}")] -// [ProducesResponseType(StatusCodes.Status200OK)] -// [ProducesResponseType(StatusCodes.Status204NoContent)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// public async Task PutProject(int id, Project project) -// { -// if (id != project.Id) { return BadRequest(); } - -// try -// { -// await _projectRepo.UpdateAsync(project); -// } -// catch (DbUpdateConcurrencyException) -// { -// if (!_projectRepo.Exists(id)) -// { -// return NotFound(); -// } -// else -// { -// throw; -// } -// } - -// return NoContent(); -// } - -// /// -// /// Creates a project. -// /// -// /// -// /// Sample request: -// /// -// /// POST: api/Projects/ -// /// { -// /// "firstName": "Thomas", -// /// "lastName": "Price", -// /// "presentation": "New Team?!", -// /// "email": "tp@mail.com", -// /// "phone": "0198237645" -// /// } -// /// -// /// -// /// Returns the created project -// [HttpPost] -// [ProducesResponseType(StatusCodes.Status201Created)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// public async Task> PostProject(Project project) -// { -// if (!ModelState.IsValid) { return BadRequest(); } -// await _projectRepo.AddAsync(project); - -// return CreatedAtAction("GetProject", new { id = project.Id }, project); -// } - - - - -// /// -// /// Deletes a project. -// /// -// /// -// /// Sample request: -// /// -// /// DELETE: api/Projects/5 -// /// -// /// -// /// Returns the deleted project -// [ProducesResponseType(StatusCodes.Status200OK)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// [HttpDelete("{id}")] -// public async Task> DeleteProject(int id) -// { -// var project = await _projectRepo.GetByIdAsync(id); -// if (project == null) -// { -// return NotFound(); -// } -// await _projectRepo.DeleteAsync(id); -// return project; -// } - -// /// -// /// Gets a project members. -// /// -// /// -// /// Sample request: -// /// -// /// GET: api/Projects/5/Members -// /// -// /// -// /// Returns the project members -// [ProducesResponseType(StatusCodes.Status200OK)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// [HttpGet("{id}/members")] -// public async Task>> GetProjectMembers(int id) -// { -// Project project = await _projectRepo.GetByIdAsync(id); -// if (project == null) -// { return NotFound(); } -// return project.GetMembers(); -// } - -// /// -// /// Updates a project members. -// /// -// /// -// /// Sample request: -// /// -// /// PUT: api/Projects/5/Members -// /// { -// /// "id": "357727fd-5262-4522-b8a3-38271d43de84", -// /// "firstName": "Thomas", -// /// "lastName": "Price", -// /// "presentation": "New Team?!", -// /// "email": "tp@mail.com", -// /// "phone": "0198237645" -// /// } -// /// -// /// No content -// [ProducesResponseType(StatusCodes.Status204NoContent)] -// [ProducesResponseType(StatusCodes.Status404NotFound)] -// [HttpPut("{id}/members")] -// public async Task> SetProjectMembers(int id, List projectMembers) -// { -// Project project = await _projectRepo.GetByIdAsync(id); -// if (project == null) -// { -// return NotFound(); -// } -// project.SetMembers(projectMembers); -// try -// { -// await _projectRepo.UpdateAsync(project); -// } -// catch (DbUpdateException /* ex */) -// { -// //Log the error (uncomment ex variable name and write a log.) -// ModelState.AddModelError("", "Unable to save changes. " + -// "Try again, and if the problem persists, " + -// "see your system administrator."); -// } -// return NoContent(); -// } - -// // /// -// // /// Assign a user to a project. -// // /// -// // /// -// // /// Sample request: -// // /// -// // /// POST: api/Projects/addmembers -// // /// [{ -// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", -// // /// "firstName": "Thomas", -// // /// "lastName": "Price", -// // /// "presentation": "New Team?!", -// // /// "email": "tp@mail.com", -// // /// "phone": "0198237645" -// // /// }] -// // /// -// // /// -// // /// Returns the created project -// // [ProducesResponseType(StatusCodes.Status204NoContent)] -// // [ProducesResponseType(StatusCodes.Status404NotFound)] -// // [HttpPut("{id}/addMembers")] -// // public async Task> AddMembersToProject(int id, List usersToAdd) -// // { -// // if (usersToAdd == null) -// // { -// // return BadRequest(); -// // } -// // Project project = await GetProjectByIdAsync(id); -// // project.AddMembers(usersToAdd); -// // try -// // { -// // await _context.SaveChangesAsync(); -// // } -// // catch (DbUpdateException /* ex */) -// // { -// // //Log the error (uncomment ex variable name and write a log.) -// // ModelState.AddModelError("", "Unable to save changes. " + -// // "Try again, and if the problem persists, " + -// // "see your system administrator."); -// // } -// // return NoContent(); -// // } - -// // /// -// // /// Remove a user to a project. -// // /// -// // /// -// // /// Sample request: -// // /// -// // /// PUT: api/Projects/removemembers -// // /// [{ -// // /// "id": "357727fd-5262-4522-b8a3-38271d43de84", -// // /// "firstName": "Thomas", -// // /// "lastName": "Price", -// // /// "presentation": "New Team?!", -// // /// "email": "tp@mail.com", -// // /// "phone": "0198237645" -// // /// }] -// // /// -// // /// -// // /// Returns the created project -// // [ProducesResponseType(StatusCodes.Status204NoContent)] -// // [ProducesResponseType(StatusCodes.Status404NotFound)] -// // [HttpPut("{id}/removeMembers")] -// // public async Task> RemoveMembersFromProject(int id, List usersToRemove) -// // { -// // Project project = await GetProjectByIdAsync(id); -// // project.RemoveMembers(usersToRemove); -// // try -// // { -// // await _context.SaveChangesAsync(); -// // } -// // catch (DbUpdateException /* ex */) -// // { -// // //Log the error (uncomment ex variable name and write a log.) -// // ModelState.AddModelError("", "Unable to save changes. " + -// // "Try again, and if the problem persists, " + -// // "see your system administrator."); -// // } -// // return NoContent(); -// // } - - - - -// } -// } diff --git a/Controllers/TicketsController.cs b/Controllers/TicketsController.cs index 17dac2c..90844f0 100644 --- a/Controllers/TicketsController.cs +++ b/Controllers/TicketsController.cs @@ -1,8 +1,6 @@ -using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; @@ -10,35 +8,34 @@ using TicketManager.Models; namespace TicketManager.Controllers { + [Authorize] [Route("api/v1/[controller]")] [ApiController] public class TicketsController : ControllerBase { - private readonly AppDbContext _context; + private readonly ITicketRepository _tickets; - public TicketsController(AppDbContext context) + public TicketsController(ITicketRepository tickets) { - _context = context; + _tickets = tickets; } // GET: api/Tickets [HttpGet] - public async Task>> GetTickets() + public async Task> GetTickets() { - return await getAllTicketsAsync(); + return await _tickets.List(); } // GET: api/Tickets/5 [HttpGet("{id}")] public async Task> GetTicket(int id) { - var ticket = await getTicketByIdAsync(id); - + var ticket = await _tickets.Get(id); if (ticket == null) { return NotFound(); } - return ticket; } @@ -52,16 +49,13 @@ namespace TicketManager.Controllers { return BadRequest(); } - - _context.Entry(ticket).State = EntityState.Modified; - try { - await _context.SaveChangesAsync(); + await _tickets.Update(ticket); } catch (DbUpdateConcurrencyException) { - if (!TicketExists(id)) + if (!_tickets.Exists(id)) { return NotFound(); } @@ -70,7 +64,6 @@ namespace TicketManager.Controllers throw; } } - return NoContent(); } @@ -80,9 +73,7 @@ namespace TicketManager.Controllers [HttpPost] public async Task> PostTicket(Ticket ticket) { - _context.Tickets.Add(ticket); - await _context.SaveChangesAsync(); - + await _tickets.Add(ticket); return CreatedAtAction("GetTicket", new { id = ticket.Id }, ticket); } @@ -90,59 +81,28 @@ namespace TicketManager.Controllers [HttpDelete("{id}")] public async Task> DeleteTicket(int id) { - var ticket = await _context.Tickets.FindAsync(id); + var ticket = await _tickets.Get(id); if (ticket == null) { return NotFound(); } - - _context.Tickets.Remove(ticket); - await _context.SaveChangesAsync(); - + await _tickets.Delete(ticket); return ticket; } [HttpGet("{id}/assignees")] public async Task>> GetTicketAssignees(int id) { - Ticket ticket = await getTicketByIdAsync(id); + Ticket ticket = await _tickets.Get(id); return ticket.GetAssignees(); } [HttpPut("{id}/closed")] - public async Task CloseTicket(int id) + public async Task CloseTicket(int id) { - Ticket ticket = await getTicketByIdAsync(id); + Ticket ticket = await _tickets.Get(id); ticket.Close(); - return NoContent(); - } - - private bool TicketExists(int id) - { - return _context.Tickets.Any(e => e.Id == id); - } - - private IQueryable ticketQuery() // problem with link - { - return _context.Tickets - .Include(p => p.Project) - .ThenInclude(a => a.Assignments) - .ThenInclude(p => p.User) - // .Include(p => p.Edits) - // .Include(p => p.Notes) - // .Include(p => p.Files) - // .Include(p => p.Creator) - ; - } - - private async Task>> getAllTicketsAsync() - { - return await ticketQuery().ToListAsync(); - } - - private async Task getTicketByIdAsync(int id) - { - return await ticketQuery().FirstOrDefaultAsync(a => a.Id == id); + return await PutTicket(ticket.Id, ticket); } } } diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs index b249c6b..af77479 100644 --- a/Data/AppDbContext.cs +++ b/Data/AppDbContext.cs @@ -12,7 +12,7 @@ namespace TicketManager.Data public DbSet AppUsers { get; set; } public DbSet Tickets { get; set; } public DbSet Assignments { get; set; } - public DbSet Edits { get; set; } + public DbSet Activities { get; set; } public DbSet Notes { get; set; } public DbSet Files { get; set; } diff --git a/Data/AppUserRepository.cs b/Data/AppUserRepository.cs new file mode 100644 index 0000000..a8b4a62 --- /dev/null +++ b/Data/AppUserRepository.cs @@ -0,0 +1,37 @@ +using System.Threading.Tasks; +using TicketManager.Models; +using System.Linq; +using System; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; + +namespace TicketManager.Data +{ + public class AppUserRepository : GenericRepository, IAppUserRepository + { + private readonly IQueryable _query; + public AppUserRepository(AppDbContext context) : base(context) + { + _query = _dbSet + .Include(p => p.Assignments) + .ThenInclude(a => a.Project) + .ThenInclude(p => p.Tickets) + .Include(p => p.Activities); + } + + public async Task GetUser(Guid id) + { + return await _query.FirstOrDefaultAsync(p => p.Id == id); + } + + public override async Task> List() + { + return await _query.ToListAsync(); + } + + public bool Exists(Guid id) + { + return _dbSet.Any(e => e.Id == id); + } + } +} \ No newline at end of file diff --git a/Data/GenericRepository.cs b/Data/GenericRepository.cs index ac7d4e8..1a90aa1 100644 --- a/Data/GenericRepository.cs +++ b/Data/GenericRepository.cs @@ -17,16 +17,18 @@ namespace TicketManager.Data _dbSet = _context.Set(); } - public void Add(T entity) + public async Task Add(T entity) { _dbSet.Add(entity); + return await _context.SaveChangesAsync(); } - public void Delete(T entity) + public async Task Delete(T entity) { if (_context.Entry(entity).State == EntityState.Detached) { _dbSet.Attach(entity); } _dbSet.Remove(entity); + return await _context.SaveChangesAsync(); } public async Task> Find(int id, Expression> expr) @@ -43,10 +45,11 @@ namespace TicketManager.Data return await _dbSet.AsNoTracking().ToListAsync(); } - public void Update(T entity) + public async Task Update(T entity) { _dbSet.Attach(entity); _context.Entry(entity).State = EntityState.Modified; + return await _context.SaveChangesAsync(); } } } \ No newline at end of file diff --git a/Data/Interfaces/IAppUserRepository.cs b/Data/Interfaces/IAppUserRepository.cs new file mode 100644 index 0000000..ab57db3 --- /dev/null +++ b/Data/Interfaces/IAppUserRepository.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; +using TicketManager.Models; + +namespace TicketManager.Data +{ + public interface IAppUserRepository : IGenericRepository + { + Task GetUser(Guid id); + bool Exists(Guid id); + } +} \ No newline at end of file diff --git a/Data/Interfaces/IGenericRepository.cs b/Data/Interfaces/IGenericRepository.cs index e8594d8..915ef12 100644 --- a/Data/Interfaces/IGenericRepository.cs +++ b/Data/Interfaces/IGenericRepository.cs @@ -11,10 +11,10 @@ namespace TicketManager.Data Task Get(int id); Task> Find(int id, Expression> expr); - void Add(T entity); + Task Add(T entity); - void Update(T entity); + Task Update(T entity); - void Delete(T entity); + Task Delete(T entity); } } \ No newline at end of file diff --git a/Data/Interfaces/ITicketRepository.cs b/Data/Interfaces/ITicketRepository.cs new file mode 100644 index 0000000..de8af26 --- /dev/null +++ b/Data/Interfaces/ITicketRepository.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TicketManager.Models; + +namespace TicketManager.Data +{ + public interface ITicketRepository : IGenericRepository + { + bool Exists(int id); + } +} \ No newline at end of file diff --git a/Data/Interfaces/IUnitOfWork.cs b/Data/Interfaces/IUnitOfWork.cs index 6a614f3..eab4e21 100644 --- a/Data/Interfaces/IUnitOfWork.cs +++ b/Data/Interfaces/IUnitOfWork.cs @@ -6,6 +6,8 @@ namespace TicketManager.Data public interface IUnitOfWork : IDisposable { IProjectRepository Projects { get; } + IAppUserRepository AppUsers { get; } + ITicketRepository Tickets { get; } Task Complete(); } } \ No newline at end of file diff --git a/Data/ProjectRepository working.cs b/Data/ProjectRepository working.cs deleted file mode 100644 index c63560c..0000000 --- a/Data/ProjectRepository working.cs +++ /dev/null @@ -1,57 +0,0 @@ -// using System.Threading.Tasks; -// using TicketManager.Models; -// using System.Linq; -// using Microsoft.EntityFrameworkCore; -// using System.Collections.Generic; -// using Microsoft.AspNetCore.Mvc; - -// namespace TicketManager.Data -// { -// public class ProjectRepository : IProjectRepository -// { -// private readonly AppDbContext _context; -// private readonly IQueryable _query; -// public ProjectRepository(AppDbContext context) -// { -// _context = context; -// _query = _context.Projects -// .Include(p => p.Assignments) -// .ThenInclude(a => a.User) -// .Include(p => p.Tickets) -// .Include(p => p.Manager) -// .Include(p => p.Files); -// } - -// public Task AddAsync(Project project) -// { -// _context.Projects.Add(project); -// return _context.SaveChangesAsync(); -// } - -// public async Task DeleteAsync(int id) -// { -// Project project = await GetByIdAsync(id); -// _context.Projects.Remove(project); -// return await _context.SaveChangesAsync(); -// } - -// public async Task GetByIdAsync(int id) -// { -// return await _query.FirstOrDefaultAsync(p => p.Id == id); -// } - -// public async Task> ListAsync() -// { -// return await _query.ToListAsync(); -// } - -// public Task UpdateAsync(Project project) -// { -// _context.Entry(project).State = EntityState.Modified; -// return _context.SaveChangesAsync(); -// } -// public bool Exists(int id) -// { return _context.Projects.Any(e => e.Id == id); } - -// } -// } \ No newline at end of file diff --git a/Data/ProjectRepository.cs b/Data/ProjectRepository.cs index 7349bfd..ec8dede 100644 --- a/Data/ProjectRepository.cs +++ b/Data/ProjectRepository.cs @@ -15,8 +15,7 @@ namespace TicketManager.Data .Include(p => p.Assignments).ThenInclude(a => a.User) .Include(p => p.Tickets) .Include(p => p.Manager) - .Include(p => p.Files) - .AsNoTracking(); + .Include(p => p.Files); } public override async Task Get(int id) @@ -30,7 +29,9 @@ namespace TicketManager.Data } public bool Exists(int id) - { return _dbSet.Any(e => e.Id == id); } + { + return _dbSet.Any(e => e.Id == id); + } public async Task> GetMembers(int id) { diff --git a/Data/TicketRepository.cs b/Data/TicketRepository.cs new file mode 100644 index 0000000..e142c8f --- /dev/null +++ b/Data/TicketRepository.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using TicketManager.Models; +using Microsoft.EntityFrameworkCore; + +namespace TicketManager.Data +{ + public class TicketRepository : GenericRepository, ITicketRepository + { + private IQueryable _query; + public TicketRepository(AppDbContext context) : base(context) + { + _query = _dbSet + .Include(p => p.Project) + .ThenInclude(a => a.Assignments) + .ThenInclude(p => p.User) + // .Include(p => p.Edits) + // .Include(p => p.Notes) + // .Include(p => p.Files) + // .Include(p => p.Creator) + ; + } + + public override async Task Get(int id) + { + return await _query.FirstOrDefaultAsync(p => p.Id == id); + } + + public override async Task> List() + { + return await _query.ToListAsync(); + } + + public bool Exists(int id) + { + return _dbSet.Any(e => e.Id == id); + } + } +} \ No newline at end of file diff --git a/Data/UnitOfWork.cs b/Data/UnitOfWork.cs index ad82b8b..1a0e21a 100644 --- a/Data/UnitOfWork.cs +++ b/Data/UnitOfWork.cs @@ -10,15 +10,20 @@ namespace TicketManager.Data { _context = context; Projects = new ProjectRepository(_context); + Tickets = new TicketRepository(_context); + AppUsers = new AppUserRepository(_context); } public IProjectRepository Projects { get; private set; } + public IAppUserRepository AppUsers { get; private set; } + + public ITicketRepository Tickets { get; private set; } + public async Task Complete() { return await _context.SaveChangesAsync(); } - public void Dispose() { _context.DisposeAsync(); diff --git a/DataTransfertObjects/ProjectDTO.cs b/DataTransfertObjects/ProjectDTO.cs new file mode 100644 index 0000000..ccfa8a3 --- /dev/null +++ b/DataTransfertObjects/ProjectDTO.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using TicketManager.Models; + +namespace TicketManager.DTO +{ + public class ProjectDTO + { + public ProjectDTO(Project project) + { + Id = project.Id; + Title = project.Title; + Description = project.Description; + CreatedAt = project.CreatedAt; + Progression = project.Progression; + Status = project.Status.ToString(); + Manager = project.Manager; + Users = project.GetMembers(); + Tickets = project.Tickets; + Activities = project.Activities; + Files = project.Files; + } + + public int Id { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + public DateTime CreatedAt { get; private set; } = DateTime.Now; + + public DateTime PlannedEnding { get; set; } + + public decimal Progression { get; set; } + + public string Status { get; set; } + + public AppUser Manager { get; set; } + public List Users { get; set; } = new List(); + + public List Tickets { get; set; } = new List(); + + public List Activities { get; set; } = new List(); + + public List Files { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Models/History.cs b/Models/Activity.cs similarity index 86% rename from Models/History.cs rename to Models/Activity.cs index e039343..dcad951 100644 --- a/Models/History.cs +++ b/Models/Activity.cs @@ -1,9 +1,8 @@ using System; -using System.Collections.Generic; namespace TicketManager.Models { - public class History + public class Activity { public int Id { get; set; } public string Description { get; set; } diff --git a/Models/AppUser.cs b/Models/AppUser.cs index 8d81141..41b08c6 100644 --- a/Models/AppUser.cs +++ b/Models/AppUser.cs @@ -38,12 +38,11 @@ namespace TicketManager.Models // [Display(Name = "Avatar")] // public byte[] Picture { get; set; } - // public Role Role { get; set; } public List Assignments { get; set; } = new List(); [Display(Name = "Activity")] - public List Edits { get; set; } = new List(); + public List Activities { get; set; } = new List(); // Methods public List GetProjects() @@ -53,7 +52,7 @@ namespace TicketManager.Models public List GetTickets() { - List tickets = new List(); + var tickets = new List(); GetProjects().ForEach(p => tickets.Concat(p.Tickets)); return tickets; } diff --git a/Models/File.cs b/Models/File.cs index 8dd01e4..b2b7485 100644 --- a/Models/File.cs +++ b/Models/File.cs @@ -5,8 +5,6 @@ namespace TicketManager.Models { public class File { - - public int Id { get; set; } public string FileName { get; set; } diff --git a/Models/Interfaces/ITask.cs b/Models/Interfaces/ITask.cs index 124b8c0..8f83813 100644 --- a/Models/Interfaces/ITask.cs +++ b/Models/Interfaces/ITask.cs @@ -11,18 +11,18 @@ namespace TicketManager.Models string Description { get; set; } DateTime CreatedAt { get; } DateTime PlannedEnding { get; set; } - List Edits { get; set; } + List Activities { get; set; } public virtual void AddLogEntry(string description)//, User user) { - History Edit = new History() + Activity Activity = new Activity() { Description = description, ActivityType = ActivityType.Undefined, // User = user, UpdateDate = DateTime.Now }; - Edits.Add(Edit); + Activities.Add(Activity); } } } diff --git a/Models/Project.cs b/Models/Project.cs index acebe5f..fceedcf 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -26,7 +26,6 @@ namespace TicketManager.Models [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PlannedEnding { get; set; } - // private decimal _progression; [Display(Name = "Progress")] public decimal Progression { @@ -67,7 +66,7 @@ namespace TicketManager.Models public List Tickets { get; set; } = new List(); - public List Edits { get; set; } = new List(); + public List Activities { get; set; } = new List(); public List Files { get; set; } = new List(); @@ -119,8 +118,7 @@ namespace TicketManager.Models this.AddMembers(projectMembers); } } - // public int GetMembersCount() => this.GetMembers().Count(); - // public void GetTicketsCount() => this.Tickets.Count(); + public void GetTicketsUpdates() { throw new NotImplementedException("Not Implemented"); } diff --git a/Models/Ticket.cs b/Models/Ticket.cs index abd1ede..5b28ded 100644 --- a/Models/Ticket.cs +++ b/Models/Ticket.cs @@ -29,7 +29,7 @@ namespace TicketManager.Models public Difficulty Difficulty { get; set; } = Difficulty.Undefined; public Category Category { get; set; } = Category.Undefined; - [Display(Name = "Created By")] + // [Display(Name = "Created By")] // public AppUser Creator { get; set; } public Guid CreatorId { get; set; } @@ -38,7 +38,7 @@ namespace TicketManager.Models // public int ProjectId { get; set; } public List Notes = new List(); - public List Edits = new List(); + public List Activities = new List(); public List Files = new List(); diff --git a/README.md b/README.md index d37ede2..17b770b 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,17 @@ ## TO DO -- Write API tests using Postman: request + test, environment variables, mock server -- Annotate API request in controllers -- Annotate Properties in Models -- Write backend tests -- Have a Look at typeahead component -- Ensure Tickets Edits belong to Project Edits -- Ensure Tickets Files belong to Project Files -- Async model methods ? -- setMembers & removeMembers from project api not working -- Write a query class to refactor code and optimize perf on get queries (AsNoTracking) -- repository + strategy to decouple controllers from DbContext. Easier testing -- update assignments automatically from context +- [ ] Write API tests using Postman: request + test, environment variables, mock server +- [ ] Annotate API request in controllers +- [ ] Annotate Properties in Models +- [ ] Write backend tests +- [ ] Have a Look at typeahead component +- [ ] Ensure Tickets Edits belong to Project Edits +- [ ] Ensure Tickets Files belong to Project Files +- [ ] Async model methods ? +- [ ] update assignments automatically from context +- [ ] use PATCH instead of PUT +- [ ] logging +- [ ] check useRef, useReducer, dispatch +- [ ] error page redirect when offline. +- [ ] ticket/files/activities list placeholders when empty diff --git a/Scripts/apiQueries.sh b/Scripts/apiQueries.sh new file mode 100755 index 0000000..08cee4e --- /dev/null +++ b/Scripts/apiQueries.sh @@ -0,0 +1 @@ +curl --insecure https://localhost:5001/api/v1/ \ No newline at end of file diff --git a/Scripts/authentication.sh b/Scripts/authentication.sh new file mode 100755 index 0000000..3c54e42 --- /dev/null +++ b/Scripts/authentication.sh @@ -0,0 +1 @@ +dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer \ No newline at end of file diff --git a/Scripts/cleanDevDb.sh b/Scripts/cleanDevDb.sh deleted file mode 100755 index f470323..0000000 --- a/Scripts/cleanDevDb.sh +++ /dev/null @@ -1,5 +0,0 @@ -rm -r Migrations -rm app.db -dotnet ef migrations add Migration1 -dotnet ef database update -dotnet run \ No newline at end of file diff --git a/Scripts/scaffoldControllers.sh b/Scripts/scaffoldControllers.sh deleted file mode 100755 index e68e6a7..0000000 --- a/Scripts/scaffoldControllers.sh +++ /dev/null @@ -1,6 +0,0 @@ -rm Controllers/AppUsersController.cs -rm Controllers/TicketsController.cs -rm Controllers/ProjectsController.cs -dotnet aspnet-codegenerator controller -name AppUsersController -async -api -m AppUser -dc AppDbContext -outDir Controllers -dotnet aspnet-codegenerator controller -name TicketsController -async -api -m Ticket -dc AppDbContext -outDir Controllers -dotnet aspnet-codegenerator controller -name ProjectsController -async -api -m Project -dc AppDbContext -outDir Controllers \ No newline at end of file diff --git a/Startup.cs b/Startup.cs index 9c41d9c..f86c247 100644 --- a/Startup.cs +++ b/Startup.cs @@ -19,6 +19,7 @@ using TicketManager.Data; using TicketManager.Models; using Microsoft.AspNetCore.Mvc.NewtonsoftJson; using Newtonsoft.Json; +using Microsoft.AspNetCore.Authentication.JwtBearer; [assembly: ApiController] namespace TicketManager @@ -32,18 +33,33 @@ namespace TicketManager public IConfiguration Configuration { get; } - // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseSqlite(Configuration.GetConnectionString("Sqlite"))); services.AddScoped(); - services.AddControllers() - .AddNewtonsoftJson(options => + services.AddScoped(); + services.AddScoped(); + + services.AddAuthentication(options => { - options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // avoid cycle ref errors + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(options => + { + options.Authority = "https://dev-fyjrvohx.auth0.com/"; + options.Audience = "https://localhost:5001/api/V1/"; + //options.Authority = $"https://{Configuration["Auth0:Domain"]}/"; + //options.Audience = Configuration["Auth0:Audience"]; }); + services.AddControllers() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // avoid cycle ref errors + } + ); + services.AddSpaStaticFiles(configuration => { configuration.RootPath = "client/build"; @@ -70,17 +86,12 @@ namespace TicketManager services.AddSwaggerGenNewtonsoftSupport(); // explicit opt-in - needs to be placed after AddSwaggerGen() } - - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - var repository = serviceProvider.GetRequiredService(); - - // InitializeDatabaseAsync(repository).Wait() + // var repository = serviceProvider.GetRequiredService(); } else { @@ -90,7 +101,6 @@ namespace TicketManager app.UseHttpsRedirection(); app.UseDefaultFiles(); - app.UseSwagger(); app.UseSwaggerUI(c => @@ -98,11 +108,9 @@ namespace TicketManager c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ticket Manager API v1"); }); - - app.UseSpaStaticFiles(); app.UseRouting(); - + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => @@ -121,9 +129,4 @@ namespace TicketManager }); } } -} - - - - - +} \ No newline at end of file diff --git a/TicketManager.csproj b/TicketManager.csproj index db8150f..9a81c04 100644 --- a/TicketManager.csproj +++ b/TicketManager.csproj @@ -3,10 +3,7 @@ netcoreapp3.1 - - 8.0 - - + 8.0 @@ -16,17 +13,18 @@ + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + - - + + diff --git a/appsettings.json b/appsettings.json index 390f10c..070e52f 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,5 +10,9 @@ "AllowedHosts": "*", "ConnectionStrings": { "Sqlite": "Data Source=app.db" + }, + "Auth0": { + "Domain": "https://dev-fyjrvohx.auth0.com/", + "Audience": "https://localhost:5001/api/V1/" } } \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css deleted file mode 100644 index 90edddb..0000000 --- a/client/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -.panel { - padding-left: 0px; - padding-top: 10px; -} -.field { - padding-left: 10px; - padding-right: 10px; -} -.city { - display: flex; - background: linear-gradient( - 90deg, - rgba(2, 0, 36, 1) 0%, - rgba(25, 112, 245, 0.6399510487788865) 0%, - rgba(0, 212, 255, 1) 100% - ); - flex-direction: column; - height: 40vh; - justify-content: center; - align-items: center; - padding: 0px 20px 20px 20px; - margin: 0px 0px 50px 0px; - border: 1px solid; - border-radius: 5px; - box-shadow: 2px 2px #888888; - font-family: "Merriweather", serif; -} -.city h1 { - line-height: 1.2; -} -.city span { - padding-left: 20px; -} -.city .row { - padding-top: 20px; -} -.weatherError { - color: #f16051; - font-size: 20px; - letter-spacing: 1px; - font-weight: 200; -} diff --git a/client/src/App.tsx b/client/src/App.tsx index 2400e59..88220e3 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,4 @@ import React, { FC } from "react"; -import { AppRouter } from "./utils/router"; -import "./App.css"; import Layout from "./pages/Layout"; const App: FC = () => { diff --git a/client/src/VM/ProjectVM.ts b/client/src/VM/ProjectVM.ts new file mode 100644 index 0000000..717a233 --- /dev/null +++ b/client/src/VM/ProjectVM.ts @@ -0,0 +1,38 @@ +import { Ticket } from "../types/Ticket"; +import { Project } from "../types/Project"; +import { AppFile } from "../types/AppFile"; +import { Activity } from "../types/Activity"; +import { User } from "../types/User"; +import { getRemainingdays } from "../utils/methods"; + +export default class ProjectVM { + public id: number; + public title: string; + public description: string; + public value: number; + public tickets: Ticket[]; + public users: User[]; + public ticketsTotalCount: number; + public ticketsDone: number; + public remainingDays: number; + public files: AppFile[]; + public activities: Activity[]; + + public constructor(project: Project) { + this.id = project.id; + this.title = project.title; + this.description = project.description; + this.users = project.users; + this.value = project.progression; + this.tickets = project.tickets; + this.ticketsTotalCount = + this.tickets === undefined ? 0 : this.tickets.length; + this.ticketsDone = + this.tickets === undefined + ? 0 + : this.tickets.filter(t => t.status === "Done").length; + this.files = project.files; + this.activities = project.activities; + this.remainingDays = getRemainingdays(project.plannedEnding); + } +} diff --git a/client/src/viewModels/TicketVM.ts b/client/src/VM/TicketVM.ts similarity index 100% rename from client/src/viewModels/TicketVM.ts rename to client/src/VM/TicketVM.ts diff --git a/client/src/viewModels/UserVM.ts b/client/src/VM/UserVM.ts similarity index 100% rename from client/src/viewModels/UserVM.ts rename to client/src/VM/UserVM.ts diff --git a/client/src/components/ActivityCollection.tsx b/client/src/components/ActivityCollection.tsx new file mode 100644 index 0000000..b37d8ac --- /dev/null +++ b/client/src/components/ActivityCollection.tsx @@ -0,0 +1,49 @@ +import React, { FC } from "react"; +import { Activity } from "../types/Activity"; + +type IProps = { + activities: Activity[]; + filterText: string; +}; + +export const ActivityCollection: FC = ({ activities, filterText }) => { + return activities === undefined ? ( + <> + ) : ( + <> +
    + {activities + .filter( + a => + a.description.toLowerCase().includes(filterText.toLowerCase()) || + a.user.firstName + .toLowerCase() + .includes(filterText.toLowerCase()) || + a.ticket.title.toLowerCase().includes(filterText.toLowerCase()) + ) + .map((activity: Activity) => ( +
  • + +
  • + ))} +
+ + ); +}; + +type IFProps = { + activity: Activity; +}; + +export const ActivityEntry: FC = ({ activity }) => { + return ( + <> + + {/* folder */} + + {activity.user.firstName} {activity.description} {activity.ticket.title} + +

{activity.date.toDateString()}

+ + ); +}; diff --git a/client/src/components/ActivityList.tsx b/client/src/components/ActivityList.tsx new file mode 100644 index 0000000..5f597c0 --- /dev/null +++ b/client/src/components/ActivityList.tsx @@ -0,0 +1,34 @@ +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; +import { ActivityCollection } from "./ActivityCollection"; +import { Activity } from "../types/Activity"; +import { FilterBar } from "./FilterBar"; + +type IProps = { + activities: Activity[]; +}; + +export const ActivityList: FC = ({ activities }) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { + setFilterText(""); + }; + const handleChange: (e: ChangeEvent) => void = ( + e: ChangeEvent + ) => { + setFilterText(e.target.value); + }; + + return ( + <> +
+

Activity

+ +
+ + + ); +}; diff --git a/client/src/components/AppFileList.tsx b/client/src/components/AppFileList.tsx new file mode 100644 index 0000000..fd4cd63 --- /dev/null +++ b/client/src/components/AppFileList.tsx @@ -0,0 +1,35 @@ +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; +import { AppFile } from "../types/AppFile"; +import { FileCollection } from "./FileCollection"; +import { InputFile } from "./InputFile"; +import { FilterBar } from "./FilterBar"; + +type IProps = { + files: AppFile[]; +}; + +export const FileList: FC = ({ files }) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { + setFilterText(""); + }; + const handleChange: (e: ChangeEvent) => void = ( + e: ChangeEvent + ) => { + setFilterText(e.target.value); + }; + return ( + <> +
+

Files

+ +
+ + + + ); +}; diff --git a/client/src/components/AvatarList.tsx b/client/src/components/AvatarList.tsx index 7075728..33dc256 100644 --- a/client/src/components/AvatarList.tsx +++ b/client/src/components/AvatarList.tsx @@ -1,15 +1,24 @@ import React, { FC } from "react"; -import { FloatingButton } from "./FloatingButton"; +import { User } from "../types/User"; interface AvatarListProps { - avatars: string[]; + users: User[]; } -export const AvatarList: FC = ({ avatars }) => { - return ( +export const AvatarList: FC = ({ users }) => { + return users === undefined ? ( + <> + ) : ( <> - {avatars.map((avatar: string) => ( - + {users.map((user: User, i: number) => ( + {user.fullName} ))} ); diff --git a/client/src/components/Button.tsx b/client/src/components/Button.tsx index 0a42e10..bb3c49b 100644 --- a/client/src/components/Button.tsx +++ b/client/src/components/Button.tsx @@ -1,4 +1,4 @@ -import React, { FC, Children } from "react"; +import React, { FC, MouseEvent } from "react"; interface IProps { icon?: string; @@ -6,18 +6,20 @@ interface IProps { shape?: string; color?: string; text?: string; + onClick?: (e: MouseEvent) => void; } export const Button: FC = ({ size = "small", shape = "", color, - text, + onClick, children }) => { return ( diff --git a/client/src/components/FileCollection.tsx b/client/src/components/FileCollection.tsx new file mode 100644 index 0000000..114ae98 --- /dev/null +++ b/client/src/components/FileCollection.tsx @@ -0,0 +1,45 @@ +import React, { FC } from "react"; +import { AppFile } from "../types/AppFile"; + +type IProps = { + files: AppFile[]; + filterText: string; +}; + +export const FileCollection: FC = ({ files, filterText }) => { + return ( + <> +
    + {files + .filter( + f => + f.name.toLowerCase().includes(filterText.toLowerCase()) || + f.format.toLowerCase().includes(filterText.toLowerCase()) + ) + .map((file: AppFile) => ( + + ))} +
+ + ); +}; + +type IFProps = { + file: AppFile; +}; + +export const FileEntry: FC = ({ file }) => { + return ( +
  • + {/* */} + folder + {file.name} +

    + {file.size}kb {file.format} +

    + + more_vert + +
  • + ); +}; diff --git a/client/src/components/FilterBar.tsx b/client/src/components/FilterBar.tsx new file mode 100644 index 0000000..fc9cb9c --- /dev/null +++ b/client/src/components/FilterBar.tsx @@ -0,0 +1,41 @@ +import React, { FC, ChangeEvent, MouseEvent } from "react"; +import { useRouteMatch } from "react-router-dom"; + +type IProps = { + filterText: string; + handleChange: (e: ChangeEvent) => void; + clearFilterText: (e: MouseEvent) => void; +}; + +export const FilterBar: FC = ({ + filterText, + handleChange, + clearFilterText +}) => { + const { url } = useRouteMatch(); + const placeholder: string = url.split("/")[3] || "users"; + return ( + <> +
    +
    + + + + close + +
    +
    + + ); +}; diff --git a/client/src/components/FloatingButton.tsx b/client/src/components/FloatingButton.tsx index eb9dfd1..0031443 100644 --- a/client/src/components/FloatingButton.tsx +++ b/client/src/components/FloatingButton.tsx @@ -1,20 +1,22 @@ -import React, { FC } from "react"; +import React, { FC, MouseEvent } from "react"; import { Button } from "./Button"; interface IProps { icon?: string; size?: string; color?: string; + onClick?: (e: MouseEvent) => void; } export const FloatingButton: FC = ({ icon = "add", size = "small", - color = "red" + color = "red", + onClick }) => { const iconComponent = {icon}; return ( - ); diff --git a/client/src/components/HorizontalCard.tsx b/client/src/components/HorizontalCard.tsx index a0d20dd..f9b7f2c 100644 --- a/client/src/components/HorizontalCard.tsx +++ b/client/src/components/HorizontalCard.tsx @@ -1,54 +1,44 @@ import React, { FC, MouseEvent } from "react"; -import { AvatarList } from "./AvatarList"; +import { Link } from "react-router-dom"; +import { getRemainingdays } from "../utils/methods"; interface IProps { title: string; - tasksTotalCount?: number; - tasksDone?: number; - remainingDays?: number; - avatars: string[]; + remainingDays: string; validateTicket: (event: MouseEvent) => void; archiveTicket: (event: MouseEvent) => void; } export const HorizontalCard: FC = ({ title, - tasksDone, - tasksTotalCount, remainingDays, - avatars, archiveTicket, validateTicket }) => { return ( -
    -
    -
    -
    -
    -
    -
    {title}
    -
    - Due {remainingDays} days - {/* */} -
    - {/* playlist_add_check - - {" "} - {tasksDone}/{tasksTotalCount} - */} - - - - check - - - - - archive - - -
    +
    +
    +
    +
    +
    +
    + + {title} + +
    +
    + Due {getRemainingdays(remainingDays)} days +
    + + + check + + + + + archive + +
    diff --git a/client/src/components/InputFile.tsx b/client/src/components/InputFile.tsx new file mode 100644 index 0000000..aa528e9 --- /dev/null +++ b/client/src/components/InputFile.tsx @@ -0,0 +1,29 @@ +import React, { FC } from "react"; + +type IProps = {}; + +export const InputFile: FC = () => { + return ( + <> +
    +
    +
    + cloud_upload + +
    +
    + +
    +
    +
    + + ); +}; diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx new file mode 100644 index 0000000..73595e6 --- /dev/null +++ b/client/src/components/Modal.tsx @@ -0,0 +1,24 @@ +import React, { FC, useState, CSSProperties } from "react"; + +interface IProps { + handleClose: () => void; + show: boolean; +} +export const Modal: FC = ({ handleClose, show, children }) => { + const showHideStyle: CSSProperties = show + ? { display: "block", zIndex: 10 } + : { display: "none", zIndex: 10 }; + return ( +
    +
    {children}
    +
    + +
    +
    + ); +}; diff --git a/client/src/components/Preloader.tsx b/client/src/components/Preloader.tsx new file mode 100644 index 0000000..5c77cca --- /dev/null +++ b/client/src/components/Preloader.tsx @@ -0,0 +1,55 @@ +import React, { FC } from "react"; + +export const Preloader: FC = () => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/client/src/components/ProgressBar.tsx b/client/src/components/ProgressBar.tsx index a9324bb..1095cc9 100644 --- a/client/src/components/ProgressBar.tsx +++ b/client/src/components/ProgressBar.tsx @@ -1,4 +1,4 @@ -import React, { FC, HTMLAttributes, CSSProperties } from "react"; +import React, { FC, CSSProperties } from "react"; type ProgressBarProps = { value: number; diff --git a/client/src/components/TabRouter.tsx b/client/src/components/TabRouter.tsx index 7a5c217..3b3915e 100644 --- a/client/src/components/TabRouter.tsx +++ b/client/src/components/TabRouter.tsx @@ -1,52 +1,47 @@ import React, { FC } from "react"; import { TabRouterHeader } from "./TabRouterHeader"; import { TicketList } from "./TicketList"; +import { FileList } from "./AppFileList"; import { Ticket } from "../types/Ticket"; -import { Switch, Route, useRouteMatch, Redirect } from "react-router-dom"; +import { AppFile } from "../types/AppFile"; +import { Route, useRouteMatch, Redirect } from "react-router-dom"; +import { ActivityList } from "./ActivityList"; +import { Activity } from "../types/Activity"; interface IProps { tickets: Ticket[]; - tasksTotalCount?: number; - tasksDone?: number; remainingDays?: number; - avatars: string[]; + tabNames: string[]; + files: AppFile[]; + activities: Activity[]; } export const TabRouter: FC = ({ tickets, - tasksDone, - tasksTotalCount, - remainingDays, - avatars + tabNames, + files, + activities }) => { const { url } = useRouteMatch(); return ( <> - -
    - +
    + - + - - - + + + - - {/* */} - + + + - - {/* */} - -
    - + + + +
    ); }; diff --git a/client/src/components/TabRouterHeader.tsx b/client/src/components/TabRouterHeader.tsx index b21eb11..5a95261 100644 --- a/client/src/components/TabRouterHeader.tsx +++ b/client/src/components/TabRouterHeader.tsx @@ -1,12 +1,50 @@ import React, { FC, useState } from "react"; import { Link, useRouteMatch } from "react-router-dom"; +interface IProps { + tabClass?: string; + tabNames: string[]; +} + +export const TabRouterHeader: FC = ({ + tabClass = "tab col s4", + tabNames +}) => { + const [isActive, setIsActive] = useState(0); + const nTabs = tabNames.length; + return ( + <> +
      + {tabNames.map((name, i) => ( + + ))} +
    • +
    + + ); +}; + interface TabUnitProps { tabClass: string; isActive: number; setIsActive: React.Dispatch>; text: string; value: string; + nTabs: number; } const TabUnit: FC = ({ @@ -14,15 +52,23 @@ const TabUnit: FC = ({ isActive, setIsActive, text, - value + value, + nTabs }) => { const { url } = useRouteMatch(); return ( -
  • +
  • setIsActive(parseInt(value))} > {text} @@ -30,50 +76,3 @@ const TabUnit: FC = ({
  • ); }; - -interface IProps { - tabClass?: string; -} - -export const TabRouterHeader: FC = ({ - tabClass = "tab col s3", - - children -}) => { - const [isActive, setIsActive] = useState(1); - - // const switchTab = (e: React.MouseEvent): void => { - // e.preventDefault(); - // setIsActive(e.target.id); - // }; - - return ( - <> -
    -
      - - - -
    -
    - - ); -}; diff --git a/client/src/components/TicketList.tsx b/client/src/components/TicketList.tsx index b7835a0..d1be3c3 100644 --- a/client/src/components/TicketList.tsx +++ b/client/src/components/TicketList.tsx @@ -1,52 +1,62 @@ -import React, { FC } from "react"; +import React, { FC, useState, ChangeEvent, MouseEvent } from "react"; import { Ticket } from "../types/Ticket"; import { FloatingButton } from "./FloatingButton"; import { HorizontalCard } from "./HorizontalCard"; +import { FilterBar } from "./FilterBar"; type TicketListProps = { tickets: Ticket[]; - tasksTotalCount?: number; - tasksDone?: number; - remainingDays?: number; - avatars: string[]; }; -export const TicketList: FC = ({ - tickets, - tasksDone, - tasksTotalCount, - remainingDays, - avatars -}) => { +export const TicketList: FC = ({ tickets }) => { + const [filterText, setFilterText] = useState(""); + const clearFilterText: (e: MouseEvent) => void = (e: MouseEvent) => { + setFilterText(""); + }; const archiveTicket = () => {}; const validateTicket = () => {}; + const onClick: (e: MouseEvent) => void = (e: MouseEvent) => { + e.preventDefault(); + }; + const handleChange: (e: ChangeEvent) => void = ( + e: ChangeEvent + ) => { + setFilterText(e.target.value); + }; return ( -
    + <>
    -
    -

    Tickets

    -
    -
    - -
    +

    Tickets

    + +
    - -
      - {tickets.map((t: Ticket) => ( -
    • - -
    • - ))} -
    -
    +
    +
      + {tickets + .filter(t => + t.title.toLowerCase().includes(filterText.toLowerCase()) + ) + .map((t: Ticket) => ( +
    • + +
    • + ))} +
    +
    + ); }; diff --git a/client/src/components/UsersModal.tsx b/client/src/components/UsersModal.tsx new file mode 100644 index 0000000..0cf92a5 --- /dev/null +++ b/client/src/components/UsersModal.tsx @@ -0,0 +1,103 @@ +import React, { FC, useState, ChangeEvent, useEffect } from "react"; +import { Modal } from "./Modal"; +import { AvatarList } from "./AvatarList"; +import { User } from "../types/User"; +import { FilterBar } from "./FilterBar"; +import { HttpResponse } from "../types/HttpResponse"; +import { get } from "../utils/http"; +import { Constants } from "../utils/Constants"; + +interface IProps { + show: boolean; + handleClose: () => void; + users: User[]; +} + +export const UsersModal: FC = ({ show, handleClose, users }) => { + const [filterText, setFilterText] = useState(""); + const handleChange: (e: ChangeEvent) => void = ( + e: ChangeEvent + ) => { + setFilterText(e.target.value); + }; + const [allUsers, setAllUsers] = useState(); + + async function httpGet(): Promise { + try { + const response: HttpResponse = await get( + `${Constants.usersURI}` + ); + if (response.parsedBody !== undefined) { + setAllUsers(response.parsedBody); + // setIsLoading(false); + } + } catch (ex) { + // setHasError(true); + // setError(ex); + } + } + + useEffect(() => { + // if (id !== undefined) { + httpGet(); + // } else { + // setHasError(true); + // setError("Bad Request"); + // } + }, []); + + return ( + +
    +
    +

    Manage users

    +
    +
    + + close + +
    +
    +
    + + setFilterText("")} + handleChange={handleChange} + /> +
    + {/*
    {allUsers}
    */} +
    +
      + {users.map((u: User) => ( +
    • +
      + false} + // checked + /> + + {u.fullName} + {u.fullName} + +
      +
    • + ))} +
    +
    +
    + ); +}; diff --git a/client/src/controllers/ErrorController.tsx b/client/src/controllers/ErrorController.tsx new file mode 100644 index 0000000..4376691 --- /dev/null +++ b/client/src/controllers/ErrorController.tsx @@ -0,0 +1,19 @@ +import React, { FC } from "react"; +import { Redirect } from "react-router-dom"; + +interface IProps { + error: any; +} + +export const ErrorController: FC = ({ error }) => { + switch (error) { + case "Bad Request": + return ; + + case "Not Found": + return ; + + default: + return ; + } +}; diff --git a/client/src/controllers/ProjectController.tsx b/client/src/controllers/ProjectController.tsx index bc97254..e6d37fe 100644 --- a/client/src/controllers/ProjectController.tsx +++ b/client/src/controllers/ProjectController.tsx @@ -1,80 +1,48 @@ import React, { FC, useState, useEffect } from "react"; import { useParams } from "react-router-dom"; +import { ErrorController } from "./ErrorController"; import { ProjectPage } from "../pages/ProjectPage"; -import ProjectVM from "../viewModels/ProjectVM"; -import { Constants } from "../utils/Constants"; +import ProjectVM from "../VM/ProjectVM"; import { Project } from "../types/Project"; -import { Ticket } from "../types/Ticket"; -import { User } from "../types/User"; +import { HttpResponse } from "../types/HttpResponse"; +import { Preloader } from "../components/Preloader"; +import { Constants } from "../utils/Constants"; +import { get } from "../utils/http"; export const ProjectController: FC = () => { - // const [project, setProject] = useState({} as Project); - const [isLoading, setIsLoading] = useState(false); + const [project, setProject] = useState({} as Project); + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + const [error, setError] = useState(""); + const { id } = useParams(); - // const { id } = useParams(); - - // const getProject: Function = (id: number) => { - // fetch(`${Constants.getProjectURI}/${id}`) - // .then(res => res.json()) - // .catch(err => console.log(err)) - // .then(data => setProject(data)) - // .finally(() => setIsLoading(false)); - // }; - - // useEffect(() => { - // getProject(id); - // }, []); - - // const viewModel = new ProjectVM(project); - // console.log(viewModel.getMembers()); - - const tickets: Ticket[] = [ - { - id: 1, - title: "Ticket #1", - status: "Done" - }, - { - id: 2, - title: "Ticket #2", - status: "To Do" + async function httpGet(id: string): Promise { + try { + const response: HttpResponse = await get( + `${Constants.projectsURI}/${id}` + ); + if (response.parsedBody !== undefined) { + setProject(response.parsedBody); + setIsLoading(false); + } + } catch (ex) { + setHasError(true); + setError(ex); } - ]; + } - const users: User[] = [ - { - id: "1", - picture: - "data:image/jpeg;base64,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" - }, - { - id: "2", - picture: - "data:image/png;base64,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" - }, - { - id: "3", - picture: - "data:image/jpeg;base64,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" - }, - { - id: "4", - picture: - "data:image/jpeg;base64,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" + useEffect(() => { + if (id !== undefined) { + httpGet(id); + } else { + setHasError(true); + setError("Bad Request"); } - ]; - - const project: Project = { - id: 1, - title: "Project Title", - description: "What is it about", - progression: 25, - tickets: tickets, - users: users, - plannedEnding: "2020-02-17 15:51:02.787373" - }; + }, [id]); + if (hasError) { + return ; + } const viewModel = new ProjectVM(project); - - return isLoading ?

    Loading ...

    : ; + return isLoading ? : ; }; diff --git a/client/src/controllers/TicketController.tsx b/client/src/controllers/TicketController.tsx index afc218e..5d3a949 100644 --- a/client/src/controllers/TicketController.tsx +++ b/client/src/controllers/TicketController.tsx @@ -4,3 +4,108 @@ import { TicketPage } from "../pages/TicketPage"; export const TicketController: FC = () => { return ; }; + +// const viewModel = new ProjectVM(project); + +// const tickets: Ticket[] = [ +// { +// id: 1, +// title: "Client objective meeting", +// description: "Client objective meeting", +// plannedEnding: "2020-02-17 15:51:02.787373", +// status: "Done" +// }, +// { +// id: 2, +// title: "Assemble Outcomes Report for client", +// description: "Assemble Outcomes Report for client", +// plannedEnding: "2020-02-27 15:51:02.787373", +// status: "To Do" +// } +// ]; + +// const users: User[] = [ +// { +// id: "1", +// firstName: "PacMan", +// picture: +// "data:image/jpeg;base64,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" +// }, +// { +// id: "2", +// firstName: "Avatar", +// picture: +// "data:image/png;base64,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" +// }, +// { +// id: "3", +// firstName: "Zoey", +// picture: +// "data:image/jpeg;base64,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" +// }, +// { +// id: "4", +// firstName: "Kory", +// picture: +// "data:image/jpeg;base64,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" +// } +// ]; + +// const files: AppFile[] = [ +// { +// id: 1, +// name: "SRS", +// description: "App blueprint", +// size: 15, +// format: "pdf" +// }, +// { +// id: 2, +// name: "Business Model Canvas", +// description: "Path to 1B$", +// size: 2100, +// format: "png" +// } +// ]; + +// const activities: Activity[] = [ +// { +// id: 1, +// description: " completed the task ", +// date: new Date(), +// user: users[0], +// ticket: tickets[0] +// }, +// { +// id: 2, +// description: " added the task ", +// date: new Date(), +// user: users[1], +// ticket: tickets[1] +// }, +// { +// id: 3, +// description: " rescheduled the task ", +// date: new Date(), +// user: users[2], +// ticket: tickets[1] +// }, +// { +// id: 4, +// description: " added the task ", +// date: new Date(), +// user: users[3], +// ticket: tickets[0] +// } +// ]; +// const project: Project = { +// id: 1, +// title: "Project Title", +// description: "What is it about", +// progression: 25, +// tickets: tickets, +// users: users, +// plannedEnding: "2020-02-17 15:51:02.787373", +// files: files, +// activities: activities +// }; diff --git a/client/src/index.css b/client/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/client/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/client/src/index.tsx b/client/src/index.tsx index 90fb3f6..8780a3a 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,6 +1,5 @@ import React from "react"; import ReactDOM from "react-dom"; -import "./index.css"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; diff --git a/client/src/pages/NotFoundPage.tsx b/client/src/pages/NotFoundPage.tsx new file mode 100644 index 0000000..a13397c --- /dev/null +++ b/client/src/pages/NotFoundPage.tsx @@ -0,0 +1,10 @@ +import React, { FC } from "react"; + +interface IProps {} +export const NotFoundPage: FC = () => { + return ( +
    +

    error

    +
    + ); +}; diff --git a/client/src/pages/ProjectPage.tsx b/client/src/pages/ProjectPage.tsx index 5ce7d1e..43528c7 100644 --- a/client/src/pages/ProjectPage.tsx +++ b/client/src/pages/ProjectPage.tsx @@ -1,32 +1,50 @@ -import React, { FC } from "react"; +import React, { FC, useState } from "react"; +import ProjectVM from "../VM/ProjectVM"; import { Header } from "../components/Header"; import { AvatarList } from "../components/AvatarList"; import { ProgressBar } from "../components/ProgressBar"; -import ProjectVM from "../viewModels/ProjectVM"; import { TabRouter } from "../components/TabRouter"; import { FloatingButton } from "../components/FloatingButton"; +import { UsersModal } from "../components/UsersModal"; interface IProps { viewModel: ProjectVM; } + export const ProjectPage: FC = ({ viewModel }) => { const { title, description, - avatars, + users, value, tickets, ticketsDone, ticketsTotalCount, - remainingDays + remainingDays, + files, + activities } = viewModel; + + const tabNames: string[] = ["Tickets", "Files", "Activity"]; + const [showModal, setShowModal] = useState(false); + return (
    - - + + setShowModal(true)} + /> + setShowModal(false)} + />
    = ({ viewModel }) => { remainingDays={remainingDays} />
    diff --git a/client/src/pages/TicketPage.tsx b/client/src/pages/TicketPage.tsx index 81e1468..4036985 100644 --- a/client/src/pages/TicketPage.tsx +++ b/client/src/pages/TicketPage.tsx @@ -10,7 +10,7 @@ export const TicketPage: FC = () => { description="Research, ideate and present brand concepts for client consideration" title="Brand Concept and Design" /> - + {/* */} {/* // // diff --git a/client/src/react-app-env.d.ts b/client/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5..0000000 --- a/client/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/client/src/types/Activity.ts b/client/src/types/Activity.ts new file mode 100644 index 0000000..4fb8157 --- /dev/null +++ b/client/src/types/Activity.ts @@ -0,0 +1,10 @@ +import { User } from "./User"; +import { Ticket } from "./Ticket"; + +export interface Activity { + id: number; + description: string; + date: Date; + user: User; + ticket: Ticket; +} diff --git a/client/src/types/AppFile.ts b/client/src/types/AppFile.ts new file mode 100644 index 0000000..6fc9cec --- /dev/null +++ b/client/src/types/AppFile.ts @@ -0,0 +1,9 @@ +import { User } from "./User"; + +export interface AppFile { + id: number; + name: string; + description: string; + format: string; + size: number; +} diff --git a/client/src/types/File.ts b/client/src/types/File.ts deleted file mode 100644 index f916c15..0000000 --- a/client/src/types/File.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface File { - Id: number; -} diff --git a/client/src/types/History.ts b/client/src/types/History.ts deleted file mode 100644 index 9f3fbf3..0000000 --- a/client/src/types/History.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface History { - Id: number; -} diff --git a/client/src/types/HttpResponse.ts b/client/src/types/HttpResponse.ts new file mode 100644 index 0000000..a5825c0 --- /dev/null +++ b/client/src/types/HttpResponse.ts @@ -0,0 +1,3 @@ +export interface HttpResponse extends Response { + parsedBody?: T; +} diff --git a/client/src/types/Project.ts b/client/src/types/Project.ts index 9b41fcc..f6f3eb9 100644 --- a/client/src/types/Project.ts +++ b/client/src/types/Project.ts @@ -1,12 +1,19 @@ import { Ticket } from "./Ticket"; import { User } from "./User"; +import { AppFile } from "./AppFile"; +import { Activity } from "./Activity"; export interface Project { id: number; title: string; description: string; - progression: number; - tickets: Ticket[]; - users: User[]; + createdAt: string; plannedEnding: string; + progression: number; + status: string; + manager: User; + users: User[]; + tickets: Ticket[]; + files: AppFile[]; + activities: Activity[]; } diff --git a/client/src/types/Ticket.ts b/client/src/types/Ticket.ts index a258326..7d675b1 100644 --- a/client/src/types/Ticket.ts +++ b/client/src/types/Ticket.ts @@ -1,5 +1,7 @@ export interface Ticket { id: number; title: string; + description: string; status: string; + plannedEnding: string; } diff --git a/client/src/types/User.ts b/client/src/types/User.ts index b31c2e3..6869cd4 100644 --- a/client/src/types/User.ts +++ b/client/src/types/User.ts @@ -1,4 +1,6 @@ export interface User { id: string; picture: string; + firstName: string; + fullName?: string; } diff --git a/client/src/utils/Constants.ts b/client/src/utils/Constants.ts index f889fc4..cdfb2b9 100644 --- a/client/src/utils/Constants.ts +++ b/client/src/utils/Constants.ts @@ -1,3 +1,5 @@ export class Constants { - static getProjectURI: string = "/api/projects"; + static projectsURI: string = "/api/v1/projects"; + static ticketsURI: string = "/api/v1/tickets"; + static usersURI: string = "/api/v1/users"; } diff --git a/client/src/utils/http.ts b/client/src/utils/http.ts new file mode 100644 index 0000000..2ba9a2d --- /dev/null +++ b/client/src/utils/http.ts @@ -0,0 +1,35 @@ +import { HttpResponse } from "../types/HttpResponse"; + +export async function http(request: RequestInfo): Promise> { + const response: HttpResponse = await fetch(request); + try { + response.parsedBody = await response.json(); + } catch (ex) {} + if (!response.ok) { + throw response.statusText; + } + return response; +} + +export async function get( + path: string, + args: RequestInit = { method: "get" } +): Promise> { + return await http(new Request(path, args)); +} + +export async function post( + path: string, + body: any, + args: RequestInit = { method: "post", body: JSON.stringify(body) } +): Promise> { + return await http(new Request(path, args)); +} + +export async function put( + path: string, + body: any, + args: RequestInit = { method: "put", body: JSON.stringify(body) } +): Promise> { + return await http(new Request(path, args)); +} diff --git a/client/src/utils/methods.ts b/client/src/utils/methods.ts new file mode 100644 index 0000000..8051548 --- /dev/null +++ b/client/src/utils/methods.ts @@ -0,0 +1,7 @@ +export const getRemainingdays: (endDate: string) => number = ( + endDate: string +) => { + let endingDate: Date = new Date(endDate); + let today: Date = new Date(); + return Math.abs(endingDate.getDate() - today.getDate()); +}; diff --git a/client/src/utils/router.tsx b/client/src/utils/router.tsx index a90cafb..2d37f3e 100644 --- a/client/src/utils/router.tsx +++ b/client/src/utils/router.tsx @@ -1,19 +1,31 @@ import React from "react"; -import { Router, Route, Switch, Link, NavLink } from "react-router-dom"; +import { + Router, + Route, + Switch + // Redirect + //Link, NavLink +} from "react-router-dom"; import * as creacteHistory from "history"; -import { TicketPage } from "../pages/TicketPage"; -import { HomeController } from "../controllers/HomeController"; +// import { TicketPage } from "../pages/TicketPage"; +// import { HomeController } from "../controllers/HomeController"; import { ProjectController } from "../controllers/ProjectController"; -import { UserController } from "../controllers/UserController"; -import { TicketController } from "../controllers/TicketController"; +import { NotFoundPage } from "../pages/NotFoundPage"; +import { TestPage } from "../pages/TestPage"; +// import { UserController } from "../controllers/UserController"; +// import { TicketController } from "../controllers/TicketController"; export const history = creacteHistory.createBrowserHistory(); export const AppRouter = () => { return ( -
    +
    + + + + {/* @@ -26,6 +38,14 @@ export const AppRouter = () => { {/* */} + + + + + + {/* + + */}
    diff --git a/client/src/viewModels/ProjectVM.ts b/client/src/viewModels/ProjectVM.ts deleted file mode 100644 index 7dd2cf2..0000000 --- a/client/src/viewModels/ProjectVM.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Ticket } from "../types/Ticket"; -import { Project } from "../types/Project"; -import { Constants } from "../utils/Constants"; -import { User } from "../types/User"; - -export default class ProjectVM { - public id: number; - public title: string; - public description: string; - public value: number; - public tickets: Ticket[]; - public avatars: string[]; - public ticketsTotalCount: number; - public ticketsDone: number; - public remainingDays: number; - - /** - * getMembers - */ - // public getMembers(): string { - // let res: Promise = fetch( - // `${Constants.getProjectURI}/${this.id}/members` - // ); - // return JSON.stringify(res); - // // res.json(); - // } - - public constructor(project: Project) { - this.id = project.id; - this.title = project.title; - this.description = project.description; - this.avatars = project.users.map(u => u.picture); - this.value = project.progression; - this.tickets = project.tickets; - this.ticketsTotalCount = this.tickets.length; - this.ticketsDone = this.tickets.filter(t => t.status === "Done").length; - - let endingDate: Date = new Date(project.plannedEnding); - let today: Date = new Date(); - let plannedEnding: number = Math.abs( - endingDate.getDate() - today.getDate() - ); - - this.remainingDays = plannedEnding; - } -}