diff --git a/.gitignore b/.gitignore index b033101..f7577c8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,29 @@ Migrations/ app.db* .DS_Store app.db +Data/Interfaces +Data/UnitOfWork.cs +Data/*Repository.cs + +# client +client/src/pages/TestPage.tsx +client/react-app-env.d.ts client/node_modules -client/src/pages/TestPage.tsx \ No newline at end of file +client/.pnp +client/.pnp.js + +# testing +client/coverage + +# production +client/build + +# misc +client/.DS_Store +client/.env.local +client/.env.development.local +client/.env.test.local +client/.env.production.local +client/npm-debug.log* +client/yarn-debug.log* +client/yarn-error.log* diff --git a/Controllers/AppUsersController.cs b/Controllers/AppUsersController.cs index d3ed49a..98262ef 100644 --- a/Controllers/AppUsersController.cs +++ b/Controllers/AppUsersController.cs @@ -1,38 +1,78 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using TicketManager.Data; using TicketManager.Models; -using Microsoft.AspNetCore.Authorization; +using TicketManager.DTO; + namespace TicketManager.Controllers { // [Authorize] + [Produces("application/json")] [Route("api/v1/users")] [ApiController] public class UsersController : ControllerBase { - private readonly IAppUserRepository _users; + private readonly AppDbContext _context; - public UsersController(IAppUserRepository users) + public UsersController(AppDbContext context) { - _users = users; + _context = context; } - // GET: api/Users + /// + /// Returns all Users stored in the database. + /// + /// + /// Sample request: + /// + /// GET: api/v1/Users + /// + /// + /// Returns a list of users [HttpGet] - public async Task> GetUsers() + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> GetUsers() { - return await _users.List(); + return await _context.AppUsers + .Include(u => u.Assignments) + .ThenInclude(a => a.Project) + .Include(u => u.Activities) + .AsNoTracking() + .Select(u => new AppUserDTO(u)) + .ToListAsync(); } - // GET: api/Users/5 + /// + /// Locate a specific User stored in the database by its Id + /// + /// + /// Sample request: + /// + /// GET: api/v1/Users/2 + /// + /// + /// Returns a User object + /// If the required User is null [HttpGet("{id}")] - public async Task> GetUser(Guid id) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetUser(Guid id) { - var user = await _users.GetUser(id); + var user = await _context.AppUsers + .Include(u => u.Assignments) + .ThenInclude(a => a.Project) + .Include(u => u.Activities) + .AsNoTracking() + .Select(u => new AppUserDTO(u)) + .FirstOrDefaultAsync(u => u.Id == id); + if (user == null) { return NotFound(); @@ -40,23 +80,44 @@ namespace TicketManager.Controllers return user; } - // PUT: api/Users/5 - // To protect from overposting attacks, please enable the specific properties you want to bind to, for - // more details see https://aka.ms/RazorPagesCRUD. + /// + /// 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 PutUser(Guid id, AppUser user) { if (id != user.Id) { return BadRequest(); } + + _context.Entry(user).State = EntityState.Modified; + try { - await _users.Update(user); + await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { - if (!_users.Exists(id)) + if (!UserExists(id)) { return NotFound(); } @@ -68,49 +129,101 @@ namespace TicketManager.Controllers return NoContent(); } - // POST: api/Users - // To protect from overposting attacks, please enable the specific properties you want to bind to, for - // more details see https://aka.ms/RazorPagesCRUD. + /// + /// 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] - public async Task> PostUser(AppUser user) + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> PostUser(AppUser user) { - await _users.Add(user); - return CreatedAtAction("GetUser", new { id = user.Id }, user); + if (!ModelState.IsValid) + { + return BadRequest(); + } + + _context.AppUsers.Add(user); + await _context.SaveChangesAsync(); + + var dto = new AppUserDTO(user); + + return CreatedAtAction("GetUser", new { id = user.Id }, dto); } - // DELETE: api/Users/5 + /// + /// 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> DeleteUser(Guid id) + public async Task> DeleteUser(Guid id) { - var user = await _users.GetUser(id); + var user = await _context.AppUsers.FindAsync(id); if (user == null) { return NotFound(); } - await _users.Delete(user); - return user; + _context.AppUsers.Remove(user); + await _context.SaveChangesAsync(); + var dto = new AppUserDTO(user); + return Ok(user); } [HttpGet("{id}/projects")] - public async Task>> GetAppUserProjects(Guid id) + public async Task>> GetAppUserProjects(Guid id) { - AppUser user = await _users.GetUser(id); + var user = await _context.AppUsers + .Include(u => u.Assignments) + .ThenInclude(a => a.Project) + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == id); if (user == null) { return BadRequest(); } - return user.GetProjects(); + return user.GetProjects().Select(p => new ProjectDTO(p)).ToList(); } [HttpGet("{id}/tickets/")] - public async Task>> GetAppUserTickets(Guid id) + public async Task>> GetAppUserTickets(Guid id) { - AppUser user = await _users.GetUser(id); + var user = await _context.AppUsers + .Include(u => u.Assignments) + .ThenInclude(a => a.Project) + .ThenInclude(p => p.Tickets) + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == id); if (user == null) { return BadRequest(); } - return user.GetTickets(); + return user.GetTickets().Select(t => new TicketDTO(t)).ToList(); + } + + private bool UserExists(Guid id) + { + return _context.AppUsers.Any(e => e.Id == id); } } } diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index 82a1ee9..bc9543b 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -18,10 +18,11 @@ namespace TicketManager.Controllers [ApiController] public class ProjectsController : ControllerBase { - private IProjectRepository _projects; - public ProjectsController(IProjectRepository context) + private readonly AppDbContext _context; + + public ProjectsController(AppDbContext context) { - _projects = context; + _context = context; } /// @@ -36,9 +37,18 @@ namespace TicketManager.Controllers /// Returns a list of projects [HttpGet] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetProjects() + public async Task> GetProjects() { - return await _projects.List(); + return await _context.Projects + .Include(p => p.Assignments) + .ThenInclude(a => a.User) + .Include(p => p.Tickets) + .Include(p => p.Manager) + .Include(p => p.Files) + .Include(p => p.Activities) + .AsNoTracking() + .Select(p => new ProjectDTO(p)) + .ToListAsync(); } /// @@ -58,12 +68,22 @@ namespace TicketManager.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetProject(int id) { - Project project = await _projects.Get(id); + var project = await _context.Projects + .Include(p => p.Assignments) + .ThenInclude(a => a.User) + .Include(p => p.Tickets) + .Include(p => p.Manager) + .Include(p => p.Files) + .Include(p => p.Activities) + .AsNoTracking() + .Select(p => new ProjectDTO(p)) + .FirstOrDefaultAsync(p => p.Id == id); + if (project == null) { return NotFound(); } - return new ProjectDTO(project); + return project; } /// @@ -90,15 +110,25 @@ namespace TicketManager.Controllers [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task PutProject(int id, Project project) { - if (id != project.Id) { return BadRequest(); } + if (id != project.Id) + { + return BadRequest(); + } + _context.Entry(project).State = EntityState.Modified; try { - await _projects.Update(project); + await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { - if (!_projects.Exists(id)) { return NotFound(); } - else { throw; } + if (!ProjectExists(id)) + { + return NotFound(); + } + else + { + throw; + } } return NoContent(); } @@ -123,11 +153,16 @@ namespace TicketManager.Controllers [HttpPost] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task> PostProject(Project project) + public async Task> PostProject(Project project) { - if (!ModelState.IsValid) { return BadRequest(); } - await _projects.Add(project); - return CreatedAtAction("GetProject", new { id = project.Id }, project); + if (!ModelState.IsValid) + { + return BadRequest(); + } + _context.Projects.Add(project); + await _context.SaveChangesAsync(); + var dto = new ProjectDTO(project); + return CreatedAtAction("GetProject", new { id = project.Id }, dto); } /// @@ -145,13 +180,15 @@ namespace TicketManager.Controllers [HttpDelete("{id}")] public async Task DeleteProject(int id) { - var project = await _projects.Get(id); + var project = await _context.Projects.FindAsync(id); if (project == null) { return NotFound(); } - await _projects.Delete(project); - return Ok(); + _context.Projects.Remove(project); + await _context.SaveChangesAsync(); + var dto = new ProjectDTO(project); + return Ok(dto); } /// @@ -169,9 +206,20 @@ namespace TicketManager.Controllers [HttpGet("{id}/members")] public async Task>> GetProjectMembers(int id) { - var project = await _projects.Get(id); + Project project = await _context.Projects + .Include(p => p.Assignments) + .ThenInclude(a => a.User) + .Include(p => p.Tickets) + .Include(p => p.Manager) + .Include(p => p.Files) + .Include(p => p.Activities) + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == id); + if (project == null) - { return NotFound(); } + { + return NotFound(); + } return project.GetMembers(); } @@ -198,15 +246,21 @@ namespace TicketManager.Controllers [HttpPatch("{id}/members")] public async Task> SetProjectMembers(int id, List projectMembers) { - Project project = await _projects.Get(id); + Project project = await _context.Projects + .Include(p => p.Assignments) + .FirstOrDefaultAsync(p => p.Id == id); + if (project == null) { return NotFound(); } + project.SetMembers(projectMembers); + _context.Entry(project).State = EntityState.Modified; try { - await _projects.Update(project); + + await _context.SaveChangesAsync(); } catch (DbUpdateException /* ex */) { @@ -218,86 +272,9 @@ namespace TicketManager.Controllers return NoContent(); } - // // /// - // // /// Assign a user to a project. - // // /// - // // /// - // // /// Sample request: - // // /// - // // /// POST: api/v1/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/v1/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(); - // // } + private bool ProjectExists(int id) + { + return _context.Projects.Any(e => e.Id == id); + } } } diff --git a/Controllers/TicketsController.cs b/Controllers/TicketsController.cs index 90844f0..cc94ec6 100644 --- a/Controllers/TicketsController.cs +++ b/Controllers/TicketsController.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using TicketManager.Data; +using TicketManager.DTO; using TicketManager.Models; namespace TicketManager.Controllers @@ -13,25 +15,40 @@ namespace TicketManager.Controllers [ApiController] public class TicketsController : ControllerBase { - private readonly ITicketRepository _tickets; + private readonly AppDbContext _context; - public TicketsController(ITicketRepository tickets) + public TicketsController(AppDbContext context) { - _tickets = tickets; + _context = context; } // GET: api/Tickets [HttpGet] - public async Task> GetTickets() + public async Task> GetTickets() { - return await _tickets.List(); + return await _context.Tickets + .Include(t => t.Project) + .Include(t => t.Files) + .Include(t => t.Activities) + .Include(t => t.Notes) + .AsNoTracking() + .Select(t => new TicketDTO(t)) + .ToListAsync(); } // GET: api/Tickets/5 [HttpGet("{id}")] - public async Task> GetTicket(int id) + public async Task> GetTicket(int id) { - var ticket = await _tickets.Get(id); + var ticket = await _context.Tickets + .Include(t => t.Project) + .Include(t => t.Files) + .Include(t => t.Activities) + .Include(t => t.Notes) + .AsNoTracking() + .Select(t => new TicketDTO(t)) + .FirstOrDefaultAsync(t => t.Id == id); + if (ticket == null) { return NotFound(); @@ -49,13 +66,14 @@ namespace TicketManager.Controllers { return BadRequest(); } + _context.Entry(ticket).State = EntityState.Modified; try { - await _tickets.Update(ticket); + await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { - if (!_tickets.Exists(id)) + if (!TicketExists(id)) { return NotFound(); } @@ -73,36 +91,56 @@ namespace TicketManager.Controllers [HttpPost] public async Task> PostTicket(Ticket ticket) { - await _tickets.Add(ticket); - return CreatedAtAction("GetTicket", new { id = ticket.Id }, ticket); + _context.Tickets.Add(ticket); + await _context.SaveChangesAsync(); + + var dto = new TicketDTO(ticket); + return CreatedAtAction("GetTicket", new { id = ticket.Id }, dto); } // DELETE: api/Tickets/5 [HttpDelete("{id}")] - public async Task> DeleteTicket(int id) + public async Task> DeleteTicket(int id) { - var ticket = await _tickets.Get(id); + var ticket = await _context.Tickets.FindAsync(id); if (ticket == null) { return NotFound(); } - await _tickets.Delete(ticket); - return ticket; + + _context.Tickets.Remove(ticket); + await _context.SaveChangesAsync(); + + var dto = new TicketDTO(ticket); + return Ok(dto); } [HttpGet("{id}/assignees")] - public async Task>> GetTicketAssignees(int id) + public async Task>> GetTicketAssignees(int id) { - Ticket ticket = await _tickets.Get(id); - return ticket.GetAssignees(); + Ticket ticket = await _context.Tickets + .Include(t => t.Project) + .ThenInclude(p => p.Assignments) + .ThenInclude(a => a.User) + .AsNoTracking() + .FirstOrDefaultAsync(t => t.Id == id); + + return ticket.GetAssignees().Select(u => new AppUserDTO(u)).ToList(); } [HttpPut("{id}/closed")] public async Task CloseTicket(int id) { - Ticket ticket = await _tickets.Get(id); + Ticket ticket = await _context.Tickets.FindAsync(id); ticket.Close(); + // _context.Entry(ticket).State = EntityState.Modified; + return await PutTicket(ticket.Id, ticket); } + + private bool TicketExists(int id) + { + return _context.Tickets.Any(e => e.Id == id); + } } } diff --git a/Data/AppUserRepository.cs b/Data/AppUserRepository.cs deleted file mode 100644 index a8b4a62..0000000 --- a/Data/AppUserRepository.cs +++ /dev/null @@ -1,37 +0,0 @@ -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 deleted file mode 100644 index 1a90aa1..0000000 --- a/Data/GenericRepository.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; - -namespace TicketManager.Data -{ - public class GenericRepository : IGenericRepository where T : class - { - protected readonly AppDbContext _context; - protected readonly DbSet _dbSet; - public GenericRepository(AppDbContext context) - { - _context = context; - _dbSet = _context.Set(); - } - - public async Task Add(T entity) - { - _dbSet.Add(entity); - return await _context.SaveChangesAsync(); - } - - 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) - { - return await _dbSet.Where(expr).AsNoTracking().ToListAsync(); - } - - public virtual async Task Get(int id) - { - return await _dbSet.FindAsync(id); - } - public virtual async Task> List() - { - return await _dbSet.AsNoTracking().ToListAsync(); - } - - 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 deleted file mode 100644 index ab57db3..0000000 --- a/Data/Interfaces/IAppUserRepository.cs +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 915ef12..0000000 --- a/Data/Interfaces/IGenericRepository.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace TicketManager.Data -{ - public interface IGenericRepository where T : class - { - Task> List(); - Task Get(int id); - Task> Find(int id, Expression> expr); - - Task Add(T entity); - - Task Update(T entity); - - Task Delete(T entity); - } -} \ No newline at end of file diff --git a/Data/Interfaces/IProjectRepository.cs b/Data/Interfaces/IProjectRepository.cs deleted file mode 100644 index ea8c756..0000000 --- a/Data/Interfaces/IProjectRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using TicketManager.Models; - -namespace TicketManager.Data -{ - public interface IProjectRepository : IGenericRepository - { - bool Exists(int id); - Task> GetMembers(int id); - Task SetMembers(int id, List usersToAdd); - } -} \ No newline at end of file diff --git a/Data/Interfaces/ITicketRepository.cs b/Data/Interfaces/ITicketRepository.cs deleted file mode 100644 index de8af26..0000000 --- a/Data/Interfaces/ITicketRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index eab4e21..0000000 --- a/Data/Interfaces/IUnitOfWork.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; - -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/TicketRepository.cs b/Data/TicketRepository.cs deleted file mode 100644 index e142c8f..0000000 --- a/Data/TicketRepository.cs +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index 1a0e21a..0000000 --- a/Data/UnitOfWork.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace TicketManager.Data -{ - public class UnitOfWork : IUnitOfWork - { - private readonly AppDbContext _context; - public UnitOfWork(AppDbContext context) - { - _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(); - } - } -} \ No newline at end of file diff --git a/DataTransfertObjects/AppUserDTO.cs b/DataTransfertObjects/AppUserDTO.cs new file mode 100644 index 0000000..250fbcc --- /dev/null +++ b/DataTransfertObjects/AppUserDTO.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using TicketManager.Models; + +namespace TicketManager.DTO +{ + public class AppUserDTO + { + public AppUserDTO(AppUser user) + { + Id = user.Id; + FirstName = user.FirstName; + LastName = user.LastName; + Presentation = user.Presentation; + Email = user.Email; + Phone = user.Phone; + Created_at = user.Created_at; + Picture = user.Picture; + Activities = user.Activities; + Projects = user.GetProjects(); + Tickets = user.GetTickets(); + } + + public Guid Id { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + + public string FullName => $"{FirstName} {LastName}"; + + public string Presentation { get; set; } + + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + + [DataType(DataType.Date)] + public DateTime Created_at { get; private set; } = DateTime.Now; + + public string Picture { get; set; } + + public List Activities { get; set; } = new List(); + + public List Projects { get; set; } = new List(); + + public List Tickets { get; set; } = new List(); + + } +} \ No newline at end of file diff --git a/DataTransfertObjects/TicketDTO.cs b/DataTransfertObjects/TicketDTO.cs new file mode 100644 index 0000000..4ff3bae --- /dev/null +++ b/DataTransfertObjects/TicketDTO.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using TicketManager.Models; + +namespace TicketManager.DTO +{ + public class TicketDTO + { + public TicketDTO(Ticket ticket) + { + Id = ticket.Id; + Title = ticket.Title; + Description = ticket.Description; + CreatedAt = ticket.CreatedAt; + PlannedEnding = ticket.PlannedEnding; + Status = ticket.Status.ToString(); + Impact = ticket.Impact.ToString(); + Difficulty = ticket.Difficulty.ToString(); + Category = ticket.Category.ToString(); + CreatorId = ticket.CreatorId; + Project = ticket.Project; + Notes = ticket.Notes; + Activities = ticket.Activities; + Files = ticket.Files; + Users = ticket.GetAssignees(); + } + public int Id { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + [DataType(DataType.Date)] + public DateTime CreatedAt { get; private set; } + + [DataType(DataType.Date)] + public DateTime PlannedEnding { get; set; } + + public string Status { get; set; } + public string Impact { get; set; } + public string Difficulty { get; set; } + public string Category { get; set; } + public Guid CreatorId { get; set; } + public Project Project { get; set; } + public List Notes { get; set; } = new List(); + public List Activities { get; set; } = new List(); + public List Files { get; set; } = new List(); + public List Users { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Models/AppUser.cs b/Models/AppUser.cs index 41b08c6..ceb557b 100644 --- a/Models/AppUser.cs +++ b/Models/AppUser.cs @@ -36,8 +36,8 @@ namespace TicketManager.Models [Display(Name = "Member since"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] public DateTime Created_at { get; private set; } = DateTime.Now; - // [Display(Name = "Avatar")] - // public byte[] Picture { get; set; } + [Display(Name = "Avatar")] + public string Picture { get; set; } public List Assignments { get; set; } = new List(); diff --git a/README.md b/README.md index 17b770b..73fc768 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,4 @@ - [ ] check useRef, useReducer, dispatch - [ ] error page redirect when offline. - [ ] ticket/files/activities list placeholders when empty +- [ ] think about public/private DTO's constructor, getters and setters diff --git a/Scripts/apiQueries.sh b/Scripts/apiQueries.sh index 08cee4e..b904838 100755 --- a/Scripts/apiQueries.sh +++ b/Scripts/apiQueries.sh @@ -1 +1,2 @@ -curl --insecure https://localhost:5001/api/v1/ \ No newline at end of file +curl --insecure https://localhost:5001/api/v1/ +curl --insecure https://localhost:5001/api/v1/projects/1/ | json_pp > Scripts/response.http \ No newline at end of file diff --git a/Scripts/cleanDevDb.sh b/Scripts/cleanDevDb.sh new file mode 100755 index 0000000..f3b9b7a --- /dev/null +++ b/Scripts/cleanDevDb.sh @@ -0,0 +1,5 @@ +rm -r Migrations +rm app.db +dotnet ef migrations add Migration1 +dotnet ef database update +dotnet watch run \ No newline at end of file diff --git a/Scripts/response.http b/Scripts/response.http new file mode 100644 index 0000000..7223cd0 --- /dev/null +++ b/Scripts/response.http @@ -0,0 +1,47 @@ +{ + "activities" : [], + "plannedEnding" : "0001-01-01T00:00:00", + "id" : 1, + "title" : "Secret Project", + "createdAt" : "2020-02-24T10:34:18.428046", + "users" : [ + { + "firstName" : "Thomas", + "phone" : "0198237645", + "lastName" : "Price", + "created_at" : "2020-02-25T09:42:54.462374", + "presentation" : "New Team?!", + "email" : "tp@mail.com", + "picture" : null, + "activities" : [], + "id" : "357727fd-5262-4522-b8a3-38271d43de84", + "fullName" : "Thomas Price", + "assignments" : [ + { + "project" : { + "assignments" : [], + "createdAt" : "2020-02-24T10:34:18.428046", + "title" : "Secret Project", + "id" : 1, + "plannedEnding" : "2020-02-17T15:51:02.787373", + "activities" : [], + "description" : "Shhttt Don't tell anyone", + "status" : 1, + "files" : [], + "tickets" : [], + "progression" : 0, + "manager" : null + }, + "userId" : "357727fd-5262-4522-b8a3-38271d43de84", + "projectId" : 1 + } + ] + } + ], + "manager" : null, + "progression" : 0, + "tickets" : [], + "files" : [], + "status" : "ToDo", + "description" : "Shhttt Don't tell anyone" +} diff --git a/Scripts/scaffoldControllers.sh b/Scripts/scaffoldControllers.sh new file mode 100644 index 0000000..0e65847 --- /dev/null +++ b/Scripts/scaffoldControllers.sh @@ -0,0 +1,6 @@ +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/client/.gitignore b/client/.gitignore deleted file mode 100644 index 46e52b7..0000000 --- a/client/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -react-app-env.d.ts - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx index 73595e6..b28367f 100644 --- a/client/src/components/Modal.tsx +++ b/client/src/components/Modal.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState, CSSProperties } from "react"; +import React, { FC, CSSProperties } from "react"; interface IProps { handleClose: () => void; @@ -11,14 +11,6 @@ export const Modal: FC = ({ handleClose, show, children }) => { return ( {children} - - - Done - - ); }; diff --git a/client/src/components/UsersModal.tsx b/client/src/components/UsersModal.tsx index 0cf92a5..8a0f581 100644 --- a/client/src/components/UsersModal.tsx +++ b/client/src/components/UsersModal.tsx @@ -1,11 +1,13 @@ -import React, { FC, useState, ChangeEvent, useEffect } from "react"; +import React, { FC, useState, ChangeEvent, useEffect, FormEvent } 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 { get, put } from "../utils/http"; import { Constants } from "../utils/Constants"; +import { UsersModalEntry } from "./UsersModalEntry"; +import { useParams } from "react-router-dom"; interface IProps { show: boolean; @@ -15,12 +17,27 @@ interface IProps { export const UsersModal: FC = ({ show, handleClose, users }) => { const [filterText, setFilterText] = useState(""); + const [allUsers, setAllUsers] = useState([]); + const [members, setMembers] = useState(users); + const { id } = useParams(); + const handleChange: (e: ChangeEvent) => void = ( e: ChangeEvent ) => { setFilterText(e.target.value); }; - const [allUsers, setAllUsers] = useState(); + + const handleSubmit: (event: FormEvent) => void = async ( + e: FormEvent + ) => { + e.preventDefault(); + + const response: HttpResponse = await put( + `${Constants.projectsURI}/${id}/members`, + members + ); + console.log(response); + }; async function httpGet(): Promise { try { @@ -28,8 +45,7 @@ export const UsersModal: FC = ({ show, handleClose, users }) => { `${Constants.usersURI}` ); if (response.parsedBody !== undefined) { - setAllUsers(response.parsedBody); - // setIsLoading(false); + setAllUsers((response.parsedBody as unknown) as User[]); } } catch (ex) { // setHasError(true); @@ -69,34 +85,26 @@ export const UsersModal: FC = ({ show, handleClose, users }) => { handleChange={handleChange} /> - {/* {allUsers} */} - + + - {users.map((u: User) => ( + {allUsers.map((u: User) => ( - - false} - // checked - /> - - {u.fullName} - - - + ))} + + + ); diff --git a/client/src/components/UsersModalEntry.tsx b/client/src/components/UsersModalEntry.tsx new file mode 100644 index 0000000..9b1f0be --- /dev/null +++ b/client/src/components/UsersModalEntry.tsx @@ -0,0 +1,38 @@ +import React, { FC } from "react"; +import { User } from "../types/User"; + +interface IProps { + setMembers: React.Dispatch>; + members: User[]; + user: User; +} + +export const UsersModalEntry: FC = ({ user, setMembers, members }) => { + return ( + + + { + !members.includes(user) + ? setMembers([...members, user]) + : setMembers(members.filter(p => p !== user)); + }} + /> + + {user.fullName} + + + + + ); +}; diff --git a/client/src/react-app-env.d.ts b/client/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/client/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/client/src/utils/http.ts b/client/src/utils/http.ts index 2ba9a2d..4e44925 100644 --- a/client/src/utils/http.ts +++ b/client/src/utils/http.ts @@ -13,7 +13,7 @@ export async function http(request: RequestInfo): Promise> { export async function get( path: string, - args: RequestInit = { method: "get" } + args: RequestInit = { method: "get", headers: headers } ): Promise> { return await http(new Request(path, args)); } @@ -21,7 +21,11 @@ export async function get( export async function post( path: string, body: any, - args: RequestInit = { method: "post", body: JSON.stringify(body) } + args: RequestInit = { + method: "post", + headers: headers, + body: JSON.stringify(body) + } ): Promise> { return await http(new Request(path, args)); } @@ -29,7 +33,16 @@ export async function post( export async function put( path: string, body: any, - args: RequestInit = { method: "put", body: JSON.stringify(body) } + args: RequestInit = { + method: "put", + headers: headers, + body: JSON.stringify(body) + } ): Promise> { return await http(new Request(path, args)); } + +const headers: Headers = new Headers({ + Accept: "application/json", + "Content-Type": "application/json" +});