Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
###############################
# Visual Studio / .NET
###############################
.vs/
*.user
*.suo
*.userosscache
*.sln.docstates

###############################
# Build outputs
###############################
bin/
obj/
out/
TestResults/

###############################
# IDEs
###############################
.vscode/
.idea/
*.DotSettings.user

###############################
# NuGet
###############################
packages/
*.nupkg
*.snupkg

###############################
# Secrets / Environment
###############################
secrets.json
.env
.env.*
appsettings*.Secrets.json

###############################
# OS files
###############################
Thumbs.db
.DS_Store

###############################
# Logs
###############################
*.log
4 changes: 4 additions & 0 deletions DesafioMiltec/DesafioMiltec.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Solution>
<Project Path="src/DesafioMiltec/DesafioMiltec.csproj" />
<Project Path="tests/DesafioMiltec.Tests/DesafioMiltec.Tests.csproj" />
</Solution>
39 changes: 39 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/Application/Mappers/BookMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using DesafioMiltec.Domain.Entities;
using DesafioMiltec.DTOs;
using System.Text.Json;

namespace DesafioMiltec.Application.Mappers
{
public static class BookMapper
{
public static BookDto ToDto(Book book)
=> new()
{
Id = book.Id,
Name = book.Name,
Price = book.Price,
Specifications = book.Specifications
.ToDictionary(
x => x.Key,
x => ConvertJsonElement(x.Value)
)
};

private static object? ConvertJsonElement(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number =>
element.TryGetInt32(out var i) ? i : element.GetDecimal(),
JsonValueKind.Array =>
element.EnumerateArray()
.Select(ConvertJsonElement)
.ToList(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => null
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using DesafioMiltec.Application.Sorting;
using DesafioMiltec.Domain.Entities;
using DesafioMiltec.Domain.Interfaces;
using System.Text.Json;


namespace DesafioMiltec.Application.Services
{
public sealed class BookService
{
private readonly IBookRepository _repository;
private readonly ICalculateShipping _shippingCalculator;

public BookService(
IBookRepository repository,
ICalculateShipping shippingCalculator)
{
_repository = repository;
_shippingCalculator = shippingCalculator;
}

public IReadOnlyCollection<Book> Search(
string? term,
IBookSort sort)
{
var books = _repository.GetAll().AsEnumerable();

if (!string.IsNullOrWhiteSpace(term))
{
books = books.Where(book =>
book.Name.Contains(term, StringComparison.OrdinalIgnoreCase)
|| book.Specifications.Any(x =>
JsonElementContains(x.Value, term)));
}

return sort.Sort(books).ToList();
}

public Book? GetById(long id)
{
if (id <= 0)
return null;

return _repository
.GetAll()
.FirstOrDefault(b => b.Id == id);
}

public decimal CalculateShipping(decimal price)
=> _shippingCalculator.Calculate(price);

private static bool JsonElementContains(JsonElement element, string term)
{
return element.ValueKind switch
{
JsonValueKind.String =>
element.GetString()?
.Contains(term, StringComparison.OrdinalIgnoreCase) == true,

JsonValueKind.Number =>
element.ToString()
.Contains(term, StringComparison.OrdinalIgnoreCase),

JsonValueKind.Array =>
element.EnumerateArray()
.Any(e => JsonElementContains(e, term)),

_ => false
};
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using DesafioMiltec.Domain.Interfaces;

namespace DesafioMiltec.Application.Services
{
public sealed class CalculateShippingService : ICalculateShipping
{
private const decimal ShippingRate = 0.20m;

public decimal Calculate(decimal price)
{
if (price <= 0)
throw new ArgumentException("O preço deve ser maior que zero.", nameof(price));

return Math.Round(price * ShippingRate, 2);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using DesafioMiltec.Domain.Entities;

namespace DesafioMiltec.Application.Sorting
{
public interface IBookSort
{
IEnumerable<Book> Sort(IEnumerable<Book> books);
}
}
10 changes: 10 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/Application/Sorting/NoSort.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using DesafioMiltec.Domain.Entities;

namespace DesafioMiltec.Application.Sorting
{
public sealed class NoSort : IBookSort
{
public IEnumerable<Book> Sort(IEnumerable<Book> books)
=> books;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using DesafioMiltec.Domain.Entities;

namespace DesafioMiltec.Application.Sorting
{
public sealed class PriceSortAsc : IBookSort
{
public IEnumerable<Book> Sort(IEnumerable<Book> books)
=> books.OrderBy(b => b.Price);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using DesafioMiltec.Domain.Entities;

namespace DesafioMiltec.Application.Sorting
{
public sealed class PriceSortDesc : IBookSort
{
public IEnumerable<Book> Sort(IEnumerable<Book> books)
=> books.OrderByDescending(b => b.Price);
}
}
95 changes: 95 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/Controllers/BookController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using DesafioMiltec.Application.Mappers;
using DesafioMiltec.Application.Services;
using DesafioMiltec.Application.Sorting;
using DesafioMiltec.DTOs;
using Microsoft.AspNetCore.Mvc;
using System.Globalization;

namespace DesafioMiltec.Controllers
{
[ApiController]
[Route("api/[controller]")]
public sealed class BookController : ControllerBase
{
private readonly BookService _bookService;

public BookController(BookService bookService)
{
_bookService = bookService;
}

/// <summary>
/// Busca livros por nome, autor ou qualquer especificação.
/// </summary>
/// <param name="search">Texto para busca</param>
/// <param name="order">Ordenação por preço: asc ou desc</param>
/// <returns>Lista de livros</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult Get(
[FromQuery] string? search,
[FromServices] PriceSortAsc priceSortAsc,
[FromServices] PriceSortDesc priceSortDesc,
[FromQuery] string order = "asc")
{
IBookSort sortStrategy = order.Equals("desc", StringComparison.OrdinalIgnoreCase)
? priceSortDesc
: priceSortAsc;

var books = _bookService.Search(search, sortStrategy);

var result = books
.Select(BookMapper.ToDto)
.ToList();

return Ok(result);

}

[HttpGet("{id:long}")]
[ProducesResponseType(typeof(BookDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<BookDto> GetById(long id)
{
if (id <= 0)
return BadRequest("O id deve ser maior que zero.");

var book = _bookService.GetById(id);

if (book is null)
return NotFound($"Livro com id {id} não encontrado.");

return Ok(BookMapper.ToDto(book));
}

/// <summary>
/// Calcula o valor do frete (20%) para um livro específico.
/// </summary>
/// <param name="id">Id do livro</param>
/// <returns>Valor do frete</returns>
[HttpGet("{id:long}/calcularfrete")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetFreight(long id)
{
var book = _bookService.GetById(id);

if (book is null)
return NotFound($"Livro com id {id} não encontrado.");

var freight = _bookService.CalculateShipping(book.Price);

var culture = new CultureInfo("pt-BR");

var dto = new ShippingDto
{
BookId = book.Id,
BookName = book.Name,
BookPrice = book.Price.ToString("C", culture),
Freight = freight.ToString("C", culture)
};

return Ok(dto);
}
}
}
10 changes: 10 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/DTOs/BookDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DesafioMiltec.DTOs
{
public sealed class BookDto
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public decimal Price { get; init; }
public Dictionary<string, object?> Specifications { get; init; } = [];
}
}
10 changes: 10 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/DTOs/ShippingDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DesafioMiltec.DTOs
{
public class ShippingDto
{
public int BookId { get; init; }
public string BookName { get; init; } = string.Empty;
public string BookPrice { get; init; } = string.Empty;
public string Freight { get; init; } = string.Empty;
}
}
19 changes: 19 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/DesafioMiltec.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
</ItemGroup>

<ItemGroup>
<None Update="Infrastructure\Data\books.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/DesafioMiltec.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@DesafioMiltec_HostAddress = http://localhost:5089

GET {{DesafioMiltec_HostAddress}}/weatherforecast/
Accept: application/json

###
12 changes: 12 additions & 0 deletions DesafioMiltec/src/DesafioMiltec/Domain/Entities/Book.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json;

namespace DesafioMiltec.Domain.Entities
{
public class Book
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public Dictionary<string, JsonElement> Specifications { get; init; } = [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Text.Json;

namespace DesafioMiltec.Domain.Entities
{
public sealed class BookSpecifications
{
public Dictionary<string, JsonElement> Values { get; init; } = [];
}
}
Loading