From 2f03b8f40276b0523a8d03cc15162787a5b382f0 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Mon, 10 Feb 2020 23:22:48 +0100 Subject: [PATCH 1/8] switch to Newtonsoft.Json and config ti avoid cycle errors --- .gitignore | 3 ++- Controllers/ProjectsController.cs | 1 + Models/Ticket.cs | 4 ++-- Startup.cs | 9 ++++++++- TicketManager.csproj | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7b61263..84b0e90 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ obj/ .vscode/ Migrations/ app.db -client/node_modules \ No newline at end of file +client/node_modules +Scripts/ \ No newline at end of file diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index 5f671c1..a596542 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore; using TicketManager.Data; using TicketManager.Models; + namespace TicketManager.Controllers { [Route("api/[controller]")] diff --git a/Models/Ticket.cs b/Models/Ticket.cs index 727ca6f..65fc8ae 100644 --- a/Models/Ticket.cs +++ b/Models/Ticket.cs @@ -18,8 +18,8 @@ namespace TicketManager.Models public User Creator { get; set; } public Guid CreatorId { get; set; } - // public Project Project { get; set; } - // public int ProjectId { get; set; } + public Project Project { get; set; } + public int ProjectId { get; set; } private List _notes; public List Notes { diff --git a/Startup.cs b/Startup.cs index 9da91b4..825d07d 100644 --- a/Startup.cs +++ b/Startup.cs @@ -17,6 +17,8 @@ using System.Reflection; using System.IO; using TicketManager.Data; using TicketManager.Models; +using Microsoft.AspNetCore.Mvc.NewtonsoftJson; +using Newtonsoft.Json; namespace TicketManager { @@ -34,7 +36,12 @@ namespace TicketManager { services.AddDbContext(options => options.UseSqlite(Configuration.GetConnectionString("Sqlite"))); - services.AddControllers(); + services.AddControllers() + .AddNewtonsoftJson(options => + { + options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // avoid cycle ref errors + }); + services.AddSpaStaticFiles(configuration => { configuration.RootPath = "client/build"; diff --git a/TicketManager.csproj b/TicketManager.csproj index ae2c5af..b4ab051 100644 --- a/TicketManager.csproj +++ b/TicketManager.csproj @@ -16,6 +16,7 @@ + From 54c221236bf5171cfbe38f50d11506825982dbff Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 11 Feb 2020 00:13:54 +0100 Subject: [PATCH 2/8] project/id/addmembers endpoint --- Controllers/ProjectsController.cs | 38 +++++++++++++++++++++++++++++++ Models/History.cs | 2 +- Models/Note.cs | 2 +- Models/Project.cs | 19 +++++++++------- Models/Ticket.cs | 2 +- Models/User.cs | 2 +- README.md | 23 ++++++++++++++++++- 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index a596542..7df1bb5 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -111,7 +111,45 @@ namespace TicketManager.Controllers return project; } + [HttpPost("{id}/addMembers")] + public async Task> AddMembersToProject(int id, List usersToAdd) + { + var project = await _context.Projects + .Include(p => p.Assignments) + .FirstOrDefaultAsync(p => p.Id == id); + project.AddMembers(usersToAdd); + await _context.SaveChangesAsync(); + + return project; + } + + + [HttpPost("{id}/removeMembers")] + public async Task> RemoveMembersToProject(int id, List usersToRemove) + { + var project = await _context.Projects + .Include(p => p.Assignments) + .FirstOrDefaultAsync(p => p.Id == id); + + project.RemoveMembers(usersToRemove); + await _context.SaveChangesAsync(); + + return project; + } + + [HttpPost("{id}/setMembers")] + public async Task> SetProjectMembers(int id, List projectMembers) + { + var project = await _context.Projects + .Include(p => p.Assignments) + .FirstOrDefaultAsync(p => p.Id == id); + + project.SetMembers(projectMembers); + await _context.SaveChangesAsync(); + + return project; + } private bool ProjectExists(int id) { return _context.Projects.Any(e => e.Id == id); diff --git a/Models/History.cs b/Models/History.cs index 44d45cd..2f1f3ad 100644 --- a/Models/History.cs +++ b/Models/History.cs @@ -8,7 +8,7 @@ namespace TicketManager.Models public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } - public DateTime UpdateDate { get; } = DateTime.Now; + public DateTime UpdateDate { get; set; } = DateTime.Now; public ActivityType ActivityType { get; set; } = ActivityType.Undefined; public User User { get; set; } diff --git a/Models/Note.cs b/Models/Note.cs index 7b6834f..84d5c05 100644 --- a/Models/Note.cs +++ b/Models/Note.cs @@ -8,7 +8,7 @@ namespace TicketManager.Models public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } - public DateTime Created_at { get; } = DateTime.Now; + public DateTime Created_at { get; set; } = DateTime.Now; public Ticket Ticket { get; set; } } diff --git a/Models/Project.cs b/Models/Project.cs index cf90014..ac3ce8e 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -9,7 +9,7 @@ namespace TicketManager.Models public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } - public DateTime CreatedAt { get; } = DateTime.Now; + public DateTime CreatedAt { get; set; } = DateTime.Now; public DateTime PlannedEnding { get; set; } public float Progression { @@ -40,13 +40,13 @@ namespace TicketManager.Models { return _tickets ?? new List(); } set { _tickets = value; } } - // private List _edits; - // public List Edits - // { - // get - // { return _edits ?? new List(); } - // set { _edits = value; } - // } + private List _edits; + public List Edits + { + get + { return _edits ?? new List(); } + set { _edits = value; } + } private List _files; public List Files { @@ -78,6 +78,9 @@ namespace TicketManager.Models { this.Assignments.RemoveAll(a => membersToRemove.Contains(a.User)); } + + public void SetMembers(List projectMembers) + { throw new NotImplementedException("Not Implemented"); } public int GetMembersCount() => this.GetMembers().Count(); public void GetTicketsCount() => this.Tickets.Count(); public void GetTicketsUpdates() diff --git a/Models/Ticket.cs b/Models/Ticket.cs index 65fc8ae..6176197 100644 --- a/Models/Ticket.cs +++ b/Models/Ticket.cs @@ -8,7 +8,7 @@ namespace TicketManager.Models public int Id { get; set; } public string Title { get; set; } public string Description { get; set; } - public DateTime CreatedAt { get; } = DateTime.Now; + public DateTime CreatedAt { get; set; } = DateTime.Now; public DateTime PlannedEnding { get; set; } public Status Status { get; set; } = Status.ToDo; diff --git a/Models/User.cs b/Models/User.cs index 9b04d8b..d7ed052 100644 --- a/Models/User.cs +++ b/Models/User.cs @@ -10,7 +10,7 @@ namespace TicketManager.Models public string LastName { get; set; } public string FullName => $"{FirstName} {LastName}"; public string Presentation { get; set; } - public DateTime Created_at { get; } = DateTime.Now; + public DateTime Created_at { get; set; } = DateTime.Now; public byte[] Picture { get; set; } // public Role Role { get; set; } diff --git a/README.md b/README.md index 6049d88..d4214f5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,27 @@ - [Follow the link](https://docs.google.com/presentation/d/1Gunf5MRJ_KcoFwo0x_vV8YVHnf9l0V8n7BiJGz6p4cI/edit?usp=sharing) -## ToDo +## Features + +## Supports + +- Web +- Progressive Web App +- Mobile + +## Technical Stack + +- `React` client on the front-end (TypeScript) +- [Materialize](https://materializecss.com) CSS librairy for styling +- API: Newtonsoft.Json, to avoid cycle errors +- Hosting: ? +- Authentication : [Auth0](https://auth0.com/) +- Analytics : Google Analytics & Mixpanel + +## Versions + +### Features in v.0.1 + +## TO DO - Write backend tests From f40470035d6cd73494dfb274fd2d7e342b69dd5d Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 11 Feb 2020 09:40:56 +0100 Subject: [PATCH 3/8] add project/id/setmembers, removemembers, getmembers endpoints --- Controllers/ProjectsController.cs | 87 +++++++++++++++++-------------- Models/Project.cs | 15 +++++- README.md | 9 ++++ 3 files changed, 72 insertions(+), 39 deletions(-) diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index 7df1bb5..0610fb8 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -26,31 +26,18 @@ namespace TicketManager.Controllers [HttpGet] public async Task>> GetProjects() { - return await _context.Projects - .Include(p => p.Assignments) - .Include(p => p.Tickets) - .Include(p => p.Manager) - .Include(p => p.Files) - .AsNoTracking() - .ToListAsync(); + return await GetAllProjectsAsync(); } // GET: api/Projects/5 [HttpGet("{id}")] public async Task> GetProject(int id) { - var project = await _context.Projects - .Include(p => p.Assignments) - .Include(p => p.Tickets) - .Include(p => p.Manager) - .Include(p => p.Files) - .AsNoTracking() - .FirstOrDefaultAsync(p => p.Id == id); - // .FindAsync(id); - + Project project = await GetProjectByIdAsync(id); if (project == null) - { return NotFound(); } - + { + return NotFound(); + } return project; } @@ -61,7 +48,9 @@ namespace TicketManager.Controllers public async Task PutProject(int id, Project project) { if (id != project.Id) - { return BadRequest(); } + { + return BadRequest(); + } _context.Entry(project).State = EntityState.Modified; @@ -111,12 +100,29 @@ namespace TicketManager.Controllers return project; } + + [HttpGet("{id}/members")] + public async Task>> GetProjectMembers(int id) + { + Project project = await GetProjectByIdAsync(id); + return project.GetMembers(); + } + + [HttpPut("{id}/setMembers")] // test put & post + public async Task> SetProjectMembers(int id, List projectMembers) + { + Project project = await GetProjectByIdAsync(id); + + project.SetMembers(projectMembers); + await _context.SaveChangesAsync(); + + return project; + } + [HttpPost("{id}/addMembers")] public async Task> AddMembersToProject(int id, List usersToAdd) { - var project = await _context.Projects - .Include(p => p.Assignments) - .FirstOrDefaultAsync(p => p.Id == id); + Project project = await GetProjectByIdAsync(id); project.AddMembers(usersToAdd); await _context.SaveChangesAsync(); @@ -124,13 +130,10 @@ namespace TicketManager.Controllers return project; } - [HttpPost("{id}/removeMembers")] public async Task> RemoveMembersToProject(int id, List usersToRemove) { - var project = await _context.Projects - .Include(p => p.Assignments) - .FirstOrDefaultAsync(p => p.Id == id); + Project project = await GetProjectByIdAsync(id); project.RemoveMembers(usersToRemove); await _context.SaveChangesAsync(); @@ -138,21 +141,29 @@ namespace TicketManager.Controllers return project; } - [HttpPost("{id}/setMembers")] - public async Task> SetProjectMembers(int id, List projectMembers) - { - var project = await _context.Projects - .Include(p => p.Assignments) - .FirstOrDefaultAsync(p => p.Id == id); - - project.SetMembers(projectMembers); - await _context.SaveChangesAsync(); - - return project; - } private bool ProjectExists(int id) { return _context.Projects.Any(e => e.Id == id); } + private async Task>> GetAllProjectsAsync() + { + return await _context.Projects + .Include(p => p.Assignments) + .Include(p => p.Tickets) + .Include(p => p.Manager) + .Include(p => p.Files) + .AsNoTracking() + .ToListAsync(); + } + private async Task GetProjectByIdAsync(int id) + { + return await _context.Projects + .Include(p => p.Assignments) + .Include(p => p.Tickets) + .Include(p => p.Manager) + .Include(p => p.Files) + .AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == id); + } } } diff --git a/Models/Project.cs b/Models/Project.cs index ac3ce8e..fd11ef6 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -80,7 +80,20 @@ namespace TicketManager.Models } public void SetMembers(List projectMembers) - { throw new NotImplementedException("Not Implemented"); } + { + var currentProjectMembers = this.GetMembers(); + if (currentProjectMembers != null) + { + var membersToRemove = currentProjectMembers + .FindAll( + cp => !projectMembers.Contains(cp) + ); + this.RemoveMembers(membersToRemove); + } + + this.AddMembers(projectMembers); + // remove precedent members first + } public int GetMembersCount() => this.GetMembers().Count(); public void GetTicketsCount() => this.Tickets.Count(); public void GetTicketsUpdates() diff --git a/README.md b/README.md index d4214f5..e4ebba9 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ - [Follow the link](https://docs.google.com/presentation/d/1Gunf5MRJ_KcoFwo0x_vV8YVHnf9l0V8n7BiJGz6p4cI/edit?usp=sharing) +## API Documentation + +- [Internal Link. Don't forget to update](https://localhost:5001/swagger/index.html) + ## Features ## Supports @@ -28,3 +32,8 @@ ## TO DO - Write backend tests +- Reinitialize db +- Write Project SetMembers Method and related API endpoint code +- Write Project GetMembers Method and related API endpoint code +- Ensure Tickets Edits belong to Project Edits +- Ensure Tickets Files belong to Project Files From 0ca71cfcf97a73ba2a3019a40787c691e6a930b4 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 11 Feb 2020 15:02:41 +0100 Subject: [PATCH 4/8] preparation to api testing with postman --- Controllers/ProjectsController.cs | 39 ++++++++++++++++++++-------- Models/Project.cs | 42 ++++++++++++++++++++++++------- Program.cs | 2 ++ README.md | 6 ++--- Startup.cs | 12 ++++++--- TicketManager.csproj | 8 ++---- appsettings.json | 1 + 7 files changed, 77 insertions(+), 33 deletions(-) diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index 0610fb8..0d4e0b8 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -119,13 +119,28 @@ namespace TicketManager.Controllers return project; } - [HttpPost("{id}/addMembers")] + [HttpPut("{id}/addMembers")] public async Task> AddMembersToProject(int id, List usersToAdd) { + if (usersToAdd == null) + { + return BadRequest(); + } Project project = await GetProjectByIdAsync(id); project.AddMembers(usersToAdd); - await _context.SaveChangesAsync(); + 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 project; } @@ -147,23 +162,25 @@ namespace TicketManager.Controllers } private async Task>> GetAllProjectsAsync() { - return await _context.Projects - .Include(p => p.Assignments) - .Include(p => p.Tickets) - .Include(p => p.Manager) - .Include(p => p.Files) - .AsNoTracking() + return await makeProjectsQueryAsync() .ToListAsync(); } private async Task GetProjectByIdAsync(int id) { - return await _context.Projects + return await makeProjectsQueryAsync() + .FirstOrDefaultAsync(p => p.Id == id); + } + + private IQueryable makeProjectsQueryAsync() + { + return _context.Projects .Include(p => p.Assignments) + .ThenInclude(a => a.User) .Include(p => p.Tickets) .Include(p => p.Manager) .Include(p => p.Files) - .AsNoTracking() - .FirstOrDefaultAsync(p => p.Id == id); + .AsNoTracking(); } + } } diff --git a/Models/Project.cs b/Models/Project.cs index fd11ef6..9354748 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -1,38 +1,62 @@ using System; using System.Collections.Generic; using System.Linq; +using System.ComponentModel.DataAnnotations; namespace TicketManager.Models { public class Project : ITask { + public Project() + { + + } public int Id { get; set; } + + [Required] + [StringLength(50)] + [Display(Name = "Title")] public string Title { get; set; } + + [StringLength(200)] + [Display(Name = "Short Description")] public string Description { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.Now; + + [DataType(DataType.Date)] + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = false)] + public DateTime CreatedAt { get; private set; } = DateTime.Now; + + [DataType(DataType.Date)] + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PlannedEnding { get; set; } + + [Display(Name = "Progress")] public float Progression { get { - return Tickets.Count() == 0 ? 0 : (float)this.Tickets. + return Tickets.Count() == 0 ? 0 : + (float)this.Tickets. Where(t => t.Status == Status.Done).Count() / this.Tickets.Count() * 100; } } + + [Display(Name = "Project Status")] public Status Status { get; set; } = Status.ToDo; + [Display(Name = "Project Manager")] public User Manager { get; set; } public Guid ManagerId { get; set; } private List _assignments; - public List Assignments - { - get - { return _assignments ?? new List(); } - set - { _assignments = value; } - } + public List Assignments { get; set; } = new List(); + // { + // get + // { return _assignments ?? new List(); } + // set + // { _assignments = value; } + // } private List _tickets; public List Tickets { diff --git a/Program.cs b/Program.cs index b3c5cf8..35c5cb4 100644 --- a/Program.cs +++ b/Program.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; namespace TicketManager { +#pragma warning disable CS1591 public class Program { public static void Main(string[] args) @@ -23,4 +24,5 @@ namespace TicketManager webBuilder.UseStartup(); }); } +#pragma warning restore CS1591 } diff --git a/README.md b/README.md index e4ebba9..0232ef5 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ ## 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 -- Reinitialize db -- Write Project SetMembers Method and related API endpoint code -- Write Project GetMembers Method and related API endpoint code - Ensure Tickets Edits belong to Project Edits - Ensure Tickets Files belong to Project Files diff --git a/Startup.cs b/Startup.cs index 825d07d..37ce7c0 100644 --- a/Startup.cs +++ b/Startup.cs @@ -60,12 +60,12 @@ namespace TicketManager Url = new Uri("https://ruidywebsite.herokuapp.com/"), } }); - // Set the comments path for the Swagger JSON and UI. var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); + services.AddSwaggerGenNewtonsoftSupport(); // explicit opt-in - needs to be placed after AddSwaggerGen() } @@ -77,10 +77,14 @@ namespace TicketManager { app.UseDeveloperExceptionPage(); } + else + { + app.UseHsts(); + } - + app.UseHttpsRedirection(); app.UseDefaultFiles(); - app.UseStaticFiles(); + app.UseSwagger(); @@ -89,7 +93,7 @@ namespace TicketManager c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ticket Manager API V1"); }); - app.UseHttpsRedirection(); + app.UseSpaStaticFiles(); app.UseRouting(); diff --git a/TicketManager.csproj b/TicketManager.csproj index b4ab051..79dbdef 100644 --- a/TicketManager.csproj +++ b/TicketManager.csproj @@ -28,14 +28,10 @@ - - + + - - - - \ No newline at end of file diff --git a/appsettings.json b/appsettings.json index 51642cd..390f10c 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,4 +1,5 @@ { + "https_port": 443, "Logging": { "LogLevel": { "Default": "Information", From 0fa327ee7c447ae9ef5f728496d78ccfc93ec7ea Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 11 Feb 2020 18:45:37 +0100 Subject: [PATCH 5/8] project model & endpoints done --- Controllers/ProjectsController.cs | 42 ++++++++++++------- Data/AppDbContext.cs | 10 ++--- Models/Assignment.cs | 2 +- Models/History.cs | 2 - Models/Interfaces/ITask.cs | 5 ++- Models/Project.cs | 68 +++++++++++++++---------------- README.md | 1 + 7 files changed, 71 insertions(+), 59 deletions(-) diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index 0d4e0b8..f5ac843 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -112,10 +112,18 @@ namespace TicketManager.Controllers public async Task> SetProjectMembers(int id, List projectMembers) { Project project = await GetProjectByIdAsync(id); - project.SetMembers(projectMembers); - await _context.SaveChangesAsync(); - + 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 project; } @@ -127,7 +135,6 @@ namespace TicketManager.Controllers return BadRequest(); } Project project = await GetProjectByIdAsync(id); - project.AddMembers(usersToAdd); try { @@ -140,19 +147,25 @@ namespace TicketManager.Controllers "Try again, and if the problem persists, " + "see your system administrator."); } - - return project; } - [HttpPost("{id}/removeMembers")] - public async Task> RemoveMembersToProject(int id, List usersToRemove) + [HttpPut("{id}/removeMembers")] + public async Task> RemoveMembersFromProject(int id, List usersToRemove) { Project project = await GetProjectByIdAsync(id); - project.RemoveMembers(usersToRemove); - await _context.SaveChangesAsync(); - + 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 project; } @@ -160,6 +173,7 @@ namespace TicketManager.Controllers { return _context.Projects.Any(e => e.Id == id); } + private async Task>> GetAllProjectsAsync() { return await makeProjectsQueryAsync() @@ -171,16 +185,14 @@ namespace TicketManager.Controllers .FirstOrDefaultAsync(p => p.Id == id); } - private IQueryable makeProjectsQueryAsync() + private IQueryable makeProjectsQueryAsync() { return _context.Projects .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); } - } } diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs index a74eb96..92e11be 100644 --- a/Data/AppDbContext.cs +++ b/Data/AppDbContext.cs @@ -16,10 +16,10 @@ namespace TicketManager.Data public DbSet Notes { get; set; } public DbSet Files { get; set; } - // protected override void OnModelCreating(ModelBuilder builder) - // { - // base.OnModelCreating(builder); - // builder.Entity().HasKey(t => new { t.ProjectId, t.UserId }); - // } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.Entity().HasKey(a => new { a.ProjectId, a.UserId }); + } } } \ No newline at end of file diff --git a/Models/Assignment.cs b/Models/Assignment.cs index 02aaa9c..42afe79 100644 --- a/Models/Assignment.cs +++ b/Models/Assignment.cs @@ -4,7 +4,7 @@ namespace TicketManager.Models { public class Assignment { - public int Id { get; set; } + // public int Id { get; set; } public User User { get; set; } public Guid UserId { get; set; } public Project Project { get; set; } diff --git a/Models/History.cs b/Models/History.cs index 2f1f3ad..53b4be7 100644 --- a/Models/History.cs +++ b/Models/History.cs @@ -6,11 +6,9 @@ namespace TicketManager.Models public class History { public int Id { get; set; } - public string Title { get; set; } public string Description { get; set; } public DateTime UpdateDate { get; set; } = DateTime.Now; public ActivityType ActivityType { get; set; } = ActivityType.Undefined; - public User User { get; set; } public int UserId { get; set; } } diff --git a/Models/Interfaces/ITask.cs b/Models/Interfaces/ITask.cs index 9311efe..0773186 100644 --- a/Models/Interfaces/ITask.cs +++ b/Models/Interfaces/ITask.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; namespace TicketManager.Models { - public interface ITask + // public interface ITask + public abstract class ITask { int Id { get; set; } string Title { get; set; } string Description { get; set; } DateTime CreatedAt { get; } DateTime PlannedEnding { get; set; } + List Edits { get; set; } } } diff --git a/Models/Project.cs b/Models/Project.cs index 9354748..ca9d38d 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -7,10 +7,6 @@ namespace TicketManager.Models { public class Project : ITask { - public Project() - { - - } public int Id { get; set; } [Required] @@ -48,36 +44,17 @@ namespace TicketManager.Models [Display(Name = "Project Manager")] public User Manager { get; set; } - public Guid ManagerId { get; set; } - private List _assignments; + public List Assignments { get; set; } = new List(); - // { - // get - // { return _assignments ?? new List(); } - // set - // { _assignments = value; } - // } - private List _tickets; - public List Tickets - { - get - { return _tickets ?? new List(); } - set { _tickets = value; } - } - private List _edits; - public List Edits - { - get - { return _edits ?? new List(); } - set { _edits = value; } - } - private List _files; - public List Files - { - get - { return _files ?? new List(); } - set { _files = value; } - } + + + public List Tickets { get; set; } = new List(); + + + public List Edits { get; set; } = new List(); + + + public List Files { get; set; } = new List(); // Methods public List GetMembers() @@ -96,6 +73,7 @@ namespace TicketManager.Models UserId = user.Id }; this.Assignments.Add(newAssign); + AddLogEntry(" joined the project."); } } public void RemoveMembers(List membersToRemove) @@ -113,10 +91,18 @@ namespace TicketManager.Models cp => !projectMembers.Contains(cp) ); this.RemoveMembers(membersToRemove); + + var membersToAdd = projectMembers.FindAll( + pm => !currentProjectMembers.Contains(pm) + ); + this.AddMembers(membersToAdd); + } + else + { + this.AddMembers(projectMembers); } - this.AddMembers(projectMembers); - // remove precedent members first + } public int GetMembersCount() => this.GetMembers().Count(); public void GetTicketsCount() => this.Tickets.Count(); @@ -126,5 +112,17 @@ namespace TicketManager.Models { this.Status = Status.Done; } + + private void AddLogEntry(string description)//, User user) + { + History Edit = new History() + { + Description = description, + ActivityType = ActivityType.Undefined, + // User = user, + UpdateDate = DateTime.Now + }; + this.Edits.Add(Edit); + } } } \ No newline at end of file diff --git a/README.md b/README.md index 0232ef5..26d4bde 100644 --- a/README.md +++ b/README.md @@ -37,3 +37,4 @@ - Write backend tests - Ensure Tickets Edits belong to Project Edits - Ensure Tickets Files belong to Project Files +- Write a query class to refactor code and optimize perf on get queries (AsNoTracking) From a46c9aaa0b960c17af5cb6222ab9785c8ef26662 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 11 Feb 2020 23:36:39 +0100 Subject: [PATCH 6/8] api doc --- Controllers/ProjectsController.cs | 54 ++++++++++++++++++++++++++++--- Models/Project.cs | 9 ++++-- README.md | 1 + Startup.cs | 1 + 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index f5ac843..a6b7971 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -1,6 +1,7 @@ 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; @@ -11,6 +12,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { + [Produces("application/json")] [Route("api/[controller]")] [ApiController] public class ProjectsController : ControllerBase @@ -22,15 +24,37 @@ namespace TicketManager.Controllers _context = context; } - // GET: api/Projects + /// + /// Returns all existing projects. + /// + /// + /// Sample request: + /// + /// GET: api/Projects + /// + /// + /// Returns all existing projects [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetProjects() { return await GetAllProjectsAsync(); } - // GET: api/Projects/5 + /// + /// 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 GetProjectByIdAsync(id); @@ -41,10 +65,30 @@ namespace TicketManager.Controllers return project; } - // PUT: api/Projects/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 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) diff --git a/Models/Project.cs b/Models/Project.cs index ca9d38d..628c740 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -18,6 +18,12 @@ namespace TicketManager.Models [Display(Name = "Short Description")] public string Description { get; set; } + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = false)] public DateTime CreatedAt { get; private set; } = DateTime.Now; @@ -47,13 +53,10 @@ namespace TicketManager.Models public List Assignments { get; set; } = new List(); - public List Tickets { get; set; } = new List(); - public List Edits { get; set; } = new List(); - public List Files { get; set; } = new List(); // Methods diff --git a/README.md b/README.md index 26d4bde..f8613bc 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,4 @@ - Ensure Tickets Edits belong to Project Edits - Ensure Tickets Files belong to Project Files - Write a query class to refactor code and optimize perf on get queries (AsNoTracking) +- Async model methods ? diff --git a/Startup.cs b/Startup.cs index 37ce7c0..0a40edf 100644 --- a/Startup.cs +++ b/Startup.cs @@ -20,6 +20,7 @@ using TicketManager.Models; using Microsoft.AspNetCore.Mvc.NewtonsoftJson; using Newtonsoft.Json; +[assembly: ApiController] namespace TicketManager { public class Startup From 734d8e931b5767c1762b1ccd6106da73c1706cd7 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Wed, 12 Feb 2020 08:13:32 +0100 Subject: [PATCH 7/8] appuser model & endpoints done --- ...ersController.cs => AppUsersController.cs} | 69 +++++++++++++----- Controllers/ProjectsController.cs | 16 ++--- Data/AppDbContext.cs | 2 +- Models/AppUser.cs | 70 +++++++++++++++++++ Models/Assignment.cs | 2 +- Models/File.cs | 2 +- Models/History.cs | 2 +- Models/Interfaces/ITask.cs | 12 ++++ Models/Project.cs | 55 +++++++-------- Models/Ticket.cs | 2 +- Models/User.cs | 44 ------------ 11 files changed, 173 insertions(+), 103 deletions(-) rename Controllers/{UsersController.cs => AppUsersController.cs} (53%) create mode 100644 Models/AppUser.cs delete mode 100644 Models/User.cs diff --git a/Controllers/UsersController.cs b/Controllers/AppUsersController.cs similarity index 53% rename from Controllers/UsersController.cs rename to Controllers/AppUsersController.cs index 32ca0f3..ff45085 100644 --- a/Controllers/UsersController.cs +++ b/Controllers/AppUsersController.cs @@ -23,22 +23,16 @@ namespace TicketManager.Controllers // GET: api/Users [HttpGet] - public async Task>> GetUsers() + public async Task>> GetUsers() { - return await _context.Users - .Include(p => p.Assignments) - .Include(p => p.Edits) - .ToListAsync(); + return await getAllAppUsersAsync(); } // GET: api/Users/5 [HttpGet("{id}")] - public async Task> GetUser(Guid id) + public async Task> GetUser(Guid id) { - var user = await _context.Users - .Include(p => p.Assignments) - .Include(p => p.Edits) - .FirstOrDefaultAsync(p => p.Id == id); + var user = await getAppUserByIdAsync(id); if (user == null) { @@ -52,7 +46,7 @@ namespace TicketManager.Controllers // 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 PutUser(Guid id, User user) + public async Task PutUser(Guid id, AppUser user) { if (id != user.Id) { @@ -84,9 +78,9 @@ namespace TicketManager.Controllers // 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> PostUser(User user) + public async Task> PostUser(AppUser user) { - _context.Users.Add(user); + _context.AppUsers.Add(user); await _context.SaveChangesAsync(); return CreatedAtAction("GetUser", new { id = user.Id }, user); @@ -94,23 +88,64 @@ namespace TicketManager.Controllers // DELETE: api/Users/5 [HttpDelete("{id}")] - public async Task> DeleteUser(int id) + public async Task> DeleteUser(int id) { - var user = await _context.Users.FindAsync(id); + var user = await _context.AppUsers.FindAsync(id); if (user == null) { return NotFound(); } - _context.Users.Remove(user); + _context.AppUsers.Remove(user); await _context.SaveChangesAsync(); return user; } + [HttpGet("{id}/projects")] + public async Task>> GetAppUserProjects(Guid id) + { + AppUser user = await getAppUserByIdAsync(id); + if (user == null) + { + return BadRequest(); + } + return user.GetProjects(); + } + + [HttpGet("{id}/tickets/")] + public async Task>> GetAppUserTickets(Guid id) + { + AppUser user = await getAppUserByIdAsync(id); + if (user == null) + { + return BadRequest(); + } + return user.GetTickets(); + } + private bool UserExists(Guid id) { - return _context.Users.Any(e => e.Id == 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/ProjectsController.cs b/Controllers/ProjectsController.cs index a6b7971..f4b3da6 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -146,14 +146,14 @@ namespace TicketManager.Controllers } [HttpGet("{id}/members")] - public async Task>> GetProjectMembers(int id) + public async Task>> GetProjectMembers(int id) { Project project = await GetProjectByIdAsync(id); return project.GetMembers(); } - [HttpPut("{id}/setMembers")] // test put & post - public async Task> SetProjectMembers(int id, List projectMembers) + [HttpPut("{id}/setMembers")] + public async Task> SetProjectMembers(int id, List projectMembers) { Project project = await GetProjectByIdAsync(id); project.SetMembers(projectMembers); @@ -168,11 +168,11 @@ namespace TicketManager.Controllers "Try again, and if the problem persists, " + "see your system administrator."); } - return project; + return NoContent(); } [HttpPut("{id}/addMembers")] - public async Task> AddMembersToProject(int id, List usersToAdd) + public async Task> AddMembersToProject(int id, List usersToAdd) { if (usersToAdd == null) { @@ -191,11 +191,11 @@ namespace TicketManager.Controllers "Try again, and if the problem persists, " + "see your system administrator."); } - return project; + return NoContent(); } [HttpPut("{id}/removeMembers")] - public async Task> RemoveMembersFromProject(int id, List usersToRemove) + public async Task> RemoveMembersFromProject(int id, List usersToRemove) { Project project = await GetProjectByIdAsync(id); project.RemoveMembers(usersToRemove); @@ -210,7 +210,7 @@ namespace TicketManager.Controllers "Try again, and if the problem persists, " + "see your system administrator."); } - return project; + return NoContent(); } private bool ProjectExists(int id) diff --git a/Data/AppDbContext.cs b/Data/AppDbContext.cs index 92e11be..b249c6b 100644 --- a/Data/AppDbContext.cs +++ b/Data/AppDbContext.cs @@ -9,7 +9,7 @@ namespace TicketManager.Data { } public DbSet Projects { get; set; } - public DbSet Users { get; set; } + public DbSet AppUsers { get; set; } public DbSet Tickets { get; set; } public DbSet Assignments { get; set; } public DbSet Edits { get; set; } diff --git a/Models/AppUser.cs b/Models/AppUser.cs new file mode 100644 index 0000000..216007a --- /dev/null +++ b/Models/AppUser.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace TicketManager.Models +{ + public class AppUser + { + public Guid Id { get; set; } + + [Required] + [StringLength(50)] + [Display(Name = "First Name")] + public string FirstName { get; set; } + + [Required] + [StringLength(50)] + [Display(Name = "Last Name")] + public string LastName { get; set; } + + [Display(Name = "Full Name")] + public string FullName => $"{FirstName} {LastName}"; + + [StringLength(200)] + [Display(Name = "Bio")] + public string Presentation { get; set; } + + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } + + [DataType(DataType.Date)] + [Display(Name = "Member since"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] + public DateTime Created_at { get; set; } = DateTime.Now; + + // [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(); + + // Methods + public List GetProjects() + { + return Assignments.Select(a => a.Project).ToList(); + } + + // public Project GetProject(int id) + // { + // return Assignments.Single(a => a.Project.Id == id).Project; + // } + + // public List GetProjectMembers(int id) + // { + // return GetProject(id).GetMembers(); + // } + public List GetTickets() + { + List tickets = new List(); + GetProjects().ForEach(p => tickets.Concat(p.Tickets)); + return tickets; + } + } +} \ No newline at end of file diff --git a/Models/Assignment.cs b/Models/Assignment.cs index 42afe79..4d3c496 100644 --- a/Models/Assignment.cs +++ b/Models/Assignment.cs @@ -5,7 +5,7 @@ namespace TicketManager.Models public class Assignment { // public int Id { get; set; } - public User User { get; set; } + public AppUser User { get; set; } public Guid UserId { get; set; } public Project Project { get; set; } public int ProjectId { get; set; } diff --git a/Models/File.cs b/Models/File.cs index 8a7d885..2b5c3b8 100644 --- a/Models/File.cs +++ b/Models/File.cs @@ -11,7 +11,7 @@ namespace TicketManager.Models public int Size { get; set; } public string Format { get; set; } - public User AddedBy { get; set; } + public AppUser AddedBy { get; set; } public int UserId { get; set; } // public ITask AddedTo { get; set; } // public int ITaskId { get; set; } diff --git a/Models/History.cs b/Models/History.cs index 53b4be7..e039343 100644 --- a/Models/History.cs +++ b/Models/History.cs @@ -9,7 +9,7 @@ namespace TicketManager.Models public string Description { get; set; } public DateTime UpdateDate { get; set; } = DateTime.Now; public ActivityType ActivityType { get; set; } = ActivityType.Undefined; - public User User { get; set; } + public AppUser User { get; set; } public int UserId { get; set; } } } \ No newline at end of file diff --git a/Models/Interfaces/ITask.cs b/Models/Interfaces/ITask.cs index 0773186..124b8c0 100644 --- a/Models/Interfaces/ITask.cs +++ b/Models/Interfaces/ITask.cs @@ -12,5 +12,17 @@ namespace TicketManager.Models DateTime CreatedAt { get; } DateTime PlannedEnding { get; set; } List Edits { get; set; } + + public virtual void AddLogEntry(string description)//, User user) + { + History Edit = new History() + { + Description = description, + ActivityType = ActivityType.Undefined, + // User = user, + UpdateDate = DateTime.Now + }; + Edits.Add(Edit); + } } } diff --git a/Models/Project.cs b/Models/Project.cs index 628c740..4ebe976 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -18,12 +18,6 @@ namespace TicketManager.Models [Display(Name = "Short Description")] public string Description { get; set; } - [DataType(DataType.EmailAddress)] - public string Email { get; set; } - - [DataType(DataType.PhoneNumber)] - public string Phone { get; set; } - [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = false)] public DateTime CreatedAt { get; private set; } = DateTime.Now; @@ -32,16 +26,20 @@ namespace TicketManager.Models [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime PlannedEnding { get; set; } + private decimal _progression; [Display(Name = "Progress")] - public float Progression + public decimal Progression { get { - return Tickets.Count() == 0 ? 0 : - (float)this.Tickets. + return _progression; + } + set + { + _progression = Tickets.Count() == 0 ? 0 : + (decimal)this.Tickets. Where(t => t.Status == Status.Done).Count() - / this.Tickets.Count() - * 100; + / this.Tickets.Count() * 100; } } @@ -49,7 +47,7 @@ namespace TicketManager.Models public Status Status { get; set; } = Status.ToDo; [Display(Name = "Project Manager")] - public User Manager { get; set; } + public AppUser Manager { get; set; } public List Assignments { get; set; } = new List(); @@ -60,11 +58,11 @@ namespace TicketManager.Models public List Files { get; set; } = new List(); // Methods - public List GetMembers() + public List GetMembers() { return this.Assignments.Select(a => a.User).ToList(); } - public void AddMembers(List usersToAdd) + public void AddMembers(List usersToAdd) { foreach (var user in usersToAdd) { @@ -76,15 +74,15 @@ namespace TicketManager.Models UserId = user.Id }; this.Assignments.Add(newAssign); - AddLogEntry(" joined the project."); + // AddLogEntry(this, " joined the project."); } } - public void RemoveMembers(List membersToRemove) + public void RemoveMembers(List membersToRemove) { this.Assignments.RemoveAll(a => membersToRemove.Contains(a.User)); } - public void SetMembers(List projectMembers) + public void SetMembers(List projectMembers) { var currentProjectMembers = this.GetMembers(); if (currentProjectMembers != null) @@ -105,7 +103,6 @@ namespace TicketManager.Models this.AddMembers(projectMembers); } - } public int GetMembersCount() => this.GetMembers().Count(); public void GetTicketsCount() => this.Tickets.Count(); @@ -116,16 +113,16 @@ namespace TicketManager.Models this.Status = Status.Done; } - private void AddLogEntry(string description)//, User user) - { - History Edit = new History() - { - Description = description, - ActivityType = ActivityType.Undefined, - // User = user, - UpdateDate = DateTime.Now - }; - this.Edits.Add(Edit); - } + // private void AddLogEntry(string description)//, User user) + // { + // History Edit = new History() + // { + // Description = description, + // ActivityType = ActivityType.Undefined, + // // User = user, + // UpdateDate = DateTime.Now + // }; + // this.Edits.Add(Edit); + // } } } \ No newline at end of file diff --git a/Models/Ticket.cs b/Models/Ticket.cs index 6176197..a423c09 100644 --- a/Models/Ticket.cs +++ b/Models/Ticket.cs @@ -16,7 +16,7 @@ namespace TicketManager.Models public Difficulty Difficulty { get; set; } = Difficulty.Undefined; public Category Category { get; set; } = Category.Undefined; - public User Creator { get; set; } + public AppUser Creator { get; set; } public Guid CreatorId { get; set; } public Project Project { get; set; } public int ProjectId { get; set; } diff --git a/Models/User.cs b/Models/User.cs deleted file mode 100644 index d7ed052..0000000 --- a/Models/User.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace TicketManager.Models -{ - public class User - { - 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; } - public DateTime Created_at { get; set; } = DateTime.Now; - public byte[] Picture { get; set; } - // public Role Role { get; set; } - - private List _assignments; - public List Assignments - { - get - { - return _assignments ?? new List(); - } - set { _assignments = value; } - } - - private List _edits; - public List Edits - { - get - { - return _edits ?? new List(); - } - set { _edits = value; } - } - - // Methods - public void GetProjects() { throw new NotImplementedException("Not Implemented"); } - public void GetProjectMembers() { throw new NotImplementedException("Not Implemented"); } - public void GetTickets() { throw new NotImplementedException("Not Implemented"); } - - - } -} \ No newline at end of file From b4b8b6f25456edd1d549a5dae9de83297613ae41 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Wed, 12 Feb 2020 18:19:03 +0100 Subject: [PATCH 8/8] ticket model & endpoints done --- Controllers/AppUsersController.cs | 2 +- Controllers/FilesController.cs | 2 +- Controllers/HistoriesController.cs | 2 +- Controllers/NotesController.cs | 2 +- Controllers/ProjectsController.cs | 4 +- Controllers/TicketsController.cs | 55 ++++++++++++++++++++------- Models/AppUser.cs | 2 +- Models/File.cs | 20 ++++++++-- Models/Project.cs | 2 +- Models/Ticket.cs | 61 +++++++++++++++--------------- README.md | 5 ++- Startup.cs | 4 +- 12 files changed, 102 insertions(+), 59 deletions(-) diff --git a/Controllers/AppUsersController.cs b/Controllers/AppUsersController.cs index ff45085..d6bbc2b 100644 --- a/Controllers/AppUsersController.cs +++ b/Controllers/AppUsersController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class UsersController : ControllerBase { diff --git a/Controllers/FilesController.cs b/Controllers/FilesController.cs index 22aa086..6481b76 100644 --- a/Controllers/FilesController.cs +++ b/Controllers/FilesController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class FilesController : ControllerBase { diff --git a/Controllers/HistoriesController.cs b/Controllers/HistoriesController.cs index 85c8d89..0934909 100644 --- a/Controllers/HistoriesController.cs +++ b/Controllers/HistoriesController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class HistoriesController : ControllerBase { diff --git a/Controllers/NotesController.cs b/Controllers/NotesController.cs index 0646e83..f2a537b 100644 --- a/Controllers/NotesController.cs +++ b/Controllers/NotesController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class NotesController : ControllerBase { diff --git a/Controllers/ProjectsController.cs b/Controllers/ProjectsController.cs index f4b3da6..611273e 100644 --- a/Controllers/ProjectsController.cs +++ b/Controllers/ProjectsController.cs @@ -13,7 +13,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { [Produces("application/json")] - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class ProjectsController : ControllerBase { @@ -152,7 +152,7 @@ namespace TicketManager.Controllers return project.GetMembers(); } - [HttpPut("{id}/setMembers")] + [HttpPut("{id}/members")] public async Task> SetProjectMembers(int id, List projectMembers) { Project project = await GetProjectByIdAsync(id); diff --git a/Controllers/TicketsController.cs b/Controllers/TicketsController.cs index bddebe9..c61ce5b 100644 --- a/Controllers/TicketsController.cs +++ b/Controllers/TicketsController.cs @@ -10,7 +10,7 @@ using TicketManager.Models; namespace TicketManager.Controllers { - [Route("api/[controller]")] + [Route("api/v1/[controller]")] [ApiController] public class TicketsController : ControllerBase { @@ -25,25 +25,14 @@ namespace TicketManager.Controllers [HttpGet] public async Task>> GetTickets() { - return await _context.Tickets - .Include(t => t.Creator) - .Include(p => p.Notes) - .Include(p => p.Edits) - .Include(p => p.Files) - .AsNoTracking() - .ToListAsync(); + return await getAllTicketsAsync(); } // GET: api/Tickets/5 [HttpGet("{id}")] public async Task> GetTicket(int id) { - var ticket = await _context.Tickets - .Include(t => t.Creator) - .Include(p => p.Notes) - .Include(p => p.Edits) - .Include(p => p.Files) - .FirstOrDefaultAsync(t => t.Id == id); + var ticket = await getTicketByIdAsync(id); if (ticket == null) { @@ -113,9 +102,47 @@ namespace TicketManager.Controllers return ticket; } + [HttpGet("{id}/assignees")] + public async Task>> GetTicketAssignees(int id) + { + Ticket ticket = await getTicketByIdAsync(id); + return ticket.GetAssignees(); + } + + [HttpPut("{id}/closed")] + public async Task CloseTicket(int id) + { + Ticket ticket = await getTicketByIdAsync(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); + } } } diff --git a/Models/AppUser.cs b/Models/AppUser.cs index 216007a..fb53b1a 100644 --- a/Models/AppUser.cs +++ b/Models/AppUser.cs @@ -34,7 +34,7 @@ namespace TicketManager.Models [DataType(DataType.Date)] [Display(Name = "Member since"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] - public DateTime Created_at { get; set; } = DateTime.Now; + public DateTime Created_at { get; private set; } = DateTime.Now; // [Display(Name = "Avatar")] // public byte[] Picture { get; set; } diff --git a/Models/File.cs b/Models/File.cs index 2b5c3b8..8dd01e4 100644 --- a/Models/File.cs +++ b/Models/File.cs @@ -5,11 +5,25 @@ namespace TicketManager.Models { public class File { + + public int Id { get; set; } - public string Location { get; set; } + + public string FileName { get; set; } + + private string _location; + public string Location + { + get { return _location; } + private set + { + string filesUrl = ""; + _location = $"{filesUrl}/{FileName}"; + } + } public string Description { get; set; } - public int Size { get; set; } - public string Format { get; set; } + public int Size { get; set; } // deduce auto from FileName + public string Format { get; set; } // deduce auto from FileName public AppUser AddedBy { get; set; } public int UserId { get; set; } diff --git a/Models/Project.cs b/Models/Project.cs index 4ebe976..2048ccf 100644 --- a/Models/Project.cs +++ b/Models/Project.cs @@ -34,7 +34,7 @@ namespace TicketManager.Models { return _progression; } - set + private set { _progression = Tickets.Count() == 0 ? 0 : (decimal)this.Tickets. diff --git a/Models/Ticket.cs b/Models/Ticket.cs index a423c09..29c5e01 100644 --- a/Models/Ticket.cs +++ b/Models/Ticket.cs @@ -1,14 +1,27 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; namespace TicketManager.Models { public class Ticket : ITask { public int Id { get; set; } + + [Required] + [StringLength(100)] public string Title { get; set; } + + [StringLength(100)] public string Description { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.Now; + + [DataType(DataType.Date)] + [Display(Name = "Creation Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] + public DateTime CreatedAt { get; private set; } = DateTime.Now; + + [DataType(DataType.Date)] + [Display(Name = "Estimated Ending Date"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}")] public DateTime PlannedEnding { get; set; } public Status Status { get; set; } = Status.ToDo; @@ -16,42 +29,28 @@ namespace TicketManager.Models public Difficulty Difficulty { get; set; } = Difficulty.Undefined; public Category Category { get; set; } = Category.Undefined; + [Display(Name = "Created By")] public AppUser Creator { get; set; } public Guid CreatorId { get; set; } + + [Display(Name = "Project")] public Project Project { get; set; } public int ProjectId { get; set; } - private List _notes; - public List Notes - { - get - { - return _notes ?? new List(); - } - set { _notes = value; } - } - private List _edits; - public List Edits - { - get - { - return _edits ?? new List(); - } - set { _edits = value; } - } - private List _files; - public List Files - { - get - { - return _files ?? new List(); - } - set { _files = value; } - } + public List Notes = new List(); + + public List Edits = new List(); + + public List Files = new List(); // Methods - public void GetAssignees() { throw new NotImplementedException("Not Implemented"); } + public List GetAssignees() + { + return Project.Assignments.Select(a => a.User).ToList(); + } public void GetLastUpdateTime() { throw new NotImplementedException("Not Implemented"); } - public void Close() { throw new NotImplementedException("Not Implemented"); } - public void AddFile() { throw new NotImplementedException("Not Implemented"); } + public void Close() + { + Status = Status.Done; + } } } \ No newline at end of file diff --git a/README.md b/README.md index f8613bc..0b0c2eb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ ## API Documentation -- [Internal Link. Don't forget to update](https://localhost:5001/swagger/index.html) +### v1 + +- [Internal Link. Don't forget to update](https://localhost:5001/swagger) ## Features @@ -39,3 +41,4 @@ - Ensure Tickets Files belong to Project Files - Write a query class to refactor code and optimize perf on get queries (AsNoTracking) - Async model methods ? +- setMembers & removeMembers from project api not working diff --git a/Startup.cs b/Startup.cs index 0a40edf..b169da0 100644 --- a/Startup.cs +++ b/Startup.cs @@ -53,7 +53,7 @@ namespace TicketManager { Version = "v1", Title = "Ticket Manager API", - Description = "A simple example ASP.NET Core Web API", + Description = "Ticket Manger API for Teams", Contact = new OpenApiContact { Name = "Ruidy Nemausat", @@ -91,7 +91,7 @@ namespace TicketManager app.UseSwaggerUI(c => { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ticket Manager API V1"); + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ticket Manager API v1"); });