diff --git a/Controllers/TodoItemsController.cs b/Controllers/TodoItemsController.cs deleted file mode 100644 index 0ef138e7..00000000 --- a/Controllers/TodoItemsController.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using TodoApi.Models; - -namespace TodoApi.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class TodoItemsController : ControllerBase - { - private readonly TodoContext _context; - - public TodoItemsController(TodoContext context) - { - _context = context; - } - - [HttpGet] - public async Task>> GetTodoItems() - { - return await _context.TodoItems - .Select(x => ItemToDTO(x)) - .ToListAsync(); - } - - [HttpGet("{id}")] - public async Task> GetTodoItem(long id) - { - var todoItem = await _context.TodoItems.FindAsync(id); - - if (todoItem == null) - { - return NotFound(); - } - - return ItemToDTO(todoItem); - } - - [HttpPut("{id}")] - public async Task UpdateTodoItem(long id, TodoItemDTO todoItemDTO) - { - if (id != todoItemDTO.Id) - { - return BadRequest(); - } - - var todoItem = await _context.TodoItems.FindAsync(id); - if (todoItem == null) - { - return NotFound(); - } - - todoItem.Name = todoItemDTO.Name; - todoItem.IsComplete = todoItemDTO.IsComplete; - - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) when (!TodoItemExists(id)) - { - return NotFound(); - } - - return NoContent(); - } - - [HttpPost] - public async Task> CreateTodoItem(TodoItemDTO todoItemDTO) - { - var todoItem = new TodoItem - { - IsComplete = todoItemDTO.IsComplete, - Name = todoItemDTO.Name - }; - - _context.TodoItems.Add(todoItem); - await _context.SaveChangesAsync(); - - return CreatedAtAction( - nameof(GetTodoItem), - new { id = todoItem.Id }, - ItemToDTO(todoItem)); - } - - [HttpDelete("{id}")] - public async Task DeleteTodoItem(long id) - { - var todoItem = await _context.TodoItems.FindAsync(id); - - if (todoItem == null) - { - return NotFound(); - } - - _context.TodoItems.Remove(todoItem); - await _context.SaveChangesAsync(); - - return NoContent(); - } - - private bool TodoItemExists(long id) => - _context.TodoItems.Any(e => e.Id == id); - - private static TodoItemDTO ItemToDTO(TodoItem todoItem) => - new TodoItemDTO - { - Id = todoItem.Id, - Name = todoItem.Name, - IsComplete = todoItem.IsComplete - }; - } -} diff --git a/Models/TodoContext.cs b/Models/TodoContext.cs deleted file mode 100644 index 6e59e363..00000000 --- a/Models/TodoContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace TodoApi.Models -{ - public class TodoContext : DbContext - { - public TodoContext(DbContextOptions options) - : base(options) - { - } - - public DbSet TodoItems { get; set; } - } -} \ No newline at end of file diff --git a/Models/TodoItem.cs b/Models/TodoItem.cs deleted file mode 100644 index 1f6e5465..00000000 --- a/Models/TodoItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace TodoApi.Models -{ - #region snippet - public class TodoItem - { - public long Id { get; set; } - public string Name { get; set; } - public bool IsComplete { get; set; } - public string Secret { get; set; } - } - #endregion -} \ No newline at end of file diff --git a/TodoApiDTO.sln b/TodoApiDTO.sln index e49c182b..05bdc857 100644 --- a/TodoApiDTO.sln +++ b/TodoApiDTO.sln @@ -1,9 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30002.166 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32602.215 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApiDTO", "TodoApiDTO.csproj", "{623124F9-F5BA-42DD-BC26-A1720774229C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApiDTO", "VeletechToDoAPI\TodoApiDTO.csproj", "{F920CF77-6BD6-4DCB-A404-175B43E18CB1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoCore", "TodoCore\TodoCore.csproj", "{1F48623E-0BE5-4F33-9DA9-F0790EC2CD4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoInfrastructure", "TodoInfrastructure\TodoInfrastructure.csproj", "{6F3C44A1-A612-474D-BDA9-5CD1167D77AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoApplication", "TodoApplication\TodoApplication.csproj", "{45A28653-AC55-4F6D-B0B5-06DD69E6D563}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +17,22 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.Build.0 = Release|Any CPU + {F920CF77-6BD6-4DCB-A404-175B43E18CB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F920CF77-6BD6-4DCB-A404-175B43E18CB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F920CF77-6BD6-4DCB-A404-175B43E18CB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F920CF77-6BD6-4DCB-A404-175B43E18CB1}.Release|Any CPU.Build.0 = Release|Any CPU + {1F48623E-0BE5-4F33-9DA9-F0790EC2CD4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F48623E-0BE5-4F33-9DA9-F0790EC2CD4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F48623E-0BE5-4F33-9DA9-F0790EC2CD4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F48623E-0BE5-4F33-9DA9-F0790EC2CD4A}.Release|Any CPU.Build.0 = Release|Any CPU + {6F3C44A1-A612-474D-BDA9-5CD1167D77AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F3C44A1-A612-474D-BDA9-5CD1167D77AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F3C44A1-A612-474D-BDA9-5CD1167D77AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F3C44A1-A612-474D-BDA9-5CD1167D77AA}.Release|Any CPU.Build.0 = Release|Any CPU + {45A28653-AC55-4F6D-B0B5-06DD69E6D563}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45A28653-AC55-4F6D-B0B5-06DD69E6D563}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45A28653-AC55-4F6D-B0B5-06DD69E6D563}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45A28653-AC55-4F6D-B0B5-06DD69E6D563}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TodoApplication/ApplicationCollectionExtension.cs b/TodoApplication/ApplicationCollectionExtension.cs new file mode 100644 index 00000000..ae13e375 --- /dev/null +++ b/TodoApplication/ApplicationCollectionExtension.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; +using TodoApplication.AutoMapperProfiles; +using TodoApplication.Services; +using TodoCore.Services; + +namespace TodoApplication +{ + public static class ApplicationCollectionExtension + { + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddAutoMapper(typeof(TodoItemProfile)); + return services; + } + } +} diff --git a/TodoApplication/AutoMapperProfiles/TodoItemProfile.cs b/TodoApplication/AutoMapperProfiles/TodoItemProfile.cs new file mode 100644 index 00000000..3dce1f50 --- /dev/null +++ b/TodoApplication/AutoMapperProfiles/TodoItemProfile.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using TodoCore.Data.Entities; +using TodoCore.DTOs; + +namespace TodoApplication.AutoMapperProfiles +{ + public class TodoItemProfile : Profile + { + public TodoItemProfile() + { + CreateMap(); + CreateMap(); + } + + } +} diff --git a/TodoApplication/Services/AddTodoItemService.cs b/TodoApplication/Services/AddTodoItemService.cs new file mode 100644 index 00000000..d649541a --- /dev/null +++ b/TodoApplication/Services/AddTodoItemService.cs @@ -0,0 +1,27 @@ +using AutoMapper; +using System.Threading.Tasks; +using TodoCore.Data.Entities; +using TodoCore.Data.Interfaces; +using TodoCore.DTOs; +using TodoCore.Services; + +namespace TodoApplication.Services +{ + public class AddTodoItemService : IAddTodoItemService + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + public AddTodoItemService(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + + public Task AddTodoItemAsync(TodoItemDTO todoItemDTO) + { + var entity = _mapper.Map(todoItemDTO); + _unitOfWork.TodoItemReposytory.Add(entity); + return _unitOfWork.SaveChangesAsync(); + } + } +} diff --git a/TodoApplication/Services/DeleteTodoItemService.cs b/TodoApplication/Services/DeleteTodoItemService.cs new file mode 100644 index 00000000..5e81688d --- /dev/null +++ b/TodoApplication/Services/DeleteTodoItemService.cs @@ -0,0 +1,49 @@ +using AutoMapper; +using System; +using System.Threading.Tasks; +using TodoCore.Data.Entities; +using TodoCore.Data.Interfaces; +using TodoCore.DTOs; +using TodoCore.Exceptions; +using TodoCore.Services; + +namespace TodoApplication.Services +{ + public class DeleteTodoItemService : IDeleteTodoItemService + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + + public DeleteTodoItemService(IUnitOfWork unitOfWork, IMapper mapper) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + } + + public async Task DeleteTodoItemAsync(long id) + { + TodoItem todoItem; + try + { + todoItem = await _unitOfWork.TodoItemReposytory.GetByIdAsync(id); + } + catch(EntityNotFoundException ex) + { + throw ex; + } + using var transaction = _unitOfWork.StartTransation(); + try + { + _unitOfWork.TodoItemReposytory.Delete(todoItem); + await _unitOfWork.SaveChangesAsync(); + transaction.Commit(); + return _mapper.Map(todoItem); + } + catch(Exception ex) + { + transaction.Rollback(); + throw new SomethingWentWrongException("Something went wrong while deleting todo item"); + } + } + } +} diff --git a/TodoApplication/Services/GetTodoItemService.cs b/TodoApplication/Services/GetTodoItemService.cs new file mode 100644 index 00000000..6a53bf22 --- /dev/null +++ b/TodoApplication/Services/GetTodoItemService.cs @@ -0,0 +1,35 @@ +using AutoMapper; +using System.Threading.Tasks; +using TodoCore.Data.Entities; +using TodoCore.Data.Interfaces; +using TodoCore.DTOs; +using TodoCore.Exceptions; +using TodoCore.Services; + +namespace TodoApplication.Services +{ + internal class GetTodoItemService : IGetTodoItemService + { + private readonly ITodoItemRepository _todoItemReposytory; + private readonly IMapper _mapper; + + public GetTodoItemService(ITodoItemRepository todoItemReposytory, IMapper mapper) + { + _todoItemReposytory = todoItemReposytory; + _mapper = mapper; + } + + public async Task GetTodoItemAsync(long id) + { + try + { + var entity = await _todoItemReposytory.GetByIdAsync(id); + return _mapper.Map(entity); + } + catch(EntityNotFoundException ex) + { + throw ex; + } + } + } +} diff --git a/TodoApplication/Services/GetTodoItemsService.cs b/TodoApplication/Services/GetTodoItemsService.cs new file mode 100644 index 00000000..fa3e9950 --- /dev/null +++ b/TodoApplication/Services/GetTodoItemsService.cs @@ -0,0 +1,26 @@ +using AutoMapper; +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoCore.Data.Interfaces; +using TodoCore.DTOs; +using TodoCore.Services; + +namespace TodoApplication.Services +{ + public class GetTodoItemsService : IGetTodoItemsService + { + private readonly ITodoItemRepository _todoItemReposytory; + private readonly IMapper _mapper; + + public GetTodoItemsService(ITodoItemRepository todoItemReposytory, IMapper mapper) + { + _todoItemReposytory = todoItemReposytory; + _mapper = mapper; + } + public async Task> GetTodoItemsAsync() + { + var todoItems = await _todoItemReposytory.GetAllAsync(); + return _mapper.Map>(todoItems); + } + } +} diff --git a/TodoApplication/Services/UpdateTodoItemService.cs b/TodoApplication/Services/UpdateTodoItemService.cs new file mode 100644 index 00000000..cb0ee3b5 --- /dev/null +++ b/TodoApplication/Services/UpdateTodoItemService.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using TodoCore.Data.Entities; +using TodoCore.Data.Interfaces; +using TodoCore.DTOs; +using TodoCore.Exceptions; +using TodoCore.Services; + +namespace TodoApplication.Services +{ + public class UpdateTodoItemService : IUpdateTodoItemService + { + private readonly IUnitOfWork _unitOfWork; + + public UpdateTodoItemService(IUnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public async Task UpdateTodoItemAsync(long id, UpdateTodoItemDTO itemDTO) + { + TodoItem todoItem; + try + { + todoItem = await _unitOfWork.TodoItemReposytory.GetByIdAsync(id); + } + catch(EntityNotFoundException ex) + { + throw ex; + } + todoItem.Name = itemDTO.Name; + todoItem.IsComplete = itemDTO.IsComplete; + using var transaction = _unitOfWork.StartTransation(); + try + { + _unitOfWork.TodoItemReposytory.Update(todoItem); + await _unitOfWork.SaveChangesAsync(); + transaction.Commit(); + return itemDTO; + } + catch(Exception ex) + { + transaction.Rollback(); + throw new SomethingWentWrongException("Something went wrong wile updating item"); + } + } + } +} diff --git a/TodoApplication/TodoApplication.csproj b/TodoApplication/TodoApplication.csproj new file mode 100644 index 00000000..178ba881 --- /dev/null +++ b/TodoApplication/TodoApplication.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/Models/TodoItemDTO.cs b/TodoCore/DTOs/TodoItemDTO.cs similarity index 53% rename from Models/TodoItemDTO.cs rename to TodoCore/DTOs/TodoItemDTO.cs index e66a500a..9d5f99c6 100644 --- a/Models/TodoItemDTO.cs +++ b/TodoCore/DTOs/TodoItemDTO.cs @@ -1,11 +1,9 @@ -namespace TodoApi.Models +namespace TodoCore.DTOs { - #region snippet public class TodoItemDTO { public long Id { get; set; } public string Name { get; set; } - public bool IsComplete { get; set; } + public bool IsComplete { get; set; } } - #endregion } diff --git a/TodoCore/DTOs/UpdateTodoItemDTO.cs b/TodoCore/DTOs/UpdateTodoItemDTO.cs new file mode 100644 index 00000000..5c197206 --- /dev/null +++ b/TodoCore/DTOs/UpdateTodoItemDTO.cs @@ -0,0 +1,8 @@ +namespace TodoCore.DTOs +{ + public class UpdateTodoItemDTO + { + public string Name { get; set; } + public bool IsComplete { get; set; } + } +} diff --git a/TodoCore/Data/Common/BaseEntity.cs b/TodoCore/Data/Common/BaseEntity.cs new file mode 100644 index 00000000..d17bbac0 --- /dev/null +++ b/TodoCore/Data/Common/BaseEntity.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace TodoCore.Data.Common +{ + public class BaseEntity + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public long Id { get; set; } + } +} diff --git a/TodoCore/Data/Entities/TodoItem.cs b/TodoCore/Data/Entities/TodoItem.cs new file mode 100644 index 00000000..04ef6cd2 --- /dev/null +++ b/TodoCore/Data/Entities/TodoItem.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using TodoCore.Data.Common; + +namespace TodoCore.Data.Entities +{ + public class TodoItem : BaseEntity + { + [Required] + public string Name { get; set; } + [Required] + public bool IsComplete { get; set; } + } +} diff --git a/TodoCore/Data/Interfaces/IRepository.cs b/TodoCore/Data/Interfaces/IRepository.cs new file mode 100644 index 00000000..7ecaf9e4 --- /dev/null +++ b/TodoCore/Data/Interfaces/IRepository.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoCore.Data.Common; + +namespace TodoCore.Data.Interfaces +{ + public interface IRepository where T : BaseEntity + { + Task> GetAllAsync(); + Task GetByIdAsync(long id); + Task IsExistAsync(long id); + void Add(T entity); + T Update(T entity); + T Delete(T entity); + } +} diff --git a/TodoCore/Data/Interfaces/ITodoItemRepository.cs b/TodoCore/Data/Interfaces/ITodoItemRepository.cs new file mode 100644 index 00000000..7aca6892 --- /dev/null +++ b/TodoCore/Data/Interfaces/ITodoItemRepository.cs @@ -0,0 +1,9 @@ +using TodoCore.Data.Common; +using TodoCore.Data.Entities; + +namespace TodoCore.Data.Interfaces +{ + public interface ITodoItemRepository : IRepository + { + } +} diff --git a/TodoCore/Data/Interfaces/IUnitOfWork.cs b/TodoCore/Data/Interfaces/IUnitOfWork.cs new file mode 100644 index 00000000..c07d8e0c --- /dev/null +++ b/TodoCore/Data/Interfaces/IUnitOfWork.cs @@ -0,0 +1,12 @@ +using System.Data; +using System.Threading.Tasks; + +namespace TodoCore.Data.Interfaces +{ + public interface IUnitOfWork + { + public ITodoItemRepository TodoItemReposytory { get; } + public Task SaveChangesAsync(); + public IDbTransaction StartTransation(); + } +} diff --git a/TodoCore/Exceptions/EntityNotFoundException.cs b/TodoCore/Exceptions/EntityNotFoundException.cs new file mode 100644 index 00000000..433eef8c --- /dev/null +++ b/TodoCore/Exceptions/EntityNotFoundException.cs @@ -0,0 +1,13 @@ +using System; +using TodoCore.Data.Common; + +namespace TodoCore.Exceptions +{ + public class EntityNotFoundException : Exception + where TEnt : BaseEntity + { + public EntityNotFoundException(string message) : base($"{typeof(TEnt).Name} {message}") + { + } + } +} diff --git a/TodoCore/Exceptions/SomethingWentWrongException.cs b/TodoCore/Exceptions/SomethingWentWrongException.cs new file mode 100644 index 00000000..1f68395a --- /dev/null +++ b/TodoCore/Exceptions/SomethingWentWrongException.cs @@ -0,0 +1,9 @@ +using System; + +namespace TodoCore.Exceptions +{ + public class SomethingWentWrongException : Exception + { + public SomethingWentWrongException(string message) : base(message) { } + } +} diff --git a/TodoCore/Services/IAddTodoItemService.cs b/TodoCore/Services/IAddTodoItemService.cs new file mode 100644 index 00000000..4b02dc95 --- /dev/null +++ b/TodoCore/Services/IAddTodoItemService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TodoCore.DTOs; + +namespace TodoCore.Services +{ + public interface IAddTodoItemService + { + Task AddTodoItemAsync(TodoItemDTO todoItemDTO); + } +} diff --git a/TodoCore/Services/IDeleteTodoItemService.cs b/TodoCore/Services/IDeleteTodoItemService.cs new file mode 100644 index 00000000..900db708 --- /dev/null +++ b/TodoCore/Services/IDeleteTodoItemService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TodoCore.DTOs; + +namespace TodoCore.Services +{ + public interface IDeleteTodoItemService + { + Task DeleteTodoItemAsync(long id); + } +} diff --git a/TodoCore/Services/IGetTodoItemService.cs b/TodoCore/Services/IGetTodoItemService.cs new file mode 100644 index 00000000..fc34cf3b --- /dev/null +++ b/TodoCore/Services/IGetTodoItemService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using TodoCore.DTOs; + +namespace TodoCore.Services +{ + public interface IGetTodoItemService + { + Task GetTodoItemAsync(long id); + } +} diff --git a/TodoCore/Services/IGetTodoItemsService.cs b/TodoCore/Services/IGetTodoItemsService.cs new file mode 100644 index 00000000..9b575d1f --- /dev/null +++ b/TodoCore/Services/IGetTodoItemsService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoCore.DTOs; + +namespace TodoCore.Services +{ + public interface IGetTodoItemsService + { + Task> GetTodoItemsAsync(); + } +} diff --git a/TodoCore/Services/IUpdateTodoItemService.cs b/TodoCore/Services/IUpdateTodoItemService.cs new file mode 100644 index 00000000..cede9230 --- /dev/null +++ b/TodoCore/Services/IUpdateTodoItemService.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using TodoCore.DTOs; + +namespace TodoCore.Services +{ + public interface IUpdateTodoItemService + { + Task UpdateTodoItemAsync(long id, UpdateTodoItemDTO itemDTO); + } +} diff --git a/TodoCore/TodoCore.csproj b/TodoCore/TodoCore.csproj new file mode 100644 index 00000000..cb631906 --- /dev/null +++ b/TodoCore/TodoCore.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp3.1 + + + diff --git a/TodoInfrastructure/DataAccess/ApplicationDbContext.cs b/TodoInfrastructure/DataAccess/ApplicationDbContext.cs new file mode 100644 index 00000000..14961ee3 --- /dev/null +++ b/TodoInfrastructure/DataAccess/ApplicationDbContext.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using TodoCore.Data.Entities; + +namespace TodoInfrastructure.DataAccess +{ + public class ApplicationDbContext : DbContext + { + public ApplicationDbContext(DbContextOptions options) : base(options) { } + public DbSet TodoItems { get; set; } + } +} diff --git a/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.Designer.cs b/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.Designer.cs new file mode 100644 index 00000000..0ba16424 --- /dev/null +++ b/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.Designer.cs @@ -0,0 +1,44 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoInfrastructure.DataAccess; + +namespace TodoInfrastructure.DataAccess.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230427220746_InitialMigration")] + partial class InitialMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.32") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoCore.Data.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.cs b/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.cs new file mode 100644 index 00000000..45cddd0c --- /dev/null +++ b/TodoInfrastructure/DataAccess/Migrations/20230427220746_InitialMigration.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace TodoInfrastructure.DataAccess.Migrations +{ + public partial class InitialMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: false), + IsComplete = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoItems"); + } + } +} diff --git a/TodoInfrastructure/DataAccess/Migrations/ApplicationDbContextModelSnapshot.cs b/TodoInfrastructure/DataAccess/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 00000000..3370f7c1 --- /dev/null +++ b/TodoInfrastructure/DataAccess/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,42 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoInfrastructure.DataAccess; + +namespace TodoInfrastructure.DataAccess.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.32") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoCore.Data.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TodoInfrastructure/DataAccess/Repositories/Repository.cs b/TodoInfrastructure/DataAccess/Repositories/Repository.cs new file mode 100644 index 00000000..c546992d --- /dev/null +++ b/TodoInfrastructure/DataAccess/Repositories/Repository.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoCore.Data.Common; +using TodoCore.Data.Interfaces; +using TodoCore.Exceptions; + +namespace TodoInfrastructure.DataAccess.Repositories +{ + public class Repository : IRepository + where T : BaseEntity + { + private readonly ApplicationDbContext _context; + + public Repository(ApplicationDbContext context) + { + _context = context; + } + + public void Add(T entity) + { + _context.Set().Add(entity); + } + + public T Delete(T entity) + { + _context.Set().Remove(entity); + return entity; + } + + public Task> GetAllAsync() + { + return _context.Set().ToListAsync(); + } + + public async Task GetByIdAsync(long id) + { + var entity = await _context.Set().SingleOrDefaultAsync(e => e.Id == id); + if (entity == null) + throw new EntityNotFoundException($"entity with id {id} wasn't found"); + return entity; + } + + public async Task IsExistAsync(long id) + { + var entity = await _context.Set().SingleOrDefaultAsync(e => e.Id == id); + return entity != null; + } + + public T Update(T entity) + { + _context.Set().Update(entity); + return entity; + } + } +} diff --git a/TodoInfrastructure/DataAccess/Repositories/TodoItemRepository.cs b/TodoInfrastructure/DataAccess/Repositories/TodoItemRepository.cs new file mode 100644 index 00000000..8b77c46a --- /dev/null +++ b/TodoInfrastructure/DataAccess/Repositories/TodoItemRepository.cs @@ -0,0 +1,10 @@ +using TodoCore.Data.Entities; +using TodoCore.Data.Interfaces; + +namespace TodoInfrastructure.DataAccess.Repositories +{ + public class TodoItemRepository : Repository, ITodoItemRepository + { + public TodoItemRepository(ApplicationDbContext context) : base(context) { } + } +} diff --git a/TodoInfrastructure/DataAccess/UOW/UnitOfWork.cs b/TodoInfrastructure/DataAccess/UOW/UnitOfWork.cs new file mode 100644 index 00000000..442da051 --- /dev/null +++ b/TodoInfrastructure/DataAccess/UOW/UnitOfWork.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using System.Threading.Tasks; +using TodoCore.Data.Interfaces; + +namespace TodoInfrastructure.DataAccess.UOW +{ + public class UnitOfWork : IUnitOfWork + { + private readonly ApplicationDbContext _context; + private readonly ITodoItemRepository _todoItemReposytory; + + public ITodoItemRepository TodoItemReposytory => _todoItemReposytory; + public UnitOfWork(ApplicationDbContext context, ITodoItemRepository todoItemReposytory) + { + _context = context; + _todoItemReposytory = todoItemReposytory; + } + + public Task SaveChangesAsync() + { + return _context.SaveChangesAsync(); + } + + public IDbTransaction StartTransation() + { + var transaction = _context.Database.BeginTransaction(); + return transaction.GetDbTransaction(); + } + } +} diff --git a/TodoInfrastructure/InfrastructureCollectionExtension.cs b/TodoInfrastructure/InfrastructureCollectionExtension.cs new file mode 100644 index 00000000..f8ec0023 --- /dev/null +++ b/TodoInfrastructure/InfrastructureCollectionExtension.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using TodoCore.Data.Interfaces; +using TodoInfrastructure.DataAccess; +using TodoInfrastructure.DataAccess.Repositories; +using TodoInfrastructure.DataAccess.UOW; + +namespace TodoInfrastructure +{ + public static class InfrastructureCollectionExtension + { + public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + services.AddTransient(); + services.AddTransient(); + services.AddDbContext(options => + { + options.UseSqlServer(configuration.GetConnectionString(nameof(ApplicationDbContext))); + }); + return services; + + } + } +} diff --git a/TodoInfrastructure/Logger/SerilogConfiguration.cs b/TodoInfrastructure/Logger/SerilogConfiguration.cs new file mode 100644 index 00000000..05143513 --- /dev/null +++ b/TodoInfrastructure/Logger/SerilogConfiguration.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace TodoInfrastructure.Logger +{ + public static class SerilogConfiguration + { + public static IHostBuilder ConfigureSerilog(this IHostBuilder builder) + { + var loggerConfig = new LoggerConfiguration() + .WriteTo.File("Logs/errors-log.logs", + rollingInterval: RollingInterval.Day) + .MinimumLevel.Error() + .CreateLogger(); + + builder.UseSerilog(loggerConfig); + return builder; + } + } +} diff --git a/TodoInfrastructure/TodoInfrastructure.csproj b/TodoInfrastructure/TodoInfrastructure.csproj new file mode 100644 index 00000000..6b2b3a09 --- /dev/null +++ b/TodoInfrastructure/TodoInfrastructure.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/VeletechToDoAPI/Controllers/TodoItemsController.cs b/VeletechToDoAPI/Controllers/TodoItemsController.cs new file mode 100644 index 00000000..3fa422d6 --- /dev/null +++ b/VeletechToDoAPI/Controllers/TodoItemsController.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Mvc; +using Serilog; +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoCore.Data.Entities; +using TodoCore.DTOs; +using TodoCore.Exceptions; +using TodoCore.Services; + +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TodoItemsController : ControllerBase + { + private readonly IAddTodoItemService _addTodoItemService; + private readonly IGetTodoItemService _getTodoItemService; + private readonly IGetTodoItemsService _getTodoItemsService; + private readonly IUpdateTodoItemService _updateTodoItemService; + private readonly IDeleteTodoItemService _deleteTodoItemService; + private readonly ILogger _logger; + + public TodoItemsController(IAddTodoItemService addTodoItemService, IGetTodoItemService getTodoItemService, IGetTodoItemsService getTodoItemsService, + IUpdateTodoItemService updateTodoItemService, IDeleteTodoItemService deleteTodoItemService, ILogger logger) + { + _addTodoItemService = addTodoItemService; + _getTodoItemService = getTodoItemService; + _getTodoItemsService = getTodoItemsService; + _updateTodoItemService = updateTodoItemService; + _deleteTodoItemService = deleteTodoItemService; + _logger = logger; + } + + [HttpGet] + public async Task>> GetTodoItems() + { + var result = await _getTodoItemsService.GetTodoItemsAsync(); + return Ok(result); + } + + [HttpGet("{id}")] + public async Task> GetTodoItem(long id) + { + try + { + var result = await _getTodoItemService.GetTodoItemAsync(id); + return Ok(result); + } + catch(EntityNotFoundException ex) + { + _logger.Error(ex.Message); + return NotFound(ex.Message); + } + } + + [HttpPut("{id}")] + public async Task UpdateTodoItem(long id, UpdateTodoItemDTO todoItemDTO) + { + try + { + var result = await _updateTodoItemService.UpdateTodoItemAsync(id, todoItemDTO); + return Ok(result); + } + catch(EntityNotFoundException ex) + { + _logger.Error(ex.Message); + return NotFound(ex.Message); + } + catch(SomethingWentWrongException ex) + { + _logger.Error(ex.Message); + return StatusCode(500, ex.Message); + } + } + + [HttpPost] + public async Task> CreateTodoItem(TodoItemDTO todoItemDTO) + { + await _addTodoItemService.AddTodoItemAsync(todoItemDTO); + return Ok(todoItemDTO); + } + + [HttpDelete("{id}")] + public async Task DeleteTodoItem(long id) + { + try + { + var result = await _deleteTodoItemService.DeleteTodoItemAsync(id); + return Ok(); + } + catch (EntityNotFoundException ex) + { + _logger.Error(ex.Message); + return NotFound(); + } + catch (SomethingWentWrongException ex) + { + _logger.Error(ex.Message); + return StatusCode(500); + } + } + } +} diff --git a/Program.cs b/VeletechToDoAPI/Program.cs similarity index 74% rename from Program.cs rename to VeletechToDoAPI/Program.cs index b27ac16a..83cb4ce4 100644 --- a/Program.cs +++ b/VeletechToDoAPI/Program.cs @@ -1,11 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using TodoInfrastructure.Logger; namespace TodoApi { @@ -18,6 +13,7 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .ConfigureSerilog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); diff --git a/Properties/launchSettings.json b/VeletechToDoAPI/Properties/launchSettings.json similarity index 100% rename from Properties/launchSettings.json rename to VeletechToDoAPI/Properties/launchSettings.json diff --git a/README.md b/VeletechToDoAPI/README.md similarity index 100% rename from README.md rename to VeletechToDoAPI/README.md diff --git a/Startup.cs b/VeletechToDoAPI/Startup.cs similarity index 70% rename from Startup.cs rename to VeletechToDoAPI/Startup.cs index bbfbc83d..446c45ed 100644 --- a/Startup.cs +++ b/VeletechToDoAPI/Startup.cs @@ -10,8 +10,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using TodoApi.Models; +using Microsoft.OpenApi.Models; +using TodoApplication; +using TodoInfrastructure; namespace TodoApi { @@ -27,9 +28,18 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddDbContext(opt => - opt.UseInMemoryDatabase("TodoList")); + services.AddInfrastructure(Configuration); + services.AddApplication(); services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "TodoItemsAPI", + Version = "v1", + Description = "TodoItems API" + }); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -38,6 +48,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoItems API")); } app.UseHttpsRedirection(); diff --git a/TodoApiDTO.csproj b/VeletechToDoAPI/TodoApiDTO.csproj similarity index 64% rename from TodoApiDTO.csproj rename to VeletechToDoAPI/TodoApiDTO.csproj index bba6f6af..be37a5b9 100644 --- a/TodoApiDTO.csproj +++ b/VeletechToDoAPI/TodoApiDTO.csproj @@ -10,8 +10,15 @@ all - + + + + + + + + diff --git a/appsettings.Development.json b/VeletechToDoAPI/appsettings.Development.json similarity index 69% rename from appsettings.Development.json rename to VeletechToDoAPI/appsettings.Development.json index 8983e0fc..118df045 100644 --- a/appsettings.Development.json +++ b/VeletechToDoAPI/appsettings.Development.json @@ -5,5 +5,8 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } + }, + "ConnectionStrings": { + "ApplicationDbContext": "ConnString" } } diff --git a/appsettings.json b/VeletechToDoAPI/appsettings.json similarity index 100% rename from appsettings.json rename to VeletechToDoAPI/appsettings.json