diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..df5b2381 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "TodoApiDTO.sln" +} \ No newline at end of file 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/Startup.cs b/Startup.cs deleted file mode 100644 index bbfbc83d..00000000 --- a/Startup.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using TodoApi.Models; - -namespace TodoApi -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddDbContext(opt => - opt.UseInMemoryDatabase("TodoList")); - services.AddControllers(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } - } -} diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj deleted file mode 100644 index bba6f6af..00000000 --- a/TodoApiDTO.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp3.1 - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/TodoApiDTO.sln b/TodoApiDTO.sln index e49c182b..bb088ff1 100644 --- a/TodoApiDTO.sln +++ b/TodoApiDTO.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30002.166 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", ".\src\TodoApi\TodoApiDTO.csproj", "{623124F9-F5BA-42DD-BC26-A1720774229C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +15,14 @@ Global {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 + {0FAF1E6A-594A-4FDA-9F8D-B32AD299F808}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0FAF1E6A-594A-4FDA-9F8D-B32AD299F808}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0FAF1E6A-594A-4FDA-9F8D-B32AD299F808}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0FAF1E6A-594A-4FDA-9F8D-B32AD299F808}.Release|Any CPU.Build.0 = Release|Any CPU + {ED312825-6F90-4170-A494-1AEFC4ACBAED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED312825-6F90-4170-A494-1AEFC4ACBAED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED312825-6F90-4170-A494-1AEFC4ACBAED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED312825-6F90-4170-A494-1AEFC4ACBAED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -22,4 +30,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {60984BC9-01D1-4B40-9733-E459B1231F96} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {ED312825-6F90-4170-A494-1AEFC4ACBAED} = {E0EE6CC2-8FCD-43B4-BAA1-E8A578F48AB0} + EndGlobalSection EndGlobal diff --git a/appsettings.json b/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -} diff --git a/deployments/docker-compose.yaml b/deployments/docker-compose.yaml new file mode 100644 index 00000000..2232d8f2 --- /dev/null +++ b/deployments/docker-compose.yaml @@ -0,0 +1,33 @@ +version: '3.7' +services: + api: + build: + context: ../src/ + dockerfile: dockerfile + ports: + - 9000:80 + depends_on: + - db + networks: + - mynetwork + + db: + image: mcr.microsoft.com/mssql/server:2019-latest + ports: + - 1433:1433 + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=Pass44w0rd + - MSSQL_TCP_ENABLED=1 + - MSSQL_TCP_PORT=1433 + - MSSQLNP_ENABLED=1 + volumes: + - dbdata:/var/opt/mssql + networks: + - mynetwork + +networks: + mynetwork: + +volumes: + dbdata: diff --git a/src/TodoApi/.vscode/settings.json b/src/TodoApi/.vscode/settings.json new file mode 100644 index 00000000..df5b2381 --- /dev/null +++ b/src/TodoApi/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "TodoApiDTO.sln" +} \ No newline at end of file diff --git a/src/TodoApi/Controllers/TodoItemsController.cs b/src/TodoApi/Controllers/TodoItemsController.cs new file mode 100644 index 00000000..932ddf71 --- /dev/null +++ b/src/TodoApi/Controllers/TodoItemsController.cs @@ -0,0 +1,78 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TodoApi.Data; +using TodoApi.Models; +using TodoApi.Services.Interfaces; + +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TodoItemsController : ControllerBase + { + private ITodoService _todoService; + + public TodoItemsController(ITodoService todoService) + { + _todoService = todoService; + } + + [HttpGet] + public async Task>> GetTodoItems() + { + var todoItems = await _todoService.GetTodoItems(); + return Ok(todoItems.Select(item => item.AsDto())); + } + + [HttpGet("{id}")] + public async Task> GetTodoItem(long id) + { + var todoItem = await _todoService.GetTodoItem(id); + + if (todoItem == null) + { + return NotFound(); + } + + return Ok(todoItem.AsDto()); + } + + [HttpPut("{id}")] + public async Task UpdateTodoItem(long id, TodoItemDTO todoItemDTO) + { + if (id != todoItemDTO.Id) + { + return BadRequest(); + } + + var todoItem = await _todoService.GetTodoItem(id); + if (todoItem == null) + { + return NotFound(); + } + await _todoService.UpdateTodoItem(id, todoItemDTO); + return NoContent(); + } + + [HttpPost] + public async Task> CreateTodoItem(TodoItemDTO todoItemDTO) + { + await _todoService.CreateTodoItem(todoItemDTO); + return Ok(todoItemDTO); + } + + [HttpDelete("{id}")] + public async Task DeleteTodoItem(long id) + { + if (!await _todoService.TodoItemExists(id)) + { + return NotFound(); + } + await _todoService.DeleteTodoItem(id); + return NoContent(); + } + } +} diff --git a/src/TodoApi/Data/AppDbContext.cs b/src/TodoApi/Data/AppDbContext.cs new file mode 100644 index 00000000..af497e53 --- /dev/null +++ b/src/TodoApi/Data/AppDbContext.cs @@ -0,0 +1,28 @@ +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using TodoApi.Models; + +namespace TodoApi.Data{ + public class AppDbContext : DbContext{ + + public AppDbContext(DbContextOptions options) + : base(options) + { + + } + + public virtual DbSet TodoItems { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + } + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + } + + + } +} \ No newline at end of file diff --git a/src/TodoApi/Extensions.cs b/src/TodoApi/Extensions.cs new file mode 100644 index 00000000..d112771a --- /dev/null +++ b/src/TodoApi/Extensions.cs @@ -0,0 +1,27 @@ +using TodoApi.Models; + +namespace TodoApi +{ + public static class Extensions + { + public static TodoItemDTO AsDto(this IEntity todoItem) + { + return new TodoItemDTO + { + Id = todoItem.Id, + Name = todoItem.Name, + IsComplete = todoItem.IsComplete + }; + } + + public static TodoItem AsEntity(this TodoItemDTO todoItemDto) + { + return new TodoItem + { + Id = todoItemDto.Id, + Name = todoItemDto.Name, + IsComplete = todoItemDto.IsComplete + }; + } + } +} \ No newline at end of file diff --git a/src/TodoApi/Infrastructure/ErrorHandlingMiddleware.cs b/src/TodoApi/Infrastructure/ErrorHandlingMiddleware.cs new file mode 100644 index 00000000..388af452 --- /dev/null +++ b/src/TodoApi/Infrastructure/ErrorHandlingMiddleware.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +public class ErrorHandlingMiddleware +{ + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ErrorHandlingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _logger.LogError(ex, "Have an error"); + context.Response.StatusCode = StatusCodes.Status500InternalServerError; + await context.Response.WriteAsync("Have an error"); + } + } +} \ No newline at end of file diff --git a/src/TodoApi/Migrations/20230823112633_init.Designer.cs b/src/TodoApi/Migrations/20230823112633_init.Designer.cs new file mode 100644 index 00000000..d92586cd --- /dev/null +++ b/src/TodoApi/Migrations/20230823112633_init.Designer.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.Data; + +namespace TodoApiDTO.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20230823112633_init")] + partial class init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoApi.Models.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TodoApi/Migrations/20230823112633_init.cs b/src/TodoApi/Migrations/20230823112633_init.cs new file mode 100644 index 00000000..7be51e4c --- /dev/null +++ b/src/TodoApi/Migrations/20230823112633_init.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace TodoApiDTO.Migrations +{ + public partial class init : 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: true), + IsComplete = table.Column(nullable: false), + Secret = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoItems"); + } + } +} diff --git a/src/TodoApi/Migrations/AppDbContextModelSnapshot.cs b/src/TodoApi/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 00000000..39eb93cd --- /dev/null +++ b/src/TodoApi/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,44 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.Data; + +namespace TodoApiDTO.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoApi.Models.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/TodoApi/Models/IEntity.cs b/src/TodoApi/Models/IEntity.cs new file mode 100644 index 00000000..e94ec45c --- /dev/null +++ b/src/TodoApi/Models/IEntity.cs @@ -0,0 +1,10 @@ +namespace TodoApi.Models +{ + public interface IEntity + { + public long Id { get; set; } + public string Name { get; set; } + public bool IsComplete { get; set; } + public string Secret { get; set; } + } +} \ No newline at end of file diff --git a/Models/TodoItem.cs b/src/TodoApi/Models/TodoItem.cs similarity index 87% rename from Models/TodoItem.cs rename to src/TodoApi/Models/TodoItem.cs index 1f6e5465..832c8c90 100644 --- a/Models/TodoItem.cs +++ b/src/TodoApi/Models/TodoItem.cs @@ -1,7 +1,7 @@ namespace TodoApi.Models { #region snippet - public class TodoItem + public class TodoItem : IEntity { public long Id { get; set; } public string Name { get; set; } diff --git a/Models/TodoItemDTO.cs b/src/TodoApi/Models/TodoItemDTO.cs similarity index 100% rename from Models/TodoItemDTO.cs rename to src/TodoApi/Models/TodoItemDTO.cs diff --git a/Program.cs b/src/TodoApi/Program.cs similarity index 100% rename from Program.cs rename to src/TodoApi/Program.cs diff --git a/Properties/launchSettings.json b/src/TodoApi/Properties/launchSettings.json similarity index 100% rename from Properties/launchSettings.json rename to src/TodoApi/Properties/launchSettings.json diff --git a/README.md b/src/TodoApi/README.md similarity index 100% rename from README.md rename to src/TodoApi/README.md diff --git a/src/TodoApi/Services/Interfaces/IRepository.cs b/src/TodoApi/Services/Interfaces/IRepository.cs new file mode 100644 index 00000000..ae15c676 --- /dev/null +++ b/src/TodoApi/Services/Interfaces/IRepository.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoApi.Models; + +namespace TodoApi.Services.Interfaces +{ + public interface IRepository where T : class, IEntity + { + Task> GetItems(); + Task GetItem(long id); + Task UpdateItem(T entity); + Task CreateItem(T entity); + Task DeleteTodoItem(long id); + Task IEntityExists(long id); + } +} \ No newline at end of file diff --git a/src/TodoApi/Services/Interfaces/ITodoService.cs b/src/TodoApi/Services/Interfaces/ITodoService.cs new file mode 100644 index 00000000..eca246c6 --- /dev/null +++ b/src/TodoApi/Services/Interfaces/ITodoService.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoApi.Models; + +namespace TodoApi.Services.Interfaces +{ + public interface ITodoService + { + Task> GetTodoItems(); + Task GetTodoItem(long id); + Task UpdateTodoItem(long id, TodoItemDTO Item); + Task CreateTodoItem(TodoItemDTO Item); + Task DeleteTodoItem(long id); + Task TodoItemExists(long id); + + } +} \ No newline at end of file diff --git a/src/TodoApi/Services/ItemsRepository.cs b/src/TodoApi/Services/ItemsRepository.cs new file mode 100644 index 00000000..b8e83e8d --- /dev/null +++ b/src/TodoApi/Services/ItemsRepository.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using TodoApi.Data; +using TodoApi.Models; +using TodoApi.Services.Interfaces; + +namespace TodoApi.Services +{ + public class ItemsRepository : IRepository where T: class,IEntity + { + private readonly AppDbContext _context; + + public ItemsRepository(AppDbContext context) + { + _context = context; + } + + public async Task> GetItems() + => await _context.Set().ToListAsync(); + + public async Task GetItem(long id) + => await _context.Set().AsNoTracking().Where(p => p.Id == id).SingleAsync(); + + public async Task UpdateItem(T entity) + { + if (entity == null) + return false; + + _context.Set().Update(entity); + await _context.SaveChangesAsync(); + return true; + } + + public async Task CreateItem(T entity) + { + if (entity == null) + throw new ArgumentNullException($"{nameof(entity)} cann't be a null"); + + await _context.Set().AddAsync(entity); + await _context.SaveChangesAsync(); + return entity; + } + + public async Task DeleteTodoItem(long id) + { + var entity = await _context.Set().AsNoTracking().Where(p => p.Id == id).SingleAsync();; + if (entity == null) + return false; + + _context.Set().Remove(entity); + await _context.SaveChangesAsync(); + + return true; + } + + public async Task IEntityExists(long id) => + await _context.Set().AnyAsync(e => e.Id == id); + + + } +} \ No newline at end of file diff --git a/src/TodoApi/Services/TodoService.cs b/src/TodoApi/Services/TodoService.cs new file mode 100644 index 00000000..8cf3e42e --- /dev/null +++ b/src/TodoApi/Services/TodoService.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using TodoApi.Models; +using TodoApi.Services.Interfaces; + +namespace TodoApi.Services +{ + public class TodoService : ITodoService + { + private readonly IRepository _repository; + + public TodoService(IRepository repository) + { + _repository = repository; + } + + public Task CreateTodoItem(TodoItemDTO Item) + { + var todoItem = Item.AsEntity(); + return _repository.CreateItem(todoItem); + } + + public async Task DeleteTodoItem(long id) + => await _repository.DeleteTodoItem(id); + + public async Task GetTodoItem(long id) + => await _repository.GetItem(id); + + public async Task> GetTodoItems() + => await _repository.GetItems(); + + public async Task UpdateTodoItem(long id, TodoItemDTO Item) + { + var todoItem = await GetTodoItem(id); + todoItem.Name = Item.Name; + todoItem.IsComplete = Item.IsComplete; + return await _repository.UpdateItem(todoItem); + } + public Task TodoItemExists(long id) + => _repository.IEntityExists(id); + } + + +} diff --git a/src/TodoApi/Startup.cs b/src/TodoApi/Startup.cs new file mode 100644 index 00000000..a3223c43 --- /dev/null +++ b/src/TodoApi/Startup.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Interfaces; +using Microsoft.OpenApi.Models; +using Serilog; +using TodoApi.Data; +using TodoApi.Models; +using TodoApi.Services; +using TodoApi.Services.Interfaces; + +namespace TodoApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext((cfg) => + { + var connection = Configuration.GetConnectionString("ConnectionToDb"); + cfg.UseSqlServer(connection); + }); + services.AddControllers(); + Log.Logger = new LoggerConfiguration() + .WriteTo.File("log.txt") + .CreateLogger(); + + services.AddLogging(loggingBuilder => + { + loggingBuilder.AddSerilog(); + }); + services.AddScoped(typeof(IRepository<>), typeof(ItemsRepository<>)); + services.AddScoped(); + services.AddSwaggerGen(options => + { + options.SwaggerDoc("v1", + new OpenApiInfo + { + Title = "Swagger TodoApi Documentation", + Version = "v1", + Description = "API Documentation for the TodoApi application", + Contact = new OpenApiContact + { + Name = "Vasilii Mukhin", + Email = "vasjenm@gmail.com" + }, + Extensions = new Dictionary + { + {"x-logo", new OpenApiObject + { + {"url", new OpenApiString("~/logo.png")}, + { "altText", new OpenApiString("TodoApi logo")} + } + } + } + + }); + options.EnableAnnotations(); + }); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(options => + options.SwaggerEndpoint("/swagger/v1/swagger.json", + "Swagger TodoApi Documentation v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + using (var scope = app.ApplicationServices.CreateScope()) + { + var services = scope.ServiceProvider; + + var context = services.GetRequiredService(); + if (context.Database.GetPendingMigrations().Any()) + { + System.Console.WriteLine("Migrating database"); + context.Database.Migrate(); + } + }; + } + } +} diff --git a/src/TodoApi/TodoApiDTO.csproj b/src/TodoApi/TodoApiDTO.csproj new file mode 100644 index 00000000..95aa21d4 --- /dev/null +++ b/src/TodoApi/TodoApiDTO.csproj @@ -0,0 +1,22 @@ + + + netcoreapp3.1 + 29b28247-134c-4f54-ac13-07eb6fe5bd5a + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + \ No newline at end of file diff --git a/appsettings.Development.json b/src/TodoApi/appsettings.Development.json similarity index 100% rename from appsettings.Development.json rename to src/TodoApi/appsettings.Development.json diff --git a/src/TodoApi/appsettings.json b/src/TodoApi/appsettings.json new file mode 100644 index 00000000..68604cc0 --- /dev/null +++ b/src/TodoApi/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "ConnectionToDb": "Server=db;Database=TodoApi;User=sa;Password=Pass44w0rd;MultipleActiveResultSets=true; MultiSubnetFailover=True; TrustServerCertificate=True; Encrypt=false" + }, + "AllowedHosts": "*" +} diff --git a/src/dockerfile b/src/dockerfile new file mode 100644 index 00000000..9cf4cb39 --- /dev/null +++ b/src/dockerfile @@ -0,0 +1,17 @@ + +FROM mcr.microsoft.com/dotnet/sdk:3.1 AS build +WORKDIR /app + +# copy csproj and restore as distinct layers +COPY ./TodoApi/*.csproj ./ +RUN dotnet restore + +# copy everything else and build app +COPY ./TodoApi ./ +RUN dotnet publish -c release -o /app + +# final stage/image +FROM mcr.microsoft.com/dotnet/aspnet:3.1 +WORKDIR /app +COPY --from=build /app ./ +ENTRYPOINT ["dotnet", "TodoApiDTO.dll"] \ No newline at end of file