Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ API/obj
API.Tests/bin
API.Tests/obj
API/config/logs/*
API/wwwroot
.vscode
appsettings.Development.json
*.ai
14 changes: 13 additions & 1 deletion API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
<ImplicitUsings>enable</ImplicitUsings>
<Product>In-Out</Product>
<Company>Brasserie Van De Kelder</Company>
<AssemblyVersion>0.0.1</AssemblyVersion>
<AssemblyVersion>0.0.2</AssemblyVersion>
<AssemblyName>In-Out</AssemblyName>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>
<ApplicationIcon>../favicon.ico</ApplicationIcon>
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DocumentationFile>bin\$(Configuration)\$(AssemblyName).xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Flurl" Version="4.0.0" />
Expand All @@ -22,6 +33,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
Expand Down
2 changes: 2 additions & 0 deletions API/Constants/PolicyConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ public static class PolicyConstants
/// Grants user permission to change application settings
/// </summary>
public const string ManageApplication = nameof(ManageApplication);

public static readonly IList<string> Roles = [CreateForOthers, HandleDeliveries, ViewAllDeliveries, ManageStock, ManageProducts, ManageClients, ManageApplication];
}
52 changes: 52 additions & 0 deletions API/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using API.Extensions;
using API.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers;

[Route("[controller]")]
public class AuthController: ControllerBase
{
/// <summary>
/// Trigger OIDC login flow
/// </summary>
/// <param name="returnUrl"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("login")]
public IActionResult Login(string returnUrl = "/")
{
var properties = new AuthenticationProperties { RedirectUri = returnUrl };
return Challenge(properties, IdentityServiceExtensions.OpenIdConnect);
}

/// <summary>
/// Trigger OIDC logout flow, if no auth cookie is found. Redirects to root
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("logout")]
public async Task<IActionResult> Logout()
{
if (!Request.Cookies.ContainsKey(OidcService.CookieName))
{
return Redirect("/");
}

var res = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (!res.Succeeded || res.Properties == null || string.IsNullOrEmpty(res.Properties.GetString(OidcService.IdToken)))
{
HttpContext.Response.Cookies.Delete(OidcService.CookieName);
return Redirect("/");
}

return SignOut(
new AuthenticationProperties { RedirectUri = "/login" },
CookieAuthenticationDefaults.AuthenticationScheme,
IdentityServiceExtensions.OpenIdConnect);
}

}
10 changes: 10 additions & 0 deletions API/Controllers/ClientController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace API.Controllers;
public class ClientController(IUnitOfWork unitOfWork, IClientService clientService, IMapper mapper) : BaseApiController
{

/// <summary>
/// Get clients by ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("by-id")]
public async Task<ActionResult<IList<ClientDto>>> GetClientDtosByIds(IList<int> ids)
{
Expand All @@ -26,6 +31,11 @@ public async Task<ActionResult<IList<ClientDto>>> GetClientDtos()
return Ok(await unitOfWork.ClientRepository.GetClientDtos());
}

/// <summary>
/// Search clients on name, contact name, and contact email
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
[HttpGet("search")]
public async Task<ActionResult<IList<ClientDto>>> Search([FromQuery] string query)
{
Expand Down
22 changes: 0 additions & 22 deletions API/Controllers/ConfigurationController.cs

This file was deleted.

39 changes: 37 additions & 2 deletions API/Controllers/DeliveryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using API.Helpers;
using API.Services;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers;
Expand All @@ -17,6 +18,11 @@ public class DeliveryController(ILogger<DeliveryController> logger,
IUserService userService, IMapper mapper): BaseApiController
{

/// <summary>
/// Get delivery by id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
public async Task<ActionResult<DeliveryDto?>> Get(int id)
{
Expand All @@ -25,13 +31,20 @@ public class DeliveryController(ILogger<DeliveryController> logger,
return Ok(delivery);
}

/// <summary>
/// Filter deliveries
/// </summary>
/// <param name="filter"></param>
/// <param name="pagination"></param>
/// <returns></returns>
/// <exception cref="UnauthorizedAccessException"></exception>
[HttpPost("filter")]
public async Task<IList<DeliveryDto>> GetDeliveries([FromBody] FilterDto filter, [FromQuery] PaginationParams pagination)
{
var user = await unitOfWork.UsersRepository.GetByUserIdAsync(User.GetUserId());
if (user == null)
{
logger.LogError($"User {User.GetUserId()} not found");
logger.LogError("User {UserId} not found", User.GetUserId());
throw new UnauthorizedAccessException();
}

Expand All @@ -56,6 +69,11 @@ public async Task<IList<DeliveryDto>> GetDeliveries([FromBody] FilterDto filter,
return await unitOfWork.DeliveryRepository.GetDeliveries(filter, pagination);
}

/// <summary>
/// Create a new delivery
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult<DeliveryDto>> Create(DeliveryDto dto)
{
Expand All @@ -65,24 +83,41 @@ public async Task<ActionResult<DeliveryDto>> Create(DeliveryDto dto)
return Ok(mapper.Map<DeliveryDto>(delivery));
}

/// <summary>
/// Update an existing delivery
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPut]
public async Task<IActionResult> Update(DeliveryDto dto)
{
var delivery = await deliveryService.UpdateDelivery(User, dto);
return Ok(mapper.Map<DeliveryDto>(delivery));
}

/// <summary>
/// Change the state of a delivery
/// </summary>
/// <param name="deliveryId"></param>
/// <param name="nextState"></param>
/// <returns></returns>
[HttpPost("transition")]
public async Task<IActionResult> UpdateState([FromQuery] int deliveryId, [FromQuery] DeliveryState nextState)
{
await deliveryService.TransitionDelivery(User, deliveryId, nextState);
return Ok();
}

/// <summary>
/// Fully delete a delivery, this is destructive. Consider transition to <see cref="DeliveryState.Cancelled"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[Authorize(PolicyConstants.HandleDeliveries)]
public async Task<IActionResult> Delete(int id)
{
await deliveryService.DeleteDelivery(id);
await deliveryService.DeleteDelivery(User, id);
return Ok();
}

Expand Down
2 changes: 2 additions & 0 deletions API/Controllers/FallbackController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;

namespace API.Controllers;

public class FallbackController: Controller
{
[SwaggerIgnore]
public PhysicalFileResult Index()
{
return PhysicalFile(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html"), "text/HTML");
Expand Down
15 changes: 15 additions & 0 deletions API/Controllers/ProductsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ namespace API.Controllers;
public class ProductsController(IUnitOfWork unitOfWork, IProductService productService): BaseApiController
{

/// <summary>
/// Get products by ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("by-ids")]
public async Task<ActionResult<IList<ProductDto>>> GetProductsByIds(IList<int> ids)
{
Expand All @@ -28,6 +33,11 @@ public async Task<ActionResult<IList<ProductDto>>> GetProducts([FromQuery] bool
return Ok(await unitOfWork.ProductRepository.GetAllDto(onlyEnabled));
}

/// <summary>
/// Returns all product categories
/// </summary>
/// <param name="onlyEnabled"></param>
/// <returns></returns>
[HttpGet("category")]
public async Task<ActionResult<IList<ProductCategoryDto>>> GetProductCategories([FromQuery] bool onlyEnabled = false)
{
Expand Down Expand Up @@ -134,6 +144,11 @@ public async Task<IActionResult> DeleteCategory(int id)
return Ok();
}

/// <summary>
/// Re-order categories
/// </summary>
/// <param name="ids">Ids of the categories in the wanted order</param>
/// <returns></returns>
[HttpPost("category/order")]
[Authorize(Policy = PolicyConstants.ManageProducts)]
public async Task<IActionResult> OrderCategories(IList<int> ids)
Expand Down
15 changes: 15 additions & 0 deletions API/Controllers/StockController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,33 @@ namespace API.Controllers;
public class StockController(ILogger<StockController> logger, IUnitOfWork unitOfWork, IStockService stockService): BaseApiController
{

/// <summary>
/// Retrieve change history
/// </summary>
/// <param name="stockId"></param>
/// <returns></returns>
[HttpGet("history/{stockId}")]
public async Task<ActionResult<IList<StockHistoryDto>>> GetHistory(int stockId)
{
return Ok(await unitOfWork.StockRepository.GetHistoryDto(stockId));
}

/// <summary>
/// Returns all stock
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult<IList<StockDto>>> GetStock()
{
return Ok(await unitOfWork.StockRepository.GetAllDtoAsync(StockIncludes.Product));
}

/// <summary>
/// Update stock
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
/// <exception cref="UnauthorizedAccessException"></exception>
[HttpPost]
[Authorize(Policy = PolicyConstants.ManageStock)]
public async Task<IActionResult> UpdateStock(UpdateStockDto dto)
Expand Down
36 changes: 35 additions & 1 deletion API/Controllers/UserController.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,67 @@
using API.Data;
using API.DTOs;
using API.Extensions;
using API.Services;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace API.Controllers;

public class UserController(IUnitOfWork unitOfWork, IUserService userService, IMapper mapper): BaseApiController
{

/// <summary>
/// Get users by ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
[HttpPost("by-id")]
public async Task<ActionResult<IList<UserDto>>> GetByIds(IList<int> ids)
{
return Ok(await unitOfWork.UsersRepository.GetByIds(ids));
}

/// <summary>
/// Returns true if the auth cookie is present, does not say anything about its validity
/// </summary>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("has-cookie")]
public ActionResult<bool> HasCookie()
{
return Ok(Request.Cookies.ContainsKey(OidcService.CookieName));
}

/// <summary>
/// Returns the current authenticated user, and its roles
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult<UserDto>> CurrentUser()
{
var user = await userService.GetUser(User);
return Ok(mapper.Map<UserDto>(user));
var dto = mapper.Map<UserDto>(user);

dto.Roles = HttpContext.User.GetRoles();
return Ok(dto);
}

/// <summary>
/// Returns all users
/// </summary>
/// <returns></returns>
[HttpGet("all")]
public async Task<ActionResult<IList<UserDto>>> GetAll()
{
return Ok(await unitOfWork.UsersRepository.GetAll());
}

/// <summary>
/// Search for a specific user
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
[HttpGet("search")]
public async Task<ActionResult<IList<UserDto>>> Search([FromQuery] string query)
{
Expand Down
Loading