diff --git a/.gitignore b/.gitignore
index 34ca015..e2a1839 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,16 @@
-bin/
-obj/
.vs/
.vscode/
-Migrations/
-app.db*
.DS_Store
-app.db
+bin/
+obj/
+app.db*
+Data/Interfaces
+Data/UnitOfWork.cs
+Data/*Repository.cs
+Migrations/
+Properties/
+Tests/TicketManager.Tests/UnitTests/ControllersTests/ControllerTests.cs
+Tests/TicketManager.Tests/UnitTests/ControllersTests/SeedDb.cs
# client
client/src/pages/TestPage.tsx
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 d8232d9..0b98104 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,10 +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();
}
///
@@ -51,19 +60,30 @@ namespace TicketManager.Controllers
/// GET: api/v1/Projects/2
///
///
- /// Returns a project object
+ /// Identifier of the ressource
+ /// 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 _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();
}
@@ -195,18 +243,24 @@ namespace TicketManager.Controllers
/// Not Found
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- [HttpPut("{id}/members")]
+ [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/DTOs/AppUserDTO.cs b/DTOs/AppUserDTO.cs
new file mode 100644
index 0000000..b2009d3
--- /dev/null
+++ b/DTOs/AppUserDTO.cs
@@ -0,0 +1,52 @@
+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/ProjectDTO.cs b/DTOs/ProjectDTO.cs
similarity index 99%
rename from DataTransfertObjects/ProjectDTO.cs
rename to DTOs/ProjectDTO.cs
index ccfa8a3..ba8d098 100644
--- a/DataTransfertObjects/ProjectDTO.cs
+++ b/DTOs/ProjectDTO.cs
@@ -36,6 +36,7 @@ namespace TicketManager.DTO
public string Status { get; set; }
public AppUser Manager { get; set; }
+
public List Users { get; set; } = new List();
public List Tickets { get; set; } = new List();
diff --git a/DTOs/TicketDTO.cs b/DTOs/TicketDTO.cs
new file mode 100644
index 0000000..cdacb99
--- /dev/null
+++ b/DTOs/TicketDTO.cs
@@ -0,0 +1,60 @@
+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/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/ProjectRepository.cs b/Data/ProjectRepository.cs
deleted file mode 100644
index ec8dede..0000000
--- a/Data/ProjectRepository.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System.Threading.Tasks;
-using TicketManager.Models;
-using System.Linq;
-using Microsoft.EntityFrameworkCore;
-using System.Collections.Generic;
-
-namespace TicketManager.Data
-{
- public class ProjectRepository : GenericRepository, IProjectRepository
- {
- private readonly IQueryable _query;
- public ProjectRepository(AppDbContext context) : base(context)
- {
- _query = _dbSet
- .Include(p => p.Assignments).ThenInclude(a => a.User)
- .Include(p => p.Tickets)
- .Include(p => p.Manager)
- .Include(p => p.Files);
- }
-
- 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);
- }
-
- public async Task> GetMembers(int id)
- {
- Project project = await Get(id);
- return project.GetMembers();
- }
- public async Task SetMembers(int id, List usersToAdd)
- {
- Project project = await Get(id);
- project.SetMembers(usersToAdd);
- }
- }
-}
\ 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/Models/AppUser.cs b/Models/AppUser.cs
index aa762d9..ceb557b 100644
--- a/Models/AppUser.cs
+++ b/Models/AppUser.cs
@@ -37,7 +37,7 @@ namespace TicketManager.Models
public DateTime Created_at { get; private set; } = DateTime.Now;
[Display(Name = "Avatar")]
- public byte[] Picture { get; set; }
+ public string Picture { get; set; }
public List Assignments { get; set; } = new List();
diff --git a/Models/Project.cs b/Models/Project.cs
index fceedcf..7f1df5a 100644
--- a/Models/Project.cs
+++ b/Models/Project.cs
@@ -95,6 +95,10 @@ namespace TicketManager.Models
public void RemoveMembers(List membersToRemove)
{
this.Assignments.RemoveAll(a => membersToRemove.Contains(a.User));
+
+ // membersToRemove.ForEach(
+ // m => m.Assignments.RemoveAll(a => (a.Project == this))
+ // );
}
public void SetMembers(List projectMembers)
diff --git a/Models/Ticket.cs b/Models/Ticket.cs
index 5b28ded..0063be7 100644
--- a/Models/Ticket.cs
+++ b/Models/Ticket.cs
@@ -47,7 +47,7 @@ namespace TicketManager.Models
{
return Project.Assignments.Select(a => a.User).ToList();
}
- public void GetLastUpdateTime() { throw new NotImplementedException("Not Implemented"); }
+ public void GetLastUpdateTime() { throw new NotImplementedException("Not Implemented Yet."); }
public void Close()
{
Status = Status.Done;
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
index 42e74be..7fb0bbb 100644
--- a/Properties/launchSettings.json
+++ b/Properties/launchSettings.json
@@ -20,7 +20,7 @@
"TicketManager": {
"commandName": "Project",
"launchBrowser": true,
- "launchUrl": "weatherforecast",
+ "launchUrl": "/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
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/Startup.cs b/Startup.cs
index f86c247..a663213 100644
--- a/Startup.cs
+++ b/Startup.cs
@@ -1,7 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
+using System.Reflection;
+using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
@@ -13,13 +12,10 @@ using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
-using System.Reflection;
-using System.IO;
-using TicketManager.Data;
-using TicketManager.Models;
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
-using Newtonsoft.Json;
using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Newtonsoft.Json;
+using TicketManager.Data;
[assembly: ApiController]
namespace TicketManager
@@ -36,10 +32,14 @@ namespace TicketManager
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
- options.UseSqlite(Configuration.GetConnectionString("Sqlite")));
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
+ {
+ options.UseSqlite(Configuration.GetConnectionString("Sqlite"));
+ options.EnableSensitiveDataLogging(true); //Remove in production.
+ }
+ );
+ // services.AddScoped();
+ // services.AddScoped();
+ // services.AddScoped();
services.AddAuthentication(options =>
{
diff --git a/Tests/TicketManager.Tests/UnitTests/ControllersTests/ProjectControllerTests.cs b/Tests/TicketManager.Tests/UnitTests/ControllersTests/ProjectControllerTests.cs
new file mode 100644
index 0000000..6bda2fb
--- /dev/null
+++ b/Tests/TicketManager.Tests/UnitTests/ControllersTests/ProjectControllerTests.cs
@@ -0,0 +1,253 @@
+using System;
+using Xunit;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Data.Sqlite;
+using TicketManager.Controllers;
+using TicketManager.Data;
+using TicketManager.Models;
+using TicketManager.DTO;
+
+
+namespace TicketManager.Tests
+{
+ public class ProjectsControllerTests
+ {
+ [Fact]
+ public async Task Get_ReturnsListWith2Projects()
+ {
+ var connection = new SqliteConnection("DataSource=:memory:");
+ connection.Open();
+
+ try
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite(connection)
+ .Options;
+
+ // creates DB schema
+ using (var context = new AppDbContext(options))
+ {
+ context.Database.EnsureCreated();
+ }
+
+ // Seed DB usng one context instance
+ SeedDb(options);
+
+ using (var context = new AppDbContext(options))
+ {
+ var controller = new ProjectsController(context);
+
+ var result = await controller.GetProjects();
+
+ Assert.IsAssignableFrom>(result);
+ Assert.Equal(2, result.Count);
+ }
+ }
+ finally
+ {
+ connection.Close();
+ }
+ }
+
+ [Fact]
+ public async Task Get1_Returns1Project()
+ {
+ var connection = new SqliteConnection("DataSource=:memory:");
+ connection.Open();
+
+ try
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite(connection)
+ .Options;
+
+ // creates DB schema
+ using (var context = new AppDbContext(options))
+ {
+ context.Database.EnsureCreated();
+ }
+
+ // Seed DB usng one context instance
+ SeedDb(options);
+
+ using (var context = new AppDbContext(options))
+ {
+ var controller = new ProjectsController(context);
+
+ // Should Return 1 Project
+ var result = await controller.GetProject(1);
+ Assert.IsAssignableFrom(result);
+
+ // Should Return NotFound
+ result = await controller.GetProject(3);
+ Assert.IsType(result);
+ }
+ }
+ finally
+ {
+ connection.Close();
+ }
+ }
+
+ [Fact]
+ public async Task Put1_Updates1Project()
+ {
+ // ControllersTests.Wrapper(Test_PutProject, SeedDb);
+ // }
+
+ // private static async Task Test_PutProject()
+ // {
+ var connection = new SqliteConnection("DataSource=:memory:");
+ connection.Open();
+
+ try
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite(connection)
+ .Options;
+
+ // creates DB schema
+ using (var context = new AppDbContext(options))
+ {
+ context.Database.EnsureCreated();
+ }
+
+ // Seed DB usng one context instance
+ SeedDb(options);
+
+ using (var context = new AppDbContext(options))
+ {
+ var controller = new ProjectsController(context);
+
+ var result = await controller.PutProject(1,
+ new Project()
+ {
+ Id = 1,
+ Title = "Top Secret Project",
+ Description = "Shht Don't Ask don't tell",
+ PlannedEnding = new DateTime(2020, 7, 21)
+ }
+ );
+
+ // Should Update
+ Assert.Equal("Top Secret Project", context.Projects.Find(1).Title);
+ Assert.Equal(new DateTime(2020, 7, 21), context.Projects.Find(1).PlannedEnding);
+ Assert.IsType(result);
+
+
+ result = await controller.PutProject(2,
+ new Project()
+ {
+ Id = 1,
+ Title = "Top Secret Project",
+ Description = "Shht Don't Ask don't tell",
+ PlannedEnding = new DateTime(2020, 7, 21)
+ }
+ );
+
+ // Should Return BadRequest
+ Assert.NotEqual("Top Secret Project", context.Projects.Find(2).Title);
+ Assert.NotEqual(new DateTime(2020, 7, 21), context.Projects.Find(2).CreatedAt);
+ Assert.IsType(result);
+
+ // Delete updated project
+ context.Projects.RemoveRange(context.Projects.Find(1));
+ await context.SaveChangesAsync();
+
+ result = await controller.PutProject(1,
+ new Project()
+ {
+ Id = 1,
+ Title = "Top Secret Project",
+ Description = "Shht Don't Ask don't tell",
+ PlannedEnding = new DateTime(2020, 7, 21)
+ }
+ );
+
+ // Should Throw
+ Assert.IsType(result);
+ Assert.Equal("Top Secret Project", context.Projects.Find(1).Title);
+ Assert.Equal(new DateTime(2020, 7, 21), context.Projects.Find(1).PlannedEnding);
+ }
+ }
+
+ finally
+ {
+ connection.Close();
+ }
+ }
+
+ [Fact]
+ public async Task Post_CreatesProject()
+ {
+ // Create inMemory Test Database
+ var connection = new SqliteConnection("DataSource=:memory:");
+ connection.Open();
+
+ try
+ {
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite(connection)
+ .Options;
+
+ // creates DB schema
+ using (var context = new AppDbContext(options))
+ {
+ context.Database.EnsureCreated();
+ }
+
+ // Seed DB usng one context instance
+ SeedDb(options);
+
+ // use another context instance to run the test
+ using (var context = new AppDbContext(options))
+ {
+ var proj = new Project()
+ {
+ Title = "The Third",
+ Description = "Thrice in a row",
+ PlannedEnding = DateTime.Now
+ };
+
+ var controller = new ProjectsController(context);
+
+ var result = await controller.PostProject(proj);
+
+ Assert.IsAssignableFrom(result);
+ Assert.Equal(3, await context.Projects.CountAsync());
+ }
+ }
+ finally
+ {
+ connection.Close();
+ }
+ }
+
+ private static void SeedDb(DbContextOptions options)
+ // Seed DB usng one context instance
+ {
+ using (var context = new AppDbContext(options))
+ {
+ context.Projects.AddRange(
+ new Project()
+ {
+ Id = 1,
+ Title = "Secret Project",
+ Description = "Shht Don't Ask don't tell",
+ PlannedEnding = new DateTime(2021, 7, 21)
+ },
+ new Project()
+ {
+ Id = 2,
+ Title = "Public Project",
+ Description = "It's quite obvious, isn't it?!",
+ PlannedEnding = new DateTime(2036, 6, 16)
+ });
+ context.SaveChanges();
+ }
+ }
+ }
+}
diff --git a/Tests/TicketManager.Tests/UnitTests/AppUserModelTests.cs b/Tests/TicketManager.Tests/UnitTests/ModelTests/AppUserModelTests.cs
similarity index 100%
rename from Tests/TicketManager.Tests/UnitTests/AppUserModelTests.cs
rename to Tests/TicketManager.Tests/UnitTests/ModelTests/AppUserModelTests.cs
index 8243ef1..2016528 100644
--- a/Tests/TicketManager.Tests/UnitTests/AppUserModelTests.cs
+++ b/Tests/TicketManager.Tests/UnitTests/ModelTests/AppUserModelTests.cs
@@ -50,6 +50,13 @@ namespace TicketManager.Tests
Ticket t5 = new Ticket();
Ticket t6 = new Ticket();
+ p1.Tickets.Add(t1);
+ p2.Tickets.Add(t2);
+ p2.Tickets.Add(t3);
+ p3.Tickets.Add(t4);
+ p3.Tickets.Add(t5);
+ p3.Tickets.Add(t6);
+
Assignment a1 = new Assignment()
{
User = user,
@@ -69,13 +76,6 @@ namespace TicketManager.Tests
};
user.Assignments.Add(a3);
- p1.Tickets.Add(t1);
- p2.Tickets.Add(t2);
- p2.Tickets.Add(t3);
- p3.Tickets.Add(t4);
- p3.Tickets.Add(t5);
- p3.Tickets.Add(t6);
-
var res = user.GetTickets().Count;
Assert.Equal(6, res);
}
diff --git a/Tests/TicketManager.Tests/UnitTests/ProjectModelTests.cs b/Tests/TicketManager.Tests/UnitTests/ModelTests/ProjectModelTests.cs
similarity index 100%
rename from Tests/TicketManager.Tests/UnitTests/ProjectModelTests.cs
rename to Tests/TicketManager.Tests/UnitTests/ModelTests/ProjectModelTests.cs
diff --git a/Tests/TicketManager.Tests/UnitTests/TicketModelTests.cs b/Tests/TicketManager.Tests/UnitTests/ModelTests/TicketModelTests.cs
similarity index 100%
rename from Tests/TicketManager.Tests/UnitTests/TicketModelTests.cs
rename to Tests/TicketManager.Tests/UnitTests/ModelTests/TicketModelTests.cs
diff --git a/Tests/TicketManager.Tests/UnitTests/ProjectControllerTests.cs b/Tests/TicketManager.Tests/UnitTests/ProjectControllerTests.cs
deleted file mode 100644
index 6e1d788..0000000
--- a/Tests/TicketManager.Tests/UnitTests/ProjectControllerTests.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using Xunit;
-using System.Collections.Generic;
-using TicketManager.Controllers;
-using TicketManager.Data;
-using TicketManager.Models;
-
-namespace TicketManager.Tests
-{
- public class ProjectsControllerTests
- {
-
-
- public ProjectsControllerTests()
- {
- // _context = context;
- }
-
- // [Fact]
- // public void Get_ReturnsProjectList()
- // {
- // // Arange
- // // var controller = new ProjectsController();
-
- // // Act
- // // var result = controller.GetProjects();
-
- // // Assert
- // // Assert.IsType>(result);
- // }
- }
-}
diff --git a/TicketManager.csproj b/TicketManager.csproj
index 812b03b..d68b951 100644
--- a/TicketManager.csproj
+++ b/TicketManager.csproj
@@ -3,7 +3,7 @@
netcoreapp3.1
-
+
8.0
@@ -26,9 +26,14 @@
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+