From 28eaaf8ffce94f10b6b8209fe5d1a2b32199b61e Mon Sep 17 00:00:00 2001 From: Guilherme Reis Date: Fri, 18 Nov 2022 13:03:43 -0300 Subject: [PATCH 1/4] First commit --- .gitignore | 3 + LibraryAPI/API/API.csproj | 26 +++++ LibraryAPI/API/Controllers/BooksController.cs | 81 ++++++++++++++ LibraryAPI/API/Controllers/UsersController.cs | 52 +++++++++ LibraryAPI/API/Domain/Entities/Book.cs | 10 ++ LibraryAPI/API/Domain/Entities/User.cs | 10 ++ .../API/Domain/Entities/UserRoleEnum.cs | 13 +++ .../Domain/Repositories/IBooksRepository.cs | 15 +++ .../API/Domain/Repositories/IUnitOfWork.cs | 7 ++ .../Domain/Repositories/IUsersRepository.cs | 12 +++ .../Services/Communication/BaseResponse.cs | 14 +++ .../Services/Communication/BookResponse.cs | 18 ++++ .../Services/Communication/LoginResponse.cs | 19 ++++ .../Services/Communication/UserResponse.cs | 18 ++++ .../API/Domain/Services/IBooksService.cs | 14 +++ .../API/Domain/Services/IUsersService.cs | 13 +++ .../API/Extensions/ModelStateExtensions.cs | 14 +++ .../API/Extensions/ServiceExtensions.cs | 23 ++++ .../API/Mapping/EntityToModelProfile.cs | 15 +++ .../API/Mapping/ModelToEntityProfile.cs | 15 +++ .../20221115152725_Books.Designer.cs | 65 ++++++++++++ .../API/Migrations/20221115152725_Books.cs | 41 +++++++ .../20221115160605_Users.Designer.cs | 90 ++++++++++++++++ .../API/Migrations/20221115160605_Users.cs | 36 +++++++ .../Migrations/AppDbContextModelSnapshot.cs | 87 +++++++++++++++ LibraryAPI/API/Models/BookModel.cs | 10 ++ LibraryAPI/API/Models/JwtModel.cs | 8 ++ LibraryAPI/API/Models/ListBooksModel.cs | 8 ++ LibraryAPI/API/Models/LoginUserModel.cs | 15 +++ LibraryAPI/API/Models/SaveBookModel.cs | 9 ++ LibraryAPI/API/Models/SaveUserModel.cs | 24 +++++ LibraryAPI/API/Models/UserModel.cs | 7 ++ .../API/Persistence/Contexts/AppDbContext.cs | 42 ++++++++ .../Repositories/BaseRepository.cs | 14 +++ .../Repositories/BooksRepository.cs | 52 +++++++++ .../Persistence/Repositories/UnitOfWork.cs | 20 ++++ .../Repositories/UsersRepository.cs | 38 +++++++ LibraryAPI/API/Program.cs | 91 ++++++++++++++++ LibraryAPI/API/Properties/launchSettings.json | 31 ++++++ LibraryAPI/API/README.md | 97 +++++++++++++++++ LibraryAPI/API/Services/BooksService.cs | 100 ++++++++++++++++++ LibraryAPI/API/Services/UsersService.cs | 87 +++++++++++++++ LibraryAPI/API/appsettings.Development.json | 8 ++ LibraryAPI/API/appsettings.json | 12 +++ LibraryAPI/LibraryAPI.sln | 31 ++++++ .../Tests/LibraryAPITests/BooksServiceTest.cs | 95 +++++++++++++++++ .../LibraryAPITests/LibraryAPITests.csproj | 29 +++++ .../Tests/LibraryAPITests/UsersServiceTest.cs | 96 +++++++++++++++++ LibraryAPI/Tests/LibraryAPITests/Usings.cs | 1 + 49 files changed, 1636 insertions(+) create mode 100644 .gitignore create mode 100644 LibraryAPI/API/API.csproj create mode 100644 LibraryAPI/API/Controllers/BooksController.cs create mode 100644 LibraryAPI/API/Controllers/UsersController.cs create mode 100644 LibraryAPI/API/Domain/Entities/Book.cs create mode 100644 LibraryAPI/API/Domain/Entities/User.cs create mode 100644 LibraryAPI/API/Domain/Entities/UserRoleEnum.cs create mode 100644 LibraryAPI/API/Domain/Repositories/IBooksRepository.cs create mode 100644 LibraryAPI/API/Domain/Repositories/IUnitOfWork.cs create mode 100644 LibraryAPI/API/Domain/Repositories/IUsersRepository.cs create mode 100644 LibraryAPI/API/Domain/Services/Communication/BaseResponse.cs create mode 100644 LibraryAPI/API/Domain/Services/Communication/BookResponse.cs create mode 100644 LibraryAPI/API/Domain/Services/Communication/LoginResponse.cs create mode 100644 LibraryAPI/API/Domain/Services/Communication/UserResponse.cs create mode 100644 LibraryAPI/API/Domain/Services/IBooksService.cs create mode 100644 LibraryAPI/API/Domain/Services/IUsersService.cs create mode 100644 LibraryAPI/API/Extensions/ModelStateExtensions.cs create mode 100644 LibraryAPI/API/Extensions/ServiceExtensions.cs create mode 100644 LibraryAPI/API/Mapping/EntityToModelProfile.cs create mode 100644 LibraryAPI/API/Mapping/ModelToEntityProfile.cs create mode 100644 LibraryAPI/API/Migrations/20221115152725_Books.Designer.cs create mode 100644 LibraryAPI/API/Migrations/20221115152725_Books.cs create mode 100644 LibraryAPI/API/Migrations/20221115160605_Users.Designer.cs create mode 100644 LibraryAPI/API/Migrations/20221115160605_Users.cs create mode 100644 LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs create mode 100644 LibraryAPI/API/Models/BookModel.cs create mode 100644 LibraryAPI/API/Models/JwtModel.cs create mode 100644 LibraryAPI/API/Models/ListBooksModel.cs create mode 100644 LibraryAPI/API/Models/LoginUserModel.cs create mode 100644 LibraryAPI/API/Models/SaveBookModel.cs create mode 100644 LibraryAPI/API/Models/SaveUserModel.cs create mode 100644 LibraryAPI/API/Models/UserModel.cs create mode 100644 LibraryAPI/API/Persistence/Contexts/AppDbContext.cs create mode 100644 LibraryAPI/API/Persistence/Repositories/BaseRepository.cs create mode 100644 LibraryAPI/API/Persistence/Repositories/BooksRepository.cs create mode 100644 LibraryAPI/API/Persistence/Repositories/UnitOfWork.cs create mode 100644 LibraryAPI/API/Persistence/Repositories/UsersRepository.cs create mode 100644 LibraryAPI/API/Program.cs create mode 100644 LibraryAPI/API/Properties/launchSettings.json create mode 100644 LibraryAPI/API/README.md create mode 100644 LibraryAPI/API/Services/BooksService.cs create mode 100644 LibraryAPI/API/Services/UsersService.cs create mode 100644 LibraryAPI/API/appsettings.Development.json create mode 100644 LibraryAPI/API/appsettings.json create mode 100644 LibraryAPI/LibraryAPI.sln create mode 100644 LibraryAPI/Tests/LibraryAPITests/BooksServiceTest.cs create mode 100644 LibraryAPI/Tests/LibraryAPITests/LibraryAPITests.csproj create mode 100644 LibraryAPI/Tests/LibraryAPITests/UsersServiceTest.cs create mode 100644 LibraryAPI/Tests/LibraryAPITests/Usings.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5003052 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs +bin +obj \ No newline at end of file diff --git a/LibraryAPI/API/API.csproj b/LibraryAPI/API/API.csproj new file mode 100644 index 0000000..04e4cc8 --- /dev/null +++ b/LibraryAPI/API/API.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/LibraryAPI/API/Controllers/BooksController.cs b/LibraryAPI/API/Controllers/BooksController.cs new file mode 100644 index 0000000..dbc7dfa --- /dev/null +++ b/LibraryAPI/API/Controllers/BooksController.cs @@ -0,0 +1,81 @@ +using AutoMapper; +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Services; +using LibraryApi.Extensions; +using LibraryApi.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace LibraryApi.Controllers +{ + [Route("/api/[controller]")] + public class BooksController : Controller + { + private readonly IBooksService _booksService; + private readonly IMapper _mapper; + + public BooksController(IBooksService booksService, IMapper mapper) + { + _booksService = booksService; + _mapper = mapper; + } + + + [HttpGet] + [Authorize] + public async Task> GetAllAsync([FromQuery] ListBooksModel model) + { + var books = await _booksService.ListAsync(model); + var models = _mapper.Map, IEnumerable>(books); + + return models; + } + + [HttpPost] + [Authorize(Roles = "ADMINISTRADOR")] + public async Task PostAsync([FromBody] SaveBookModel model) + { + if (!ModelState.IsValid) + return BadRequest(ModelState.GetErrorMessages()); + + var book = _mapper.Map(model); + var result = await _booksService.SaveAsync(book); + + if (!result.Success) + return BadRequest(result.Message); + + var BookModel = _mapper.Map(result.Book); + return Ok(BookModel); + } + + [HttpPut("{id}")] + [Authorize(Roles = "ADMINISTRADOR")] + public async Task PutAsync(int id, [FromBody] SaveBookModel model) + { + if (!ModelState.IsValid) + return BadRequest(ModelState.GetErrorMessages()); + + var book = _mapper.Map(model); + var result = await _booksService.UpdateAsync(id, book); + + if (!result.Success) + return BadRequest(result.Message); + + var BookModel = _mapper.Map(result.Book); + return Ok(BookModel); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "ADMINISTRADOR")] + public async Task DeleteAsync(int id) + { + var result = await _booksService.DeleteAsync(id); + + if (!result.Success) + return BadRequest(result.Message); + + var BookModel = _mapper.Map(result.Book); + return Ok(BookModel); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Controllers/UsersController.cs b/LibraryAPI/API/Controllers/UsersController.cs new file mode 100644 index 0000000..068012b --- /dev/null +++ b/LibraryAPI/API/Controllers/UsersController.cs @@ -0,0 +1,52 @@ +using AutoMapper; +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Services; +using LibraryApi.Extensions; +using LibraryApi.Models; +using Microsoft.AspNetCore.Mvc; + +namespace LibraryApi.Controllers +{ + [Route("/api/[controller]")] + public class UsersController : Controller + { + private readonly IUsersService _usersService; + private readonly IMapper _mapper; + + public UsersController(IMapper mapper, IUsersService usersService) + { + _mapper = mapper; + _usersService = usersService; + } + + [HttpPost] + public async Task PostAsync([FromBody] SaveUserModel model) + { + if (!ModelState.IsValid) + return BadRequest(ModelState.GetErrorMessages()); + + var user = _mapper.Map(model); + var result = await _usersService.SaveAsync(user, model.Password); + + if (!result.Success) + return BadRequest(result.Message); + + var userModel = _mapper.Map(result.User); + return Ok(userModel); + } + + [HttpPost("/api/[controller]/login")] + public async Task LoginAsync([FromBody] LoginUserModel model) + { + if (!ModelState.IsValid) + return BadRequest(ModelState.GetErrorMessages()); + + var result = await _usersService.LoginAsync(model); + + if (!result.Success) + return BadRequest(result.Message); + + return Ok(result.Token); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Entities/Book.cs b/LibraryAPI/API/Domain/Entities/Book.cs new file mode 100644 index 0000000..6b5f98a --- /dev/null +++ b/LibraryAPI/API/Domain/Entities/Book.cs @@ -0,0 +1,10 @@ +namespace LibraryApi.Domain.Entities +{ + public class Book + { + public int Id { get; set; } + public string Name { get; set; } + public string ImageUrl { get; set; } + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Entities/User.cs b/LibraryAPI/API/Domain/Entities/User.cs new file mode 100644 index 0000000..b5a702d --- /dev/null +++ b/LibraryAPI/API/Domain/Entities/User.cs @@ -0,0 +1,10 @@ +namespace LibraryApi.Domain.Entities +{ + public class User + { + public int Id { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public UserRoleEnum Role { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Entities/UserRoleEnum.cs b/LibraryAPI/API/Domain/Entities/UserRoleEnum.cs new file mode 100644 index 0000000..ded7e74 --- /dev/null +++ b/LibraryAPI/API/Domain/Entities/UserRoleEnum.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace LibraryApi.Domain.Entities +{ + public enum UserRoleEnum + { + [Description("Básico")] + BASICO = 1, + + [Description("Administrador")] + ADMINISTRADOR = 2 + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Repositories/IBooksRepository.cs b/LibraryAPI/API/Domain/Repositories/IBooksRepository.cs new file mode 100644 index 0000000..48c978c --- /dev/null +++ b/LibraryAPI/API/Domain/Repositories/IBooksRepository.cs @@ -0,0 +1,15 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Models; + +namespace LibraryApi.Domain.Repositories +{ + public interface IBooksRepository + { + Task> ListAsync(ListBooksModel model); + Task AddAsync(Book book); + Task FindByIdAsync(int id); + Task FindByNameAsync(string name); + void Update(Book book); + void Remove(Book book); + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Repositories/IUnitOfWork.cs b/LibraryAPI/API/Domain/Repositories/IUnitOfWork.cs new file mode 100644 index 0000000..ac5dc74 --- /dev/null +++ b/LibraryAPI/API/Domain/Repositories/IUnitOfWork.cs @@ -0,0 +1,7 @@ +namespace LibraryApi.Domain.Repositories +{ + public interface IUnitOfWork + { + Task CompleteAsync(); + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Repositories/IUsersRepository.cs b/LibraryAPI/API/Domain/Repositories/IUsersRepository.cs new file mode 100644 index 0000000..5dc469d --- /dev/null +++ b/LibraryAPI/API/Domain/Repositories/IUsersRepository.cs @@ -0,0 +1,12 @@ +using LibraryApi.Domain.Entities; +using Microsoft.AspNetCore.Identity; + +namespace LibraryApi.Domain.Repositories +{ + public interface IUsersRepository + { + Task AddAsync(User user); + Task GetUserByEmailAsync(string email); + Task GetUserByEmailAndPasswordAsync(string email, string password); + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/Communication/BaseResponse.cs b/LibraryAPI/API/Domain/Services/Communication/BaseResponse.cs new file mode 100644 index 0000000..2a10fc5 --- /dev/null +++ b/LibraryAPI/API/Domain/Services/Communication/BaseResponse.cs @@ -0,0 +1,14 @@ +namespace LibraryApi.Domain.Services.Communication +{ + public abstract class BaseResponse + { + public bool Success { get; protected set; } + public string Message { get; protected set; } + + public BaseResponse(bool success, string message) + { + Success = success; + Message = message; + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/Communication/BookResponse.cs b/LibraryAPI/API/Domain/Services/Communication/BookResponse.cs new file mode 100644 index 0000000..2288bde --- /dev/null +++ b/LibraryAPI/API/Domain/Services/Communication/BookResponse.cs @@ -0,0 +1,18 @@ +using LibraryApi.Domain.Entities; + +namespace LibraryApi.Domain.Services.Communication +{ + public class BookResponse : BaseResponse + { + public Book Book { get; private set; } + + private BookResponse(bool success, string message, Book book) : base(success, message) + { + Book = book; + } + + public BookResponse(Book book) : this(true, string.Empty, book) { } + + public BookResponse(string message) : this(false, message, null) { } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/Communication/LoginResponse.cs b/LibraryAPI/API/Domain/Services/Communication/LoginResponse.cs new file mode 100644 index 0000000..9ea64c6 --- /dev/null +++ b/LibraryAPI/API/Domain/Services/Communication/LoginResponse.cs @@ -0,0 +1,19 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Models; + +namespace LibraryApi.Domain.Services.Communication +{ + public class LoginResponse : BaseResponse + { + public JwtModel Token { get; private set; } + + private LoginResponse(bool success, string message, JwtModel token) : base(success, message) + { + Token = token; + } + + public LoginResponse(JwtModel token) : this(true, string.Empty, token) { } + + public LoginResponse(string message) : this(false, message, null) { } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/Communication/UserResponse.cs b/LibraryAPI/API/Domain/Services/Communication/UserResponse.cs new file mode 100644 index 0000000..390c278 --- /dev/null +++ b/LibraryAPI/API/Domain/Services/Communication/UserResponse.cs @@ -0,0 +1,18 @@ +using LibraryApi.Domain.Entities; + +namespace LibraryApi.Domain.Services.Communication +{ + public class UserResponse : BaseResponse + { + public User User { get; private set; } + + private UserResponse(bool success, string message, User user) : base(success, message) + { + User = user; + } + + public UserResponse(User user) : this(true, string.Empty, user) { } + + public UserResponse(string message) : this(false, message, null) { } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/IBooksService.cs b/LibraryAPI/API/Domain/Services/IBooksService.cs new file mode 100644 index 0000000..e5871dc --- /dev/null +++ b/LibraryAPI/API/Domain/Services/IBooksService.cs @@ -0,0 +1,14 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Services.Communication; +using LibraryApi.Models; + +namespace LibraryApi.Domain.Services +{ + public interface IBooksService + { + Task> ListAsync(ListBooksModel model); + Task SaveAsync(Book Book); + Task UpdateAsync(int id, Book Book); + Task DeleteAsync(int id); + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Domain/Services/IUsersService.cs b/LibraryAPI/API/Domain/Services/IUsersService.cs new file mode 100644 index 0000000..f00247a --- /dev/null +++ b/LibraryAPI/API/Domain/Services/IUsersService.cs @@ -0,0 +1,13 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Services.Communication; +using LibraryApi.Models; + +namespace LibraryApi.Domain.Services +{ + public interface IUsersService + { + Task SaveAsync(User user, string password); + Task LoginAsync(LoginUserModel user); + Task GenerateToken(User user); + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Extensions/ModelStateExtensions.cs b/LibraryAPI/API/Extensions/ModelStateExtensions.cs new file mode 100644 index 0000000..d7a678b --- /dev/null +++ b/LibraryAPI/API/Extensions/ModelStateExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace LibraryApi.Extensions +{ + public static class ModelStateExtensions + { + public static List GetErrorMessages(this ModelStateDictionary dictionary) + { + return dictionary.SelectMany(m => m.Value.Errors) + .Select(m => m.ErrorMessage) + .ToList(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Extensions/ServiceExtensions.cs b/LibraryAPI/API/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..4b19ebb --- /dev/null +++ b/LibraryAPI/API/Extensions/ServiceExtensions.cs @@ -0,0 +1,23 @@ +using LibraryApi.Domain.Repositories; +using LibraryApi.Domain.Services; +using LibraryApi.Persistence.Repositories; +using LibraryApi.Services; + +namespace LibraryApi.Extensions +{ + public static class ServiceExtensions + { + public static void RegisterRepositories(this IServiceCollection collection) + { + collection.AddScoped(); + collection.AddScoped(); + } + + public static void RegisterServices(this IServiceCollection collection) + { + collection.AddScoped(); + collection.AddScoped(); + collection.AddScoped(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Mapping/EntityToModelProfile.cs b/LibraryAPI/API/Mapping/EntityToModelProfile.cs new file mode 100644 index 0000000..deb3bfd --- /dev/null +++ b/LibraryAPI/API/Mapping/EntityToModelProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using LibraryApi.Domain.Entities; +using LibraryApi.Models; + +namespace LibraryApi.Mapping +{ + public class EntityToModelProfile : Profile + { + public EntityToModelProfile() + { + CreateMap(); + CreateMap(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Mapping/ModelToEntityProfile.cs b/LibraryAPI/API/Mapping/ModelToEntityProfile.cs new file mode 100644 index 0000000..bec965b --- /dev/null +++ b/LibraryAPI/API/Mapping/ModelToEntityProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using LibraryApi.Domain.Entities; +using LibraryApi.Models; + +namespace LibraryApi.Mapping +{ + public class ModelToEntityProfile : Profile + { + public ModelToEntityProfile() + { + CreateMap(); + CreateMap(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Migrations/20221115152725_Books.Designer.cs b/LibraryAPI/API/Migrations/20221115152725_Books.Designer.cs new file mode 100644 index 0000000..352d88a --- /dev/null +++ b/LibraryAPI/API/Migrations/20221115152725_Books.Designer.cs @@ -0,0 +1,65 @@ +// +using LibraryApi.Persistence.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LibraryApi.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20221115152725_Books")] + partial class Books + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("LibraryApi.Domain.Entities.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + + b.HasData( + new + { + Id = 1, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 01" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryAPI/API/Migrations/20221115152725_Books.cs b/LibraryAPI/API/Migrations/20221115152725_Books.cs new file mode 100644 index 0000000..2806c5a --- /dev/null +++ b/LibraryAPI/API/Migrations/20221115152725_Books.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LibraryApi.Migrations +{ + /// + public partial class Books : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Books", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + ImageUrl = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + Author = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Books", x => x.Id); + }); + + migrationBuilder.InsertData( + table: "Books", + columns: new[] { "Id", "Author", "ImageUrl", "Name" }, + values: new object[] { 1, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 01" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Books"); + } + } +} diff --git a/LibraryAPI/API/Migrations/20221115160605_Users.Designer.cs b/LibraryAPI/API/Migrations/20221115160605_Users.Designer.cs new file mode 100644 index 0000000..699ebae --- /dev/null +++ b/LibraryAPI/API/Migrations/20221115160605_Users.Designer.cs @@ -0,0 +1,90 @@ +// +using LibraryApi.Persistence.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LibraryApi.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20221115160605_Users")] + partial class Users + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("LibraryApi.Domain.Entities.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + + b.HasData( + new + { + Id = 1, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 01" + }); + }); + + modelBuilder.Entity("LibraryApi.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryAPI/API/Migrations/20221115160605_Users.cs b/LibraryAPI/API/Migrations/20221115160605_Users.cs new file mode 100644 index 0000000..2150083 --- /dev/null +++ b/LibraryAPI/API/Migrations/20221115160605_Users.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LibraryApi.Migrations +{ + /// + public partial class Users : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Email = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Password = table.Column(type: "nvarchar(max)", nullable: false), + Role = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs b/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..dab8a83 --- /dev/null +++ b/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,87 @@ +// +using LibraryApi.Persistence.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LibraryApi.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("LibraryApi.Domain.Entities.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + + b.HasData( + new + { + Id = 1, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 01" + }); + }); + + modelBuilder.Entity("LibraryApi.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryAPI/API/Models/BookModel.cs b/LibraryAPI/API/Models/BookModel.cs new file mode 100644 index 0000000..c8c7351 --- /dev/null +++ b/LibraryAPI/API/Models/BookModel.cs @@ -0,0 +1,10 @@ +namespace LibraryApi.Models +{ + public class BookModel + { + public int Id { get; set; } + public string Name { get; set; } + public string ImageUrl { get; set; } + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/JwtModel.cs b/LibraryAPI/API/Models/JwtModel.cs new file mode 100644 index 0000000..db3a10f --- /dev/null +++ b/LibraryAPI/API/Models/JwtModel.cs @@ -0,0 +1,8 @@ +namespace LibraryApi.Models +{ + public class JwtModel + { + public string Email { get; set; } + public string Token { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/ListBooksModel.cs b/LibraryAPI/API/Models/ListBooksModel.cs new file mode 100644 index 0000000..1ea5c0c --- /dev/null +++ b/LibraryAPI/API/Models/ListBooksModel.cs @@ -0,0 +1,8 @@ +namespace LibraryApi.Models +{ + public class ListBooksModel + { + public int Page { get; set; } + public int NumberOfItens { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/LoginUserModel.cs b/LibraryAPI/API/Models/LoginUserModel.cs new file mode 100644 index 0000000..ebf903f --- /dev/null +++ b/LibraryAPI/API/Models/LoginUserModel.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace LibraryApi.Models +{ + public class LoginUserModel + { + [Required] + [EmailAddress(ErrorMessage = "Email is required")] + public string Email { get; set; } + + [Required(ErrorMessage = "Password is required")] + [DataType(DataType.Password)] + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/SaveBookModel.cs b/LibraryAPI/API/Models/SaveBookModel.cs new file mode 100644 index 0000000..0ab508c --- /dev/null +++ b/LibraryAPI/API/Models/SaveBookModel.cs @@ -0,0 +1,9 @@ +namespace LibraryApi.Models +{ + public class SaveBookModel + { + public string Name { get; set; } + public string ImageUrl { get; set; } + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/SaveUserModel.cs b/LibraryAPI/API/Models/SaveUserModel.cs new file mode 100644 index 0000000..c726ed0 --- /dev/null +++ b/LibraryAPI/API/Models/SaveUserModel.cs @@ -0,0 +1,24 @@ +using LibraryApi.Domain.Entities; +using System.ComponentModel.DataAnnotations; + +namespace LibraryApi.Models +{ + public class SaveUserModel + { + [Required] + [EmailAddress(ErrorMessage = "Email is required")] + public string Email { get; set; } + + [Required(ErrorMessage = "Role is required")] + public UserRoleEnum Role { get; set; } + + [Required(ErrorMessage = "Password is required")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Required(ErrorMessage = "Confirm Password is required")] + [Compare("Password", ErrorMessage = "The passwords doesnt match")] + public string ConfirmPassword { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Models/UserModel.cs b/LibraryAPI/API/Models/UserModel.cs new file mode 100644 index 0000000..a9efcac --- /dev/null +++ b/LibraryAPI/API/Models/UserModel.cs @@ -0,0 +1,7 @@ +namespace LibraryApi.Models +{ + public class UserModel + { + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs b/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs new file mode 100644 index 0000000..f4fdfc7 --- /dev/null +++ b/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs @@ -0,0 +1,42 @@ +using LibraryApi.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace LibraryApi.Persistence.Contexts +{ + public class AppDbContext : DbContext + { + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Books { get; set; } + public DbSet Users { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity().ToTable("Books"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd(); + builder.Entity().Property(p => p.Name).IsRequired().HasMaxLength(200); + builder.Entity().Property(p => p.ImageUrl).HasMaxLength(500); + + builder.Entity().HasData + ( + new Book + { + Id = 1, + Name = "Hunter x Hunter 01", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + } + ); + + builder.Entity().ToTable("Users"); + builder.Entity().HasKey(p => p.Id); + builder.Entity().Property(p => p.Id).IsRequired().ValueGeneratedOnAdd(); + builder.Entity().Property(p => p.Email).IsRequired().HasMaxLength(100); + builder.Entity().Property(p => p.Password).IsRequired(); + builder.Entity().Property(p => p.Role).IsRequired(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Persistence/Repositories/BaseRepository.cs b/LibraryAPI/API/Persistence/Repositories/BaseRepository.cs new file mode 100644 index 0000000..1de5873 --- /dev/null +++ b/LibraryAPI/API/Persistence/Repositories/BaseRepository.cs @@ -0,0 +1,14 @@ +using LibraryApi.Persistence.Contexts; + +namespace LibraryApi.Persistence.Repositories +{ + public abstract class BaseRepository + { + protected readonly AppDbContext _context; + + public BaseRepository(AppDbContext context) + { + _context = context; + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Persistence/Repositories/BooksRepository.cs b/LibraryAPI/API/Persistence/Repositories/BooksRepository.cs new file mode 100644 index 0000000..73f62e8 --- /dev/null +++ b/LibraryAPI/API/Persistence/Repositories/BooksRepository.cs @@ -0,0 +1,52 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Persistence.Contexts; +using LibraryApi.Models; +using Microsoft.EntityFrameworkCore; + +namespace LibraryApi.Persistence.Repositories +{ + public class BooksRepository : BaseRepository, IBooksRepository + { + public BooksRepository(AppDbContext context) : base(context) + { + } + + public async Task> ListAsync(ListBooksModel model) + { + var lastId = (model.Page - 1) * model.NumberOfItens; + var numberOfItens = model.NumberOfItens != 0 ? model.NumberOfItens : int.MaxValue; + + return await _context.Books + .OrderBy(d => d.Name) + .Where(d => d.Id > lastId) + .Take(numberOfItens) + .ToListAsync(); + } + + public async Task AddAsync(Book Book) + { + await _context.Books.AddAsync(Book); + } + + public async Task FindByIdAsync(int id) + { + return await _context.Books.FindAsync(id); + } + + public void Update(Book Book) + { + _context.Books.Update(Book); + } + + public void Remove(Book category) + { + _context.Books.Remove(category); + } + + public async Task FindByNameAsync(string name) + { + return await _context.Books.FirstOrDefaultAsync(b => b.Name == name); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Persistence/Repositories/UnitOfWork.cs b/LibraryAPI/API/Persistence/Repositories/UnitOfWork.cs new file mode 100644 index 0000000..83a47c9 --- /dev/null +++ b/LibraryAPI/API/Persistence/Repositories/UnitOfWork.cs @@ -0,0 +1,20 @@ +using LibraryApi.Domain.Repositories; +using LibraryApi.Persistence.Contexts; + +namespace LibraryApi.Persistence.Repositories +{ + public class UnitOfWork : IUnitOfWork + { + private readonly AppDbContext _context; + + public UnitOfWork(AppDbContext context) + { + _context = context; + } + + public async Task CompleteAsync() + { + await _context.SaveChangesAsync(); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Persistence/Repositories/UsersRepository.cs b/LibraryAPI/API/Persistence/Repositories/UsersRepository.cs new file mode 100644 index 0000000..cfa93c2 --- /dev/null +++ b/LibraryAPI/API/Persistence/Repositories/UsersRepository.cs @@ -0,0 +1,38 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Persistence.Contexts; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace LibraryApi.Persistence.Repositories +{ + public class UsersRepository : BaseRepository, IUsersRepository + { + public UsersRepository(AppDbContext context) : base(context) + { + } + + public async Task AddAsync(User user) + { + await _context.Users.AddAsync(user); + } + + public async Task GetUserByEmailAndPasswordAsync(string email, string password) + { + var user = await _context.Users + .Where(x => x.Email.ToLower() == email.ToLower() && x.Password == password) + .FirstOrDefaultAsync(); + + return user; + } + + public async Task GetUserByEmailAsync(string email) + { + var result = await _context.Users + .Where(u => u.Email == email) + .FirstOrDefaultAsync(); + + return result; + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Program.cs b/LibraryAPI/API/Program.cs new file mode 100644 index 0000000..be508bf --- /dev/null +++ b/LibraryAPI/API/Program.cs @@ -0,0 +1,91 @@ +using LibraryApi.Extensions; +using LibraryApi.Persistence.Contexts; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; + +var connectionString = $"Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=LibraryDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"; + +var builder = WebApplication.CreateBuilder(args); + +builder.Configuration.AddJsonFile("appsettings.json", false, true); + +builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.RegisterRepositories(); +builder.Services.RegisterServices(); +builder.Services.AddAutoMapper(typeof(Program)); +builder.Services.AddLogging(config => +{ + config.AddDebug(); + config.AddConsole(); +}); + +builder.Services.AddSwaggerGen(option => +{ + option.SwaggerDoc("v1", new OpenApiInfo { Title = "Library API", Version = "v1" }); + option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter a valid token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + option.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type=ReferenceType.SecurityScheme, + Id="Bearer" + } + }, + new string[]{} + } + }); +}); + +var key = Encoding.ASCII.GetBytes(builder.Configuration["Authentication:SecretKey"]); + +builder.Services.AddAuthentication(x => +{ + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(x => +{ + x.RequireHttpsMetadata = false; + x.SaveToken = true; + x.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false + }; +}); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/LibraryAPI/API/Properties/launchSettings.json b/LibraryAPI/API/Properties/launchSettings.json new file mode 100644 index 0000000..3a15b2a --- /dev/null +++ b/LibraryAPI/API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:30621", + "sslPort": 44315 + } + }, + "profiles": { + "backend_test": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7044;http://localhost:5083", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/LibraryAPI/API/README.md b/LibraryAPI/API/README.md new file mode 100644 index 0000000..ae61b8e --- /dev/null +++ b/LibraryAPI/API/README.md @@ -0,0 +1,97 @@ +## Desafio Back-End + +#### Requisitos Gerais: + +Uma livraria da cidade teve um aumento no número de seus exemplares e está com um problema para identificar todos os livros que possui em estoque. +Para ajudar a livraria foi solicitado a você desenvolver uma aplicação web para gerenciar estes exemplares. Requisitos: + + +* O sistema deverá mostrar todos os livros cadastrados ordenados de forma ascendente pelo nome. +* Ao persistir, validar se o livro já foi cadastrado. +* O sistema deverá permitir consultar, criar, editar e excluir um livro. +* Os livros devem ser persistidos em um banco de dados. +* Criar algum mecanismo de log de registro e de erro. + +#### Requisitos Técnicos: + +* Configurar o Swagger na aplicação (fundamental), pois será usado para testes. +* Incluir mecanismo de autenticação no Swagger, usando Token JWT (Bearer). +* Para a persistência dos dados deve ser utilizado o Entity Framework. +* Como banco de dados, pode ser usado MySQL, PostgreSQL ou SQL Server. +* Utilizar migrations ou Gerar Scripts e disponibilizá-los um uma pasta. +* Incluir git.ignore no repositório para não subir arquivos de compilação. + + +#### Observações: +* O sistema deverá ser desenvolvido na plataforma .NET com C#. + (preferêncialmente 5.0+, caso for usado outra versão, informar no pull-request) +* Deve conter autenticação com dois níveis de acesso, um administrador e um público, o usuário de nível + público não terá autenticação, ou seja, terá acesso livre a consulta de livros +* Atenção aos princípio do SOLID. +* Não é necessária a criação de front-end, o teste será feito pelo Swagger UI. + +#### Diferenciais do desafio: +* Aplicação das boas práticas do DDD, TDD, Design Patterns, SOLID e Clean Code. +* A modelagem dos dados não será fornecida, de propósito. Desejamos avaliar a sua capacidade de abstração. +* A API deverá realizar tratamento de entrada de dados e retornar códigos de erro quando aplicáveis. +* Criar massa de dados para que seja possível verificar o funcionamento das lógicas propostas. +* Incluir parâmetros de paginação e campos de filtro nos métodos de consulta (GET). +* Documentar, via código-fonte, os campos, parâmetros e dados de retorno da API para exibição no Swagger. + + +## Como deverá ser entregue: + + 1. Faça um fork deste repositório; + 2. Realize o teste; + 3. Adicione seu currículo na raiz do repositório; + 4. Envie-nos o PULL-REQUEST para que seja avaliado. + + + +## Back-End Challenge (English) + +#### General requirements: + +A bookstore in town has had an increase in the number of its copies and is having a problem identifying all the books it has in stock. +To help the bookstore, you were asked to develop a web application to manage these copies. Requirements: + + +* The system should show all registered books sorted in ascending order by name. +* When persisting, validate if the book has already been registered. +* The system should allow consulting, creating, editing and deleting books. +* Books must be persisted in a database. +* Create some logging and error logging mechanism. + +#### Technical requirements: + +* Configure Swagger in the application (fundamental), as it will be used for testing. +* Include authentication mechanism in Swagger, using JWT Token (Bearer). +* For data persistence, Entity Framework must be used. +* As a database, MySQL, PostgreSQL or SQL Server can be used. +* Use migrations or Generate Scripts and make them available in a folder. +* Include git.ignore in the repository to avoid uploading deployment files. + + +#### Comments: +* The system must be developed on the .NET platform with C#. +(preferably 5.0+, if another version is used, inform the pull-request) +* Must contain authentication with two levels of access, an administrator and a public, user level +public will not have authentication, that is, it will have free access to consult books +* Attention to the principles of SOLID. +* No front-end creation required, testing will be done by Swagger UI. + +#### Challenge differentials: +* Application of DDD, TDD, Design Patterns, SOLID and Clean Code best practices. +* Data modeling will not be provided on purpose. We wish to assess your capacity for abstraction. +* The API must perform data entry handling and return error codes when applicable. +* Create mass of data so that it is possible to verify the functioning of the proposed logics. +* Include pagination parameters and filter fields in query (GET) methods. +* Document, via source code, the API fields, parameters and return data for display in Swagger. + + +## How it should be delivered: + + 1. Fork this repository; + 2. Carry out the test; + 3. Add your CV to the repository root; + 4. Send us the PULL-REQUEST to be evaluated. \ No newline at end of file diff --git a/LibraryAPI/API/Services/BooksService.cs b/LibraryAPI/API/Services/BooksService.cs new file mode 100644 index 0000000..1180eb2 --- /dev/null +++ b/LibraryAPI/API/Services/BooksService.cs @@ -0,0 +1,100 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Domain.Services; +using LibraryApi.Domain.Services.Communication; +using LibraryApi.Models; +using Microsoft.Extensions.Logging; + +namespace LibraryApi.Services +{ + public class BooksService : IBooksService + { + private readonly IBooksRepository _booksRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly ILogger _logger; + + public BooksService(IBooksRepository booksRepository, IUnitOfWork unitOfWork, ILogger logger) + { + _booksRepository = booksRepository; + _unitOfWork = unitOfWork; + _logger = logger; + } + + public async Task> ListAsync(ListBooksModel model) + { + return await _booksRepository.ListAsync(model); + } + + public async Task UpdateAsync(int id, Book book) + { + var existingBook = await _booksRepository.FindByIdAsync(id); + if (existingBook == null) + return new BookResponse("Book not found."); + + var bookWithSameName = await _booksRepository.FindByNameAsync(book.Name); + if (bookWithSameName != null) + return new BookResponse($"A book with the same name already exists."); + + existingBook.Name = book.Name; + existingBook.Author = book.Author; + existingBook.ImageUrl = book.ImageUrl; + + try + { + _booksRepository.Update(existingBook); + await _unitOfWork.CompleteAsync(); + + _logger.Log(LogLevel.Information, $"Book with id {existingBook.Id} updated with Success"); + return new BookResponse(existingBook); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, $"An error occurred when trying to update the Book with id {id}: {ex.Message}"); + return new BookResponse($"An error occurred when updating the Book: {ex.Message}"); + } + } + + public async Task SaveAsync(Book book) + { + try + { + var bookWithSameName = await _booksRepository.FindByNameAsync(book.Name); + if (bookWithSameName != null) + return new BookResponse($"A book with the same name already exists."); + + await _booksRepository.AddAsync(book); + await _unitOfWork.CompleteAsync(); + + _logger.Log(LogLevel.Information, $"Book with id {book.Id} saved with Success"); + return new BookResponse(book); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, $"An error occurred when trying to save a Book: {ex.Message}"); + return new BookResponse($"An error occurred when saving the Book: {ex.Message}"); + } + } + + public async Task DeleteAsync(int id) + { + var existingBook = await _booksRepository.FindByIdAsync(id); + + if (existingBook == null) + return new BookResponse("Book not found."); + + try + { + _booksRepository.Remove(existingBook); + await _unitOfWork.CompleteAsync(); + + _logger.Log(LogLevel.Information, $"Book with id {id} deleted with Success"); + return new BookResponse(existingBook); + } + catch (Exception ex) + { + _logger.Log(LogLevel.Error, $"An error occurred when trying to delete the Book with id {id}: {ex.Message}"); + return new BookResponse($"An error occurred when deleting the category: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/Services/UsersService.cs b/LibraryAPI/API/Services/UsersService.cs new file mode 100644 index 0000000..8a2fd83 --- /dev/null +++ b/LibraryAPI/API/Services/UsersService.cs @@ -0,0 +1,87 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Domain.Services; +using LibraryApi.Domain.Services.Communication; +using LibraryApi.Models; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace LibraryApi.Services +{ + public class UsersService : IUsersService + { + private readonly IUsersRepository _usersRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IConfiguration _configuration; + + public UsersService(IUsersRepository usersRepository, IUnitOfWork unitOfWork, IConfiguration configuration) + { + _usersRepository = usersRepository; + _unitOfWork = unitOfWork; + _configuration = configuration; + } + + public async Task GenerateToken(User user) + { + var tokenHandler = new JwtSecurityTokenHandler(); + var key = Encoding.ASCII.GetBytes(_configuration["Authentication:SecretKey"]); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity(new Claim[] + { + new Claim(ClaimTypes.Name, user.Email.ToString()), + new Claim(ClaimTypes.Role, user.Role.ToString()) + }), + Expires = DateTime.UtcNow.AddHours(2), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) + }; + var token = tokenHandler.CreateToken(tokenDescriptor); + return tokenHandler.WriteToken(token); + } + + public async Task LoginAsync(LoginUserModel model) + { + try + { + var user = await _usersRepository.GetUserByEmailAsync(model.Email); + if (user == null || !BCrypt.Net.BCrypt.Verify(model.Password, user.Password)) + return new LoginResponse($"There is no user with this username and password"); + + var token = await GenerateToken(user); + var tokenModel = new JwtModel { Email = user.Email, Token = token }; + + return new LoginResponse(tokenModel); + } + catch (Exception ex) + { + // Do some logging stuff + return new LoginResponse($"An error occurred trying to Login: {ex.Message}"); + } + } + + public async Task SaveAsync(User user, string password) + { + try + { + var existsUserWithEmail = await _usersRepository.GetUserByEmailAsync(user.Email); + if (existsUserWithEmail != null) + return new UserResponse($"An user with this email aready exists"); + + user.Password = BCrypt.Net.BCrypt.HashPassword(password); + + await _usersRepository.AddAsync(user); + await _unitOfWork.CompleteAsync(); + + return new UserResponse(user); + } + catch (Exception ex) + { + // Do some logging stuff + return new UserResponse($"An error occurred when saving the user: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/LibraryAPI/API/appsettings.Development.json b/LibraryAPI/API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/LibraryAPI/API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/LibraryAPI/API/appsettings.json b/LibraryAPI/API/appsettings.json new file mode 100644 index 0000000..c6c8666 --- /dev/null +++ b/LibraryAPI/API/appsettings.json @@ -0,0 +1,12 @@ +{ + "Authentication": { + "SecretKey": "fedaf7d8863b48e197b9287d492b708e" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/LibraryAPI/LibraryAPI.sln b/LibraryAPI/LibraryAPI.sln new file mode 100644 index 0000000..48bd9c7 --- /dev/null +++ b/LibraryAPI/LibraryAPI.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "API", "API\API.csproj", "{4FD1F1FB-DF80-4EC8-BA76-8F7A23981530}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibraryAPITests", "Tests\LibraryAPITests\LibraryAPITests.csproj", "{B8B8BEAC-3A49-4874-9809-CE1D34156A95}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4FD1F1FB-DF80-4EC8-BA76-8F7A23981530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD1F1FB-DF80-4EC8-BA76-8F7A23981530}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD1F1FB-DF80-4EC8-BA76-8F7A23981530}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD1F1FB-DF80-4EC8-BA76-8F7A23981530}.Release|Any CPU.Build.0 = Release|Any CPU + {B8B8BEAC-3A49-4874-9809-CE1D34156A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8B8BEAC-3A49-4874-9809-CE1D34156A95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8B8BEAC-3A49-4874-9809-CE1D34156A95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8B8BEAC-3A49-4874-9809-CE1D34156A95}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E13B5106-1AAE-49EE-BEAE-D756BFD10CE1} + EndGlobalSection +EndGlobal diff --git a/LibraryAPI/Tests/LibraryAPITests/BooksServiceTest.cs b/LibraryAPI/Tests/LibraryAPITests/BooksServiceTest.cs new file mode 100644 index 0000000..6e69e85 --- /dev/null +++ b/LibraryAPI/Tests/LibraryAPITests/BooksServiceTest.cs @@ -0,0 +1,95 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Models; +using LibraryApi.Services; +using Microsoft.Extensions.Logging; +using Moq; + +namespace Tests +{ + public class BooksServiceTest + { + public Mock booksRepositoryMock = new Mock(); + public Mock unitOfWork = new Mock(); + public Mock> logger = new Mock>(); + + [Fact] + public async void SaveBookWithSuccess() + { + var book = new Book { Id = 1, Name = "Name", Author = "Author", ImageUrl = "SomeImageUrl" }; + + var booksService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await booksService.SaveAsync(book); + + Assert.Equal(book, result.Book); + Assert.True(result.Success); + } + + [Fact] + public async void SaveBookWithExistentName() + { + var bookToInsert = new Book { Id = 1, Name = "Name", Author = "Author", ImageUrl = "SomeImageUrl" }; + var ListBooksModel = new ListBooksModel { Page = 1, NumberOfItens = 10 }; + + booksRepositoryMock + .Setup(s => s.FindByNameAsync(bookToInsert.Name)) + .ReturnsAsync(bookToInsert); + + var usersService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await usersService.SaveAsync(bookToInsert); + + Assert.False(result.Success); + } + + [Fact] + public async void UpdateBookWithSuccess() + { + var book = new Book { Id = 1, Name = "Name", Author = "Author", ImageUrl = "SomeImageUrl" }; + + booksRepositoryMock + .Setup(s => s.FindByIdAsync(book.Id)) + .ReturnsAsync(book); + + var booksService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await booksService.UpdateAsync(1, book); + + Assert.Equal(book, result.Book); + Assert.True(result.Success); + } + + [Fact] + public async void UpdateBookWithInexistentId() + { + var bookToUpdate = new Book { Id = 1, Name = "Name", Author = "Author", ImageUrl = "SomeImageUrl" }; + + var booksService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await booksService.UpdateAsync(0, bookToUpdate); + + Assert.Null(result.Book); + Assert.False(result.Success); + } + + [Fact] + public async void DeleteBookWithSuccess() + { + var bookToDelete = new Book { Id = 1, Name = "Name", Author = "Author", ImageUrl = "SomeImageUrl" }; + booksRepositoryMock.Setup(s => s.FindByIdAsync(1)).ReturnsAsync(bookToDelete); + + var booksService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await booksService.DeleteAsync(1); + + Assert.Equal(bookToDelete, result.Book); + Assert.True(result.Success); + } + + [Fact] + public async void DeleteBookWithInexistentId() + { + var booksService = new BooksService(booksRepositoryMock.Object, unitOfWork.Object, logger.Object); + var result = await booksService.DeleteAsync(0); + + Assert.Null(result.Book); + Assert.False(result.Success); + } + } +} diff --git a/LibraryAPI/Tests/LibraryAPITests/LibraryAPITests.csproj b/LibraryAPI/Tests/LibraryAPITests/LibraryAPITests.csproj new file mode 100644 index 0000000..6616e0d --- /dev/null +++ b/LibraryAPI/Tests/LibraryAPITests/LibraryAPITests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/LibraryAPI/Tests/LibraryAPITests/UsersServiceTest.cs b/LibraryAPI/Tests/LibraryAPITests/UsersServiceTest.cs new file mode 100644 index 0000000..7e94ba7 --- /dev/null +++ b/LibraryAPI/Tests/LibraryAPITests/UsersServiceTest.cs @@ -0,0 +1,96 @@ +using LibraryApi.Domain.Entities; +using LibraryApi.Domain.Repositories; +using LibraryApi.Models; +using LibraryApi.Services; +using Microsoft.Extensions.Configuration; +using Moq; + +namespace Tests +{ + public class UsersServiceTest + { + public Mock usersRepositoryMock = new Mock(); + public Mock unitOfWork = new Mock(); + public IConfiguration configurationForUnitTests; + + public UsersServiceTest() + { + var configuration = new Dictionary + { + {"Authentication:SecretKey", "fedaf7d8863b48e197b9287d492b708e"}, + }; + + configurationForUnitTests = new ConfigurationBuilder() + .AddInMemoryCollection(configuration) + .Build(); + } + + [Fact] + public async void SaveUserWithSuccess() + { + var user = new User { Email = "email@email.com", Password = "password", Role = UserRoleEnum.BASICO }; + + var usersService = new UsersService(usersRepositoryMock.Object, unitOfWork.Object, configurationForUnitTests); + var result = await usersService.SaveAsync(user, user.Password); + + Assert.Equal(user, result.User); + } + + [Fact] + public async void SaveUserWithExistentEmail() + { + var user = new User { Email = "email@email.com", Password = "password", Role = UserRoleEnum.BASICO }; + + usersRepositoryMock + .Setup(s => s.GetUserByEmailAsync(user.Email)) + .ReturnsAsync(user); + + var usersService = new UsersService(usersRepositoryMock.Object, unitOfWork.Object, configurationForUnitTests); + var result = await usersService.SaveAsync(user, user.Password); + + Assert.False(result.Success); + } + + [Fact] + public async void LoginWithSuccess() + { + var user = new User + { + Email = "email@email.com", + Password = "$2b$10$epQGPBtEbYioEDCg7x/jjuggbcIGCNuoEj4UIUTf/fdeC7rXeJJQq", + Role = UserRoleEnum.BASICO + }; + var loginUserResource = new LoginUserModel { Email = "email@email.com", Password = "123456" }; + + usersRepositoryMock + .Setup(s => s.GetUserByEmailAsync(user.Email)) + .ReturnsAsync(user); + + var usersService = new UsersService(usersRepositoryMock.Object, unitOfWork.Object, configurationForUnitTests); + var result = await usersService.LoginAsync(loginUserResource); + + Assert.True(result.Success); + } + + [Fact] + public async void LoginWithWrongPassword() + { + var user = new User + { + Email = "email@email.com", + Password = "$2b$10$epQGPBtEbYioEDCg7x/jjuggbcIGCNuoEj4UIUTf/fdeC7rXeJJQq", + Role = UserRoleEnum.BASICO + }; + var loginUserResource = new LoginUserModel { Email = "email@email.com", Password = "123" }; + + usersRepositoryMock + .Setup(s => s.GetUserByEmailAsync(user.Email)) + .ReturnsAsync(user); + + var usersService = new UsersService(usersRepositoryMock.Object, unitOfWork.Object, configurationForUnitTests); + var result = await usersService.LoginAsync(loginUserResource); + + Assert.False(result.Success); + } + } +} \ No newline at end of file diff --git a/LibraryAPI/Tests/LibraryAPITests/Usings.cs b/LibraryAPI/Tests/LibraryAPITests/Usings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/LibraryAPI/Tests/LibraryAPITests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file From ba75998ad9c5a10c73191e52aaa03002f0b1cff2 Mon Sep 17 00:00:00 2001 From: Guilherme Reis Date: Fri, 18 Nov 2022 14:03:05 -0300 Subject: [PATCH 2/4] Seed and README --- LibraryAPI/API/API.csproj | 4 + .../API/Persistence/Contexts/AppDbContext.cs | 77 ++++++++++++ LibraryAPI/API/Program.cs | 2 +- README.md | 112 +++++------------- 4 files changed, 112 insertions(+), 83 deletions(-) diff --git a/LibraryAPI/API/API.csproj b/LibraryAPI/API/API.csproj index 04e4cc8..5f40d61 100644 --- a/LibraryAPI/API/API.csproj +++ b/LibraryAPI/API/API.csproj @@ -16,6 +16,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs b/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs index f4fdfc7..4f00194 100644 --- a/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs +++ b/LibraryAPI/API/Persistence/Contexts/AppDbContext.cs @@ -28,6 +28,83 @@ protected override void OnModelCreating(ModelBuilder builder) Name = "Hunter x Hunter 01", ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 2, + Name = "Hunter x Hunter 02", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 4, + Name = "Hunter x Hunter 04", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 5, + Name = "Hunter x Hunter 05", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 6, + Name = "Hunter x Hunter 06", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 7, + Name = "Hunter x Hunter 07", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 8, + Name = "Hunter x Hunter 08", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 9, + Name = "Hunter x Hunter 09", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 10, + Name = "Hunter x Hunter 10", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 11, + Name = "Hunter x Hunter 11", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 12, + Name = "Hunter x Hunter 12", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" + }, + new Book + { + Id = 13, + Name = "Hunter x Hunter 13", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Author = "Yoshihiro Togashi" } ); diff --git a/LibraryAPI/API/Program.cs b/LibraryAPI/API/Program.cs index be508bf..df1779d 100644 --- a/LibraryAPI/API/Program.cs +++ b/LibraryAPI/API/Program.cs @@ -6,7 +6,7 @@ using Microsoft.OpenApi.Models; using System.Text; -var connectionString = $"Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=LibraryDb;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"; +var connectionString = $"Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=LibraryDb2;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"; var builder = WebApplication.CreateBuilder(args); diff --git a/README.md b/README.md index ae61b8e..1122fc6 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,45 @@ -## Desafio Back-End +# Library API -#### Requisitos Gerais: +## Como executar +Para executar essa API, é necessário estar rodando uma instância do SQL Server. Tenso isso em mente, é necessário trocar a connection string do arquivo LibraryAPI/API/Program.cs pela connection string do seu banco local. -Uma livraria da cidade teve um aumento no número de seus exemplares e está com um problema para identificar todos os livros que possui em estoque. -Para ajudar a livraria foi solicitado a você desenvolver uma aplicação web para gerenciar estes exemplares. Requisitos: +Em seguida, é necessário atualizar o banco de dados com as migrations existentes, entrando na pasta LibraryAPI/API e executando o comando: +``` +Update-Database +``` -* O sistema deverá mostrar todos os livros cadastrados ordenados de forma ascendente pelo nome. -* Ao persistir, validar se o livro já foi cadastrado. -* O sistema deverá permitir consultar, criar, editar e excluir um livro. -* Os livros devem ser persistidos em um banco de dados. -* Criar algum mecanismo de log de registro e de erro. +Ou, utilizando a dotnet cli -#### Requisitos Técnicos: +``` +dotnet ef database update +``` -* Configurar o Swagger na aplicação (fundamental), pois será usado para testes. -* Incluir mecanismo de autenticação no Swagger, usando Token JWT (Bearer). -* Para a persistência dos dados deve ser utilizado o Entity Framework. -* Como banco de dados, pode ser usado MySQL, PostgreSQL ou SQL Server. -* Utilizar migrations ou Gerar Scripts e disponibilizá-los um uma pasta. -* Incluir git.ignore no repositório para não subir arquivos de compilação. +Para finalizar, podemos executar a aplicação pelo visual studio ou executar o comando abaixo para rodar a aplicação localmente: +``` +dotnet run +``` -#### Observações: -* O sistema deverá ser desenvolvido na plataforma .NET com C#. - (preferêncialmente 5.0+, caso for usado outra versão, informar no pull-request) -* Deve conter autenticação com dois níveis de acesso, um administrador e um público, o usuário de nível - público não terá autenticação, ou seja, terá acesso livre a consulta de livros -* Atenção aos princípio do SOLID. -* Não é necessária a criação de front-end, o teste será feito pelo Swagger UI. +Para acessá-la, digite no navegador a url da porta seguido de /swagger, por exemplo: -#### Diferenciais do desafio: -* Aplicação das boas práticas do DDD, TDD, Design Patterns, SOLID e Clean Code. -* A modelagem dos dados não será fornecida, de propósito. Desejamos avaliar a sua capacidade de abstração. -* A API deverá realizar tratamento de entrada de dados e retornar códigos de erro quando aplicáveis. -* Criar massa de dados para que seja possível verificar o funcionamento das lógicas propostas. -* Incluir parâmetros de paginação e campos de filtro nos métodos de consulta (GET). -* Documentar, via código-fonte, os campos, parâmetros e dados de retorno da API para exibição no Swagger. +``` +https://localhost:7044/swagger +``` +## Utilizando a API +Para utilizar a API, é necessário, antes de tudo, criar um usuário. Existem dois tipos de usuário, o básico e o administrador. Um usuário do tipo básico pode apenas consultar os livros existentes, já um administrador pode inserir, atualizar e apagar livros, além de poder ver os livros existentes. -## Como deverá ser entregue: +Para criar um novo usuário, chame o endpoint a seguir (Lembrando de passar Role = 1 para Basico e Role = 2 para Administrador). - 1. Faça um fork deste repositório; - 2. Realize o teste; - 3. Adicione seu currículo na raiz do repositório; - 4. Envie-nos o PULL-REQUEST para que seja avaliado. +``` +https://localhost:7044/api/Users - POST +``` +Feito isso, é necessário chamar o endpoint de login, para obter um Token e utilizar a API: +``` +https://localhost:7044/api/Users/login - POST +``` -## Back-End Challenge (English) - -#### General requirements: - -A bookstore in town has had an increase in the number of its copies and is having a problem identifying all the books it has in stock. -To help the bookstore, you were asked to develop a web application to manage these copies. Requirements: - - -* The system should show all registered books sorted in ascending order by name. -* When persisting, validate if the book has already been registered. -* The system should allow consulting, creating, editing and deleting books. -* Books must be persisted in a database. -* Create some logging and error logging mechanism. - -#### Technical requirements: - -* Configure Swagger in the application (fundamental), as it will be used for testing. -* Include authentication mechanism in Swagger, using JWT Token (Bearer). -* For data persistence, Entity Framework must be used. -* As a database, MySQL, PostgreSQL or SQL Server can be used. -* Use migrations or Generate Scripts and make them available in a folder. -* Include git.ignore in the repository to avoid uploading deployment files. - - -#### Comments: -* The system must be developed on the .NET platform with C#. -(preferably 5.0+, if another version is used, inform the pull-request) -* Must contain authentication with two levels of access, an administrator and a public, user level -public will not have authentication, that is, it will have free access to consult books -* Attention to the principles of SOLID. -* No front-end creation required, testing will be done by Swagger UI. - -#### Challenge differentials: -* Application of DDD, TDD, Design Patterns, SOLID and Clean Code best practices. -* Data modeling will not be provided on purpose. We wish to assess your capacity for abstraction. -* The API must perform data entry handling and return error codes when applicable. -* Create mass of data so that it is possible to verify the functioning of the proposed logics. -* Include pagination parameters and filter fields in query (GET) methods. -* Document, via source code, the API fields, parameters and return data for display in Swagger. - - -## How it should be delivered: - - 1. Fork this repository; - 2. Carry out the test; - 3. Add your CV to the repository root; - 4. Send us the PULL-REQUEST to be evaluated. \ No newline at end of file +Por último, pegue o token que foi lhe devolvido na requisição e insira ele no Authorize do Swagger, liberando as outras requisições da API. \ No newline at end of file From 4b1ecfdc2921b05573ef7759416ba047e8de43ab Mon Sep 17 00:00:00 2001 From: Guilherme Reis Date: Fri, 18 Nov 2022 14:03:59 -0300 Subject: [PATCH 3/4] CV --- "CV - Guilherme Reis Ara\303\272jo.pdf" | Bin 0 -> 61635 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "CV - Guilherme Reis Ara\303\272jo.pdf" diff --git "a/CV - Guilherme Reis Ara\303\272jo.pdf" "b/CV - Guilherme Reis Ara\303\272jo.pdf" new file mode 100644 index 0000000000000000000000000000000000000000..961735bad34716b5d049ce2ad1709d44b16a28d8 GIT binary patch literal 61635 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 Date: Fri, 18 Nov 2022 14:06:46 -0300 Subject: [PATCH 4/4] seed migration --- .../20221118170602_seed.Designer.cs | 167 ++++++++++++++++++ .../API/Migrations/20221118170602_seed.cs | 93 ++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 77 ++++++++ 3 files changed, 337 insertions(+) create mode 100644 LibraryAPI/API/Migrations/20221118170602_seed.Designer.cs create mode 100644 LibraryAPI/API/Migrations/20221118170602_seed.cs diff --git a/LibraryAPI/API/Migrations/20221118170602_seed.Designer.cs b/LibraryAPI/API/Migrations/20221118170602_seed.Designer.cs new file mode 100644 index 0000000..d9a3e15 --- /dev/null +++ b/LibraryAPI/API/Migrations/20221118170602_seed.Designer.cs @@ -0,0 +1,167 @@ +// +using LibraryApi.Persistence.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace LibraryApi.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20221118170602_seed")] + partial class seed + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("LibraryApi.Domain.Entities.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageUrl") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + + b.HasData( + new + { + Id = 1, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 01" + }, + new + { + Id = 2, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 02" + }, + new + { + Id = 4, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 04" + }, + new + { + Id = 5, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 05" + }, + new + { + Id = 6, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 06" + }, + new + { + Id = 7, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 07" + }, + new + { + Id = 8, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 08" + }, + new + { + Id = 9, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 09" + }, + new + { + Id = 10, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 10" + }, + new + { + Id = 11, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 11" + }, + new + { + Id = 12, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 12" + }, + new + { + Id = 13, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 13" + }); + }); + + modelBuilder.Entity("LibraryApi.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Users", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LibraryAPI/API/Migrations/20221118170602_seed.cs b/LibraryAPI/API/Migrations/20221118170602_seed.cs new file mode 100644 index 0000000..74d920d --- /dev/null +++ b/LibraryAPI/API/Migrations/20221118170602_seed.cs @@ -0,0 +1,93 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace LibraryApi.Migrations +{ + /// + public partial class seed : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "Books", + columns: new[] { "Id", "Author", "ImageUrl", "Name" }, + values: new object[,] + { + { 2, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 02" }, + { 4, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 04" }, + { 5, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 05" }, + { 6, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 06" }, + { 7, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 07" }, + { 8, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 08" }, + { 9, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 09" }, + { 10, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 10" }, + { 11, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 11" }, + { 12, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 12" }, + { 13, "Yoshihiro Togashi", "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", "Hunter x Hunter 13" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 2); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 4); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 5); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 6); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 7); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 8); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 9); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 10); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 11); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 12); + + migrationBuilder.DeleteData( + table: "Books", + keyColumn: "Id", + keyValue: 13); + } + } +} diff --git a/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs b/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs index dab8a83..568d5d0 100644 --- a/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs +++ b/LibraryAPI/API/Migrations/AppDbContextModelSnapshot.cs @@ -54,6 +54,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) Author = "Yoshihiro Togashi", ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", Name = "Hunter x Hunter 01" + }, + new + { + Id = 2, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 02" + }, + new + { + Id = 4, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 04" + }, + new + { + Id = 5, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 05" + }, + new + { + Id = 6, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 06" + }, + new + { + Id = 7, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 07" + }, + new + { + Id = 8, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 08" + }, + new + { + Id = 9, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 09" + }, + new + { + Id = 10, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 10" + }, + new + { + Id = 11, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 11" + }, + new + { + Id = 12, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 12" + }, + new + { + Id = 13, + Author = "Yoshihiro Togashi", + ImageUrl = "https://upload.wikimedia.org/wikipedia/pt/6/63/Hunter_x_Hunter_Volume_1.JPG", + Name = "Hunter x Hunter 13" }); });