First version of Service

This commit is contained in:
Ignacio Gomez 2025-06-22 19:34:07 -06:00
commit e99b2c5a5a
51 changed files with 2167 additions and 0 deletions

363
.gitignore vendored Normal file
View File

@ -0,0 +1,363 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="3.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core.Inventory.External\Core.Inventory.External.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,33 @@
// ***********************************************************************
// <copyright file="FurnitureBasePort.cs">
// Core.Inventory.Application
// </copyright>
// ***********************************************************************
using Core.Adapters.Lib;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Inventory.Application.UseCases.Inventory.Adapter
{
public class FurnitureBasePort : BasePresenter, IFurnitureBasePort
{
/// <summary>
/// Handles success with a single <see cref="FurnitureBase"/>.
/// </summary>
/// <param name="output">The output adapter.</param>
public void Success(FurnitureBase output)
{
ViewModel = new OkObjectResult(output);
}
/// <summary>
/// Handles success with a list of <see cref="FurnitureBase"/>.
/// </summary>
/// <param name="output">The output list.</param>
public void Success(List<FurnitureBase> output)
{
ViewModel = new OkObjectResult(output);
}
}
}

View File

@ -0,0 +1,37 @@
// ***********************************************************************
// <copyright file="FurnitureVariantPort.cs">
// Core.Inventory.Application
// </copyright>
// ***********************************************************************
using Core.Adapters.Lib;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Inventory.Application.UseCases.Inventory.Adapter
{
/// <summary>
/// Presenter for FurnitureVariant use cases. Maps output to HTTP responses.
/// </summary>
public class FurnitureVariantPort : BasePresenter, IFurnitureVariantPort
{
/// <summary>
/// Handles success with a single <see cref="FurnitureVariant"/>.
/// </summary>
/// <param name="output">The output adapter.</param>
public void Success(FurnitureVariant output)
{
ViewModel = new OkObjectResult(output);
}
/// <summary>
/// Handles success with a list of <see cref="FurnitureVariant"/>.
/// </summary>
/// <param name="output">The output list.</param>
public void Success(List<FurnitureVariant> output)
{
ViewModel = new OkObjectResult(output);
}
}
}

View File

@ -0,0 +1,25 @@
// ***********************************************************************
// <copyright file="ChangeFurnitureBaseStatusRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Core.Blueprint.Mongo;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Command to change the status of a furniture base model.
/// </summary>
public class ChangeFurnitureBaseStatusRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public StatusEnum Status { get; set; }
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id);
}
}
}

View File

@ -0,0 +1,24 @@
// ***********************************************************************
// <copyright file="ChangeFurnitureVariantStatusRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Core.Blueprint.Mongo;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Command to change the status of a furniture variant.
/// </summary>
public class ChangeFurnitureVariantStatusRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public StatusEnum Status { get; set; }
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id);
}
}
}

View File

@ -0,0 +1,18 @@
// ***********************************************************************
// <copyright file="Dimensions.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
namespace Core.Inventory.Application.UseCases.Inventory.Input.Common
{
/// <summary>
/// Value object representing the dimensions of a furniture item.
/// </summary>
public class Dimensions
{
public float Width { get; set; }
public float Height { get; set; }
public float Depth { get; set; }
}
}

View File

@ -0,0 +1,33 @@
// ***********************************************************************
// <copyright file="CreateFurnitureBaseRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Core.Inventory.Application.UseCases.Inventory.Input.Common;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Command for creating a new furniture base entity.
/// </summary>
public class CreateFurnitureBaseRequest : Notificator, ICommand
{
public string ModelName { get; set; } = null!;
public string Material { get; set; } = null!;
public string Condition { get; set; } = null!;
public string? BaseDescription { get; set; }
public string? Representation { get; set; }
public string? MaintenanceNotes { get; set; }
public Dimensions Dimensions { get; set; } = new();
public List<string>? VariantIds { get; set; }
public bool Validate()
{
return !string.IsNullOrWhiteSpace(ModelName) &&
!string.IsNullOrWhiteSpace(Material) &&
!string.IsNullOrWhiteSpace(Condition);
}
}
}

View File

@ -0,0 +1,37 @@
// ***********************************************************************
// <copyright file="CreateFurnitureVariantRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
public class CreateFurnitureVariantRequest : Notificator, ICommand
{
public string ModelId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Color { get; set; } = null!;
public string? Line { get; set; }
public decimal Price { get; set; }
public string Currency { get; set; } = "USD";
public int Stock { get; set; }
public Guid CategoryId { get; set; }
public Guid ProviderId { get; set; }
public Dictionary<string, object> Attributes { get; set; } = [];
public bool Validate()
{
return !string.IsNullOrWhiteSpace(ModelId)
&& !string.IsNullOrWhiteSpace(Name)
&& !string.IsNullOrWhiteSpace(Color)
&& Price >= 0
&& Stock >= 0
&& CategoryId != Guid.Empty
&& ProviderId != Guid.Empty;
}
}
}

View File

@ -0,0 +1,14 @@
// ***********************************************************************
// <copyright file="GetAllFurnitureBaseRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
public class GetAllFurnitureBaseRequest : ICommand
{
public bool Validate() => true;
}
}

View File

@ -0,0 +1,19 @@
// ***********************************************************************
// <copyright file="GetAllFurnitureVariantsByModelIdRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
public class GetAllFurnitureVariantsByModelIdRequest : Notificator, ICommand
{
public string ModelId { get; set; } = null!;
public bool Validate()
{
return !string.IsNullOrWhiteSpace(ModelId);
}
}
}

View File

@ -0,0 +1,22 @@
// ***********************************************************************
// <copyright file="GetFurnitureBaseByIdRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Query to retrieve a furniture base by its identifier.
/// </summary>
public class GetFurnitureBaseByIdRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id);
}
}
}

View File

@ -0,0 +1,19 @@
// ***********************************************************************
// <copyright file="GetFurnitureVariantByIdRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
public class GetFurnitureVariantByIdRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id);
}
}
}

View File

@ -0,0 +1,35 @@
// ***********************************************************************
// <copyright file="UpdateFurnitureBaseRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Core.Inventory.Application.UseCases.Inventory.Input.Common;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Command for updating an existing furniture base entity.
/// </summary>
public class UpdateFurnitureBaseRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public string ModelName { get; set; } = null!;
public string Material { get; set; } = null!;
public string Condition { get; set; } = null!;
public string? BaseDescription { get; set; }
public string? Representation { get; set; }
public string? MaintenanceNotes { get; set; }
public Dimensions Dimensions { get; set; } = new();
public List<string>? VariantIds { get; set; }
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id)
&& !string.IsNullOrWhiteSpace(ModelName)
&& !string.IsNullOrWhiteSpace(Material)
&& !string.IsNullOrWhiteSpace(Condition);
}
}
}

View File

@ -0,0 +1,41 @@
// ***********************************************************************
// <copyright file="UpdateFurnitureVariantRequest.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Input
{
/// <summary>
/// Command for updating an existing furniture variant.
/// </summary>
public class UpdateFurnitureVariantRequest : Notificator, ICommand
{
public string Id { get; set; } = null!;
public string ModelId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Color { get; set; } = null!;
public string? Line { get; set; }
public int Stock { get; set; }
public decimal Price { get; set; }
public string Currency { get; set; } = "USD";
public Guid CategoryId { get; set; }
public Guid ProviderId { get; set; }
public Dictionary<string, object> Attributes { get; set; } = [];
public bool Validate()
{
return !string.IsNullOrWhiteSpace(Id) &&
!string.IsNullOrWhiteSpace(ModelId) &&
!string.IsNullOrWhiteSpace(Name) &&
!string.IsNullOrWhiteSpace(Color) &&
Price > 0 &&
Stock >= 0;
}
}
}

View File

@ -0,0 +1,324 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Core.Inventory.External.Clients;
using Core.Inventory.External.Clients.Requests;
using FluentValidation;
using Lib.Architecture.BuildingBlocks;
using Lib.Architecture.BuildingBlocks.Helpers;
namespace Core.Inventory.Application.UseCases.Inventory
{
public class InventoryHandler :
// FurnitureBase
IComponentHandler<CreateFurnitureBaseRequest>,
IComponentHandler<UpdateFurnitureBaseRequest>,
IComponentHandler<GetAllFurnitureBaseRequest>,
IComponentHandler<ChangeFurnitureBaseStatusRequest>,
IComponentHandler<GetFurnitureBaseByIdRequest>,
// FurnitureVariant
IComponentHandler<CreateFurnitureVariantRequest>,
IComponentHandler<UpdateFurnitureVariantRequest>,
IComponentHandler<GetFurnitureVariantByIdRequest>,
IComponentHandler<GetAllFurnitureVariantsByModelIdRequest>,
IComponentHandler<ChangeFurnitureVariantStatusRequest>
{
// FurnitureBase
private readonly IFurnitureBasePort _basePort;
private readonly IValidator<CreateFurnitureBaseRequest> _createBaseValidator;
private readonly IValidator<UpdateFurnitureBaseRequest> _updateBaseValidator;
private readonly IValidator<ChangeFurnitureBaseStatusRequest> _changeBaseStatusValidator;
// FurnitureVariant
private readonly IFurnitureVariantPort _variantPort;
private readonly IValidator<CreateFurnitureVariantRequest> _createVariantValidator;
private readonly IValidator<UpdateFurnitureVariantRequest> _updateVariantValidator;
private readonly IValidator<ChangeFurnitureVariantStatusRequest> _changeVariantStatusValidator;
private readonly IInventoryServiceClient _inventoryDALService;
public InventoryHandler(
// FurnitureBase
IFurnitureBasePort basePort,
IValidator<CreateFurnitureBaseRequest> createBaseValidator,
IValidator<UpdateFurnitureBaseRequest> updateBaseValidator,
IValidator<ChangeFurnitureBaseStatusRequest> changeBaseStatusValidator,
// FurnitureVariant
IFurnitureVariantPort variantPort,
IValidator<CreateFurnitureVariantRequest> createVariantValidator,
IValidator<UpdateFurnitureVariantRequest> updateVariantValidator,
IValidator<ChangeFurnitureVariantStatusRequest> changeVariantStatusValidator,
IInventoryServiceClient inventoryDALService
)
{
_basePort= basePort;
_createBaseValidator= createBaseValidator;
_updateBaseValidator= updateBaseValidator;
_changeBaseStatusValidator= changeBaseStatusValidator;
_variantPort= variantPort;
_createVariantValidator= createVariantValidator;
_updateVariantValidator= updateVariantValidator;
_changeVariantStatusValidator= changeVariantStatusValidator;
_inventoryDALService= inventoryDALService;
}
#region FurnitureBase
public async ValueTask ExecuteAsync(CreateFurnitureBaseRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_createBaseValidator))
{
_basePort.ValidationErrors(command.Notifications);
return;
}
var request = new FurnitureBaseRequest
{
BaseDescription = command.BaseDescription,
Condition = command.Condition,
MaintenanceNotes = command.MaintenanceNotes,
Material = command.Material,
ModelName = command.ModelName,
Representation = command.Representation,
VariantIds = command.VariantIds,
Dimensions = new Adapters.Lib.Dimensions
{
Depth = command.Dimensions.Depth,
Height = command.Dimensions.Height,
Width = command.Dimensions.Width
}
};
var result = await _inventoryDALService.CreateFurnitureBaseAsync(request, cancellationToken);
_basePort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _basePort);
}
}
public async ValueTask ExecuteAsync(UpdateFurnitureBaseRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_updateBaseValidator))
{
_basePort.ValidationErrors(command.Notifications);
return;
}
var request = new FurnitureBaseRequest
{
BaseDescription = command.BaseDescription,
Condition = command.Condition,
MaintenanceNotes = command.MaintenanceNotes,
Material = command.Material,
ModelName = command.ModelName,
Representation = command.Representation,
VariantIds = command.VariantIds,
Dimensions = new Adapters.Lib.Dimensions
{
Depth = command.Dimensions.Depth,
Height = command.Dimensions.Height,
Width = command.Dimensions.Width
}
};
var result = await _inventoryDALService.UpdateFurnitureBaseAsync(request, command.Id, cancellationToken);
_basePort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _basePort);
}
}
public async ValueTask ExecuteAsync(GetAllFurnitureBaseRequest command, CancellationToken cancellationToken = default)
{
try
{
var result = await _inventoryDALService.GetAllFurnitureBaseAsync(cancellationToken);
if (!result.Any())
{
_basePort.NoContentSuccess();
return;
}
_basePort.Success([.. result]);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _basePort);
}
}
public async ValueTask ExecuteAsync(GetFurnitureBaseByIdRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
var result = await _inventoryDALService.GetFurnitureBaseByIdAsync(command.Id, cancellationToken);
if (result is null)
{
_basePort.NoContentSuccess();
return;
}
_basePort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _basePort);
}
}
public async ValueTask ExecuteAsync(ChangeFurnitureBaseStatusRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_changeBaseStatusValidator))
{
_basePort.ValidationErrors(command.Notifications);
return;
}
var result = await _inventoryDALService.ChangeFurnitureBaseStatusAsync(command.Id, command.Status, cancellationToken);
_basePort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _basePort);
}
}
#endregion
#region FurnitureVariant
public async ValueTask ExecuteAsync(CreateFurnitureVariantRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_createVariantValidator))
{
_variantPort.ValidationErrors(command.Notifications);
return;
}
var request = new FurnitureVariantRequest
{
Stock = command.Stock,
Attributes= command.Attributes,
CategoryId = command.CategoryId,
Color = command.Color,
Currency = command.Currency,
Line = command.Line,
ModelId = command.ModelId,
Name = command.Name,
Price = command.Price,
ProviderId = command.ProviderId
};
var result = await _inventoryDALService.CreateFurnitureVariantAsync(request, cancellationToken);
_variantPort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _variantPort);
}
}
public async ValueTask ExecuteAsync(UpdateFurnitureVariantRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_updateVariantValidator))
{
_variantPort.ValidationErrors(command.Notifications);
return;
}
var request = new FurnitureVariantRequest
{
Stock = command.Stock,
Attributes= command.Attributes,
CategoryId = command.CategoryId,
Color = command.Color,
Currency = command.Currency,
Line = command.Line,
ModelId = command.ModelId,
Name = command.Name,
Price = command.Price,
ProviderId = command.ProviderId
};
var result = await _inventoryDALService.UpdateFurnitureVariantAsync(request, command.Id, cancellationToken);
_variantPort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _variantPort);
}
}
public async ValueTask ExecuteAsync(GetFurnitureVariantByIdRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
var result = await _inventoryDALService.GetFurnitureVariantByIdAsync(command.Id, cancellationToken);
if (result is null)
{
_variantPort.NoContentSuccess();
return;
}
_variantPort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _variantPort);
}
}
public async ValueTask ExecuteAsync(GetAllFurnitureVariantsByModelIdRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
var result = await _inventoryDALService.GetAllVariantsByModelIdAsync(command.ModelId, cancellationToken);
if (!result.Any())
{
_variantPort.NoContentSuccess();
return;
}
_variantPort.Success([.. result]);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _variantPort);
}
}
public async ValueTask ExecuteAsync(ChangeFurnitureVariantStatusRequest command, CancellationToken cancellationToken = default)
{
try
{
ArgumentNullException.ThrowIfNull(command);
if (!command.IsValid(_changeVariantStatusValidator))
{
_variantPort.ValidationErrors(command.Notifications);
return;
}
var result = await _inventoryDALService.ChangeFurnitureVariantStatusAsync(command.Id, command.Status, cancellationToken);
_variantPort.Success(result);
}
catch (Exception ex)
{
ApiResponseHelper.EvaluatePort(ex, _variantPort);
}
}
#endregion
}
}

View File

@ -0,0 +1,22 @@
// ***********************************************************************
// <copyright file="IFurnitureBasePort.cs">
// Core.Inventory.Application
// </copyright>
// ***********************************************************************
using Core.Adapters.Lib;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Ports
{
/// <summary>
/// Output port interface for handling results from FurnitureBase use cases.
/// </summary>
public interface IFurnitureBasePort : IBasePort,
ICommandSuccessPort<FurnitureBase>,
ICommandSuccessPort<List<FurnitureBase>>,
INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort,
INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort,
IBadRequestPort
{
}
}

View File

@ -0,0 +1,22 @@
// ***********************************************************************
// <copyright file="IFurnitureVariantPort.cs">
// Core.Inventory.Application
// </copyright>
// ***********************************************************************
using Core.Adapters.Lib;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Application.UseCases.Inventory.Ports
{
/// <summary>
/// Output port interface for handling results from FurnitureVariant use cases.
/// </summary>
public interface IFurnitureVariantPort : IBasePort,
ICommandSuccessPort<FurnitureVariant>,
ICommandSuccessPort<List<FurnitureVariant>>,
INoContentPort, IBusinessErrorPort, ITimeoutPort, IValidationErrorPort,
INotFoundPort, IForbiddenPort, IUnauthorizedPort, IInternalServerErrorPort,
IBadRequestPort
{
}
}

View File

@ -0,0 +1,17 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class ChangeFurnitureBaseStatusValidator : AbstractValidator<ChangeFurnitureBaseStatusRequest>
{
public ChangeFurnitureBaseStatusValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.");
RuleFor(x => x.Status)
.IsInEnum().WithMessage("Invalid status value.");
}
}
}

View File

@ -0,0 +1,17 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class ChangeFurnitureVariantStatusValidator : AbstractValidator<ChangeFurnitureVariantStatusRequest>
{
public ChangeFurnitureVariantStatusValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.");
RuleFor(x => x.Status)
.IsInEnum().WithMessage("Invalid status value.");
}
}
}

View File

@ -0,0 +1,20 @@
using Core.Inventory.Application.UseCases.Inventory.Input.Common;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator.Common
{
public class DimensionsValidator : AbstractValidator<Dimensions>
{
public DimensionsValidator()
{
RuleFor(x => x.Width)
.GreaterThan(0).WithMessage("Width must be greater than 0.");
RuleFor(x => x.Height)
.GreaterThan(0).WithMessage("Height must be greater than 0.");
RuleFor(x => x.Depth)
.GreaterThan(0).WithMessage("Depth must be greater than 0.");
}
}
}

View File

@ -0,0 +1,29 @@
// ***********************************************************************
// <copyright file="CreateFurnitureBaseValidator.cs">
// Core.Inventory
// </copyright>
// ***********************************************************************
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Validator.Common;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class CreateFurnitureBaseValidator : AbstractValidator<CreateFurnitureBaseRequest>
{
public CreateFurnitureBaseValidator()
{
RuleFor(x => x.ModelName)
.NotEmpty().WithMessage("Model name is required.");
RuleFor(x => x.Material)
.NotEmpty().WithMessage("Material is required.");
RuleFor(x => x.Condition)
.NotEmpty().WithMessage("Condition is required.");
RuleFor(x => x.Dimensions)
.SetValidator(new DimensionsValidator());
}
}
}

View File

@ -0,0 +1,35 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class CreateFurnitureVariantValidator : AbstractValidator<CreateFurnitureVariantRequest>
{
public CreateFurnitureVariantValidator()
{
RuleFor(x => x.ModelId)
.NotEmpty().WithMessage("ModelId is required.");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.");
RuleFor(x => x.Color)
.NotEmpty().WithMessage("Color is required.");
RuleFor(x => x.Price)
.GreaterThanOrEqualTo(0).WithMessage("Price must be a non-negative value.");
RuleFor(x => x.Stock)
.GreaterThanOrEqualTo(0).WithMessage("Stock must be a non-negative value.");
RuleFor(x => x.CategoryId)
.NotEqual(Guid.Empty).WithMessage("CategoryId is required.");
RuleFor(x => x.ProviderId)
.NotEqual(Guid.Empty).WithMessage("ProviderId is required.");
RuleFor(x => x.Currency)
.NotEmpty().WithMessage("Currency is required.");
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class GetAllFurnitureVariantsByModelIdValidator : AbstractValidator<GetAllFurnitureVariantsByModelIdRequest>
{
public GetAllFurnitureVariantsByModelIdValidator()
{
RuleFor(x => x.ModelId)
.NotEmpty().WithMessage("ModelId is required.");
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class GetFurnitureBaseByIdValidator : AbstractValidator<GetFurnitureBaseByIdRequest>
{
public GetFurnitureBaseByIdValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.");
}
}
}

View File

@ -0,0 +1,14 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class GetFurnitureVariantByIdValidator : AbstractValidator<GetFurnitureVariantByIdRequest>
{
public GetFurnitureVariantByIdValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.");
}
}
}

View File

@ -0,0 +1,24 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Validator.Common;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class UpdateFurnitureBaseValidator : AbstractValidator<UpdateFurnitureBaseRequest>
{
public UpdateFurnitureBaseValidator()
{
RuleFor(x => x.ModelName)
.NotEmpty().WithMessage("Model name is required.");
RuleFor(x => x.Material)
.NotEmpty().WithMessage("Material is required.");
RuleFor(x => x.Condition)
.NotEmpty().WithMessage("Condition is required.");
RuleFor(x => x.Dimensions)
.SetValidator(new DimensionsValidator());
}
}
}

View File

@ -0,0 +1,38 @@
using Core.Inventory.Application.UseCases.Inventory.Input;
using FluentValidation;
namespace Core.Inventory.Application.UseCases.Inventory.Validator
{
public class UpdateFurnitureVariantValidator : AbstractValidator<UpdateFurnitureVariantRequest>
{
public UpdateFurnitureVariantValidator()
{
RuleFor(x => x.Id)
.NotEmpty().WithMessage("Id is required.");
RuleFor(x => x.ModelId)
.NotEmpty().WithMessage("ModelId is required.");
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Name is required.");
RuleFor(x => x.Color)
.NotEmpty().WithMessage("Color is required.");
RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0.");
RuleFor(x => x.Stock)
.GreaterThanOrEqualTo(0).WithMessage("Stock must be a non-negative value.");
RuleFor(x => x.Currency)
.NotEmpty().WithMessage("Currency is required.");
RuleFor(x => x.CategoryId)
.NotEqual(Guid.Empty).WithMessage("CategoryId is required.");
RuleFor(x => x.ProviderId)
.NotEqual(Guid.Empty).WithMessage("ProviderId is required.");
}
}
}

View File

@ -0,0 +1,58 @@
using Core.Inventory.External.Clients;
using Core.Inventory.External.GatewayConfigurations;
using Core.Inventory.External.Helpers;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Refit;
namespace Core.Inventory.External.ClientConfiguration
{
public static class RegisterClientConfiguration
{
public static IServiceCollection RegisterExternalLayer(this IServiceCollection services, IConfiguration configuration)
{
var gatewayConfiguration = new GatewayConfiguration();
var gatewaySettingsConfiguration = new GatewaySettingsConfigurations(configuration);
// Register GatewayConfiguration as a singleton
services.AddSingleton(gatewayConfiguration);
// Register IHttpContextAccessor
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Register ITokenProvider
services.AddSingleton<ITokenProvider, HttpContextTokenProvider>();
// Register the custom AuthenticatedHttpClientHandler
services.AddTransient(provider =>
{
var tokenProvider = provider.GetRequiredService<ITokenProvider>();
var handler = new AuthenticatedHttpClientHandler(tokenProvider)
{
InnerHandler = new HttpClientHandler()
};
return handler;
});
var inventoryServiceApiUrl = GatewaySettingsConfigurations.GetInventoryServiceAPIEndpoint().Endpoint.Url;
// Register IInventoryServiceClient with the manually created HttpClient
services.AddScoped<IInventoryServiceClient>(provider =>
{
var handler = provider.GetRequiredService<AuthenticatedHttpClientHandler>();
var httpClient = new HttpClient(handler)
{
BaseAddress = new Uri(inventoryServiceApiUrl),
Timeout = TimeSpan.FromMinutes(1)
};
return RestService.For<IInventoryServiceClient>(httpClient);
});
services.AddScoped<IAuthenticationService, AuthenticationService>();
return services;
}
}
}

View File

@ -0,0 +1,49 @@
using Core.Adapters.Lib;
using Core.Blueprint.Mongo;
using Core.Inventory.External.Clients.Requests;
using Microsoft.AspNetCore.Mvc;
using Refit;
namespace Core.Inventory.External.Clients
{
public interface IInventoryServiceClient
{
#region FurnitureBase
[Get("/api/v1/FurnitureBase")]
Task<IEnumerable<FurnitureBase>> GetAllFurnitureBaseAsync(CancellationToken cancellationToken = default);
[Get("/api/v1/FurnitureBase/{id}")]
Task<FurnitureBase> GetFurnitureBaseByIdAsync([FromRoute] string id, CancellationToken cancellationToken = default);
[Post("/api/v1/FurnitureBase")]
Task<FurnitureBase> CreateFurnitureBaseAsync([FromBody] FurnitureBaseRequest request, CancellationToken cancellationToken = default);
[Put("/api/v1/FurnitureBase/{id}")]
Task<FurnitureBase> UpdateFurnitureBaseAsync([FromBody] FurnitureBaseRequest request, [FromRoute] string id, CancellationToken cancellationToken = default);
[Patch("/api/v1/FurnitureBase/{id}/{newStatus}/ChangeStatus")]
Task<FurnitureBase> ChangeFurnitureBaseStatusAsync([FromRoute] string id, [FromRoute] StatusEnum newStatus, CancellationToken cancellationToken = default);
#endregion
#region FurnitureVariant
[Get("/api/v1/FurnitureVariant/{modelId}")]
Task<IEnumerable<FurnitureVariant>> GetAllVariantsByModelIdAsync([FromRoute] string modelId, CancellationToken cancellationToken = default);
[Get("/api/v1/FurnitureVariant/{id}/byId")]
Task<FurnitureVariant> GetFurnitureVariantByIdAsync([FromRoute] string id, CancellationToken cancellationToken = default);
[Post("/api/v1/FurnitureVariant")]
Task<FurnitureVariant> CreateFurnitureVariantAsync([FromBody] FurnitureVariantRequest request, CancellationToken cancellationToken = default);
[Put("/api/v1/FurnitureVariant/{id}")]
Task<FurnitureVariant> UpdateFurnitureVariantAsync([FromBody] FurnitureVariantRequest request, [FromRoute] string id, CancellationToken cancellationToken = default);
[Patch("/api/v1/FurnitureVariant/{id}/{newStatus}/ChangeStatus")]
Task<FurnitureVariant> ChangeFurnitureVariantStatusAsync([FromRoute] string id, [FromRoute] StatusEnum newStatus, CancellationToken cancellationToken = default);
#endregion
}
}

View File

@ -0,0 +1,17 @@
using Core.Adapters.Lib;
namespace Core.Inventory.External.Clients.Requests
{
public class FurnitureBaseRequest
{
public string ModelName { get; set; } = null!;
public string Material { get; set; } = null!;
public string Condition { get; set; } = null!;
public string? BaseDescription { get; set; }
public string? Representation { get; set; }
public string? MaintenanceNotes { get; set; }
public Dimensions Dimensions { get; set; } = new();
public List<string>? VariantIds { get; set; }
}
}

View File

@ -0,0 +1,19 @@
namespace Core.Inventory.External.Clients.Requests
{
public class FurnitureVariantRequest
{
public string ModelId { get; set; } = null!;
public string Name { get; set; } = null!;
public string Color { get; set; } = null!;
public string? Line { get; set; }
public decimal Price { get; set; }
public string Currency { get; set; } = "USD";
public int Stock { get; set; }
public Guid CategoryId { get; set; }
public Guid ProviderId { get; set; }
public Dictionary<string, object> Attributes { get; set; } = [];
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Adapters.Lib" Version="1.0.3" />
<PackageReference Include="BuildingBlocks.Library" Version="1.0.0" />
<PackageReference Include="Refit" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
using Core.Blueprint.External;
namespace Core.Inventory.External.GatewayConfigurations
{
public record GatewayConfiguration
{
public GatewayConfiguration()
{
InventoryService = new InventoryServiceAPI();
}
public InventoryServiceAPI InventoryService { get; set; }
}
public record InventoryServiceAPI
{
public string Channel { get; set; }
public BaseEndpoint Endpoint { get; set; }
}
}

View File

@ -0,0 +1,52 @@
using Core.Blueprint.External;
using Microsoft.Extensions.Configuration;
namespace Core.Inventory.External.GatewayConfigurations
{
public class GatewaySettingsConfigurations
{
private static GatewayConfiguration GatewayConfigurations { get; set; } = new GatewayConfiguration();
private readonly IConfiguration _configuration;
public GatewaySettingsConfigurations(IConfiguration configuration)
{
_configuration = configuration;
this.SetInventoryServiceAPIEndpoint();
}
public static InventoryServiceAPI GetInventoryServiceAPIEndpoint()
{
return GatewayConfigurations.InventoryService;
}
private GatewayConfiguration SetInventoryServiceAPIEndpoint()
{
IConfigurationSection source;
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
if (environment == "Local")
source = _configuration.GetSection("LocalGateways");
else
source = _configuration.GetSection("Gateways");
var endpoint = source["InventoryDAL"] ?? string.Empty;
if (string.IsNullOrEmpty(endpoint))
throw new Exception("Inventory DAL endpoint is empty or null");
GatewayConfigurations.InventoryService = new InventoryServiceAPI()
{
Endpoint = new BaseEndpoint()
{
Uri = new Uri(endpoint),
Url = endpoint,
Token = string.Empty,
APIName = "Inventory Service"
}
};
return GatewayConfigurations;
}
}
}

View File

@ -0,0 +1,26 @@
namespace Core.Inventory.External.Helpers
{
/// <summary>
/// Class to inject the token in all requests.
/// </summary>
public class AuthenticatedHttpClientHandler : DelegatingHandler
{
private readonly ITokenProvider _tokenProvider;
public AuthenticatedHttpClientHandler(ITokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = _tokenProvider.GetToken();
if (!string.IsNullOrEmpty(token))
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Http;
namespace Core.Inventory.External.Helpers
{
/// <summary>
/// Class to return the access token to controllers.
/// </summary>
public class HttpContextTokenProvider : ITokenProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextTokenProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Get token from headers.
/// </summary>
public string GetToken()
{
return _httpContextAccessor.HttpContext?.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
}
}
}

View File

@ -0,0 +1,13 @@
namespace Core.Inventory.External.Helpers
{
/// <summary>
/// Interface for token provider.
/// </summary>
public interface ITokenProvider
{
/// <summary>
/// Get token from headers.
/// </summary>
string GetToken();
}
}

View File

@ -0,0 +1,77 @@
using Asp.Versioning;
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Inventory.Service.API.Controllers
{
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class FurnitureBaseController : ControllerBase
{
private readonly IComponentHandler<GetFurnitureBaseByIdRequest> _getByIdHandler;
private readonly IComponentHandler<GetAllFurnitureBaseRequest> _getAllHandler;
private readonly IComponentHandler<CreateFurnitureBaseRequest> _createHandler;
private readonly IComponentHandler<UpdateFurnitureBaseRequest> _updateHandler;
private readonly IComponentHandler<ChangeFurnitureBaseStatusRequest> _changeStatusHandler;
private readonly IFurnitureBasePort _port;
public FurnitureBaseController(
IComponentHandler<GetFurnitureBaseByIdRequest> getByIdHandler,
IComponentHandler<GetAllFurnitureBaseRequest> getAllHandler,
IComponentHandler<CreateFurnitureBaseRequest> createHandler,
IComponentHandler<UpdateFurnitureBaseRequest> updateHandler,
IComponentHandler<ChangeFurnitureBaseStatusRequest> changeStatusHandler,
IFurnitureBasePort port)
{
_getByIdHandler= getByIdHandler;
_getAllHandler= getAllHandler;
_createHandler= createHandler;
_updateHandler= updateHandler;
_changeStatusHandler= changeStatusHandler;
_port= port;
}
[HttpGet("GetAll")]
public async Task<IActionResult> GetAllAsync(CancellationToken cancellationToken)
{
await _getAllHandler.ExecuteAsync(new GetAllFurnitureBaseRequest { }, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPost("GetById")]
public async Task<IActionResult> GetByIdAsync([FromBody] GetFurnitureBaseByIdRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request?.Id)) return BadRequest("Furniture base identifier is required");
await _getByIdHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPost("Create")]
public async Task<IActionResult> CreateAsync([FromBody] CreateFurnitureBaseRequest request, CancellationToken cancellationToken)
{
await _createHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPut("Update")]
public async Task<IActionResult> UpdateAsync([FromBody] UpdateFurnitureBaseRequest request, CancellationToken cancellationToken)
{
await _updateHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPatch("ChangeStatus")]
public async Task<IActionResult> ChangeStatusAsync([FromBody] ChangeFurnitureBaseStatusRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request?.Id)) return BadRequest("Furniture base identifier is required");
await _changeStatusHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
}
}

View File

@ -0,0 +1,70 @@
using Asp.Versioning;
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Lib.Architecture.BuildingBlocks;
using Microsoft.AspNetCore.Mvc;
namespace Core.Inventory.Service.API.Controllers
{
[ApiVersion("1.0")]
[Route("api/v{api-version:apiVersion}/[controller]")]
[Produces("application/json")]
[ApiController]
public class FurnitureVariantController(
IComponentHandler<GetFurnitureVariantByIdRequest> getByIdHandler,
IComponentHandler<GetAllFurnitureVariantsByModelIdRequest> getAllHandler,
IComponentHandler<CreateFurnitureVariantRequest> createHandler,
IComponentHandler<UpdateFurnitureVariantRequest> updateHandler,
IComponentHandler<ChangeFurnitureVariantStatusRequest> changeStatusHandler,
IFurnitureVariantPort port) : ControllerBase
{
private readonly IComponentHandler<GetFurnitureVariantByIdRequest> _getByIdHandler = getByIdHandler;
private readonly IComponentHandler<GetAllFurnitureVariantsByModelIdRequest> _getAllHandler = getAllHandler;
private readonly IComponentHandler<CreateFurnitureVariantRequest> _createHandler = createHandler;
private readonly IComponentHandler<UpdateFurnitureVariantRequest> _updateHandler = updateHandler;
private readonly IComponentHandler<ChangeFurnitureVariantStatusRequest> _changeStatusHandler = changeStatusHandler;
private readonly IFurnitureVariantPort _port = port;
[HttpGet("GetAllByModelId")]
public async Task<IActionResult> GetAllAsync([FromQuery] string modelId, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(modelId)) return BadRequest("Model ID is required");
var request = new GetAllFurnitureVariantsByModelIdRequest { ModelId = modelId };
await _getAllHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPost("GetById")]
public async Task<IActionResult> GetByIdAsync([FromBody] GetFurnitureVariantByIdRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request?.Id)) return BadRequest("Furniture variant identifier is required");
await _getByIdHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPost("Create")]
public async Task<IActionResult> CreateAsync([FromBody] CreateFurnitureVariantRequest request, CancellationToken cancellationToken)
{
await _createHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPut("Update")]
public async Task<IActionResult> UpdateAsync([FromBody] UpdateFurnitureVariantRequest request, CancellationToken cancellationToken)
{
await _updateHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
[HttpPatch("ChangeStatus")]
public async Task<IActionResult> ChangeStatusAsync([FromBody] ChangeFurnitureVariantStatusRequest request, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(request?.Id)) return BadRequest("Furniture variant identifier is required");
await _changeStatusHandler.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
return _port.ViewModel;
}
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageReference Include="Core.Blueprint.Logging" Version="1.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core.Inventory.Application\Core.Inventory.Application.csproj" />
<ProjectReference Include="..\Core.Inventory.External\Core.Inventory.External.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@Core.Inventory.Service.API_HostAddress = http://localhost:5257
GET {{Core.Inventory.Service.API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,65 @@
using Core.Inventory.Application.UseCases.Inventory;
using Core.Inventory.Application.UseCases.Inventory.Adapter;
using Core.Inventory.Application.UseCases.Inventory.Input;
using Core.Inventory.Application.UseCases.Inventory.Ports;
using Core.Inventory.Application.UseCases.Inventory.Validator;
using FluentValidation;
using Lib.Architecture.BuildingBlocks;
namespace Core.Inventory.Service.API.Extensions
{
public static class ServiceCollectionExtension
{
public static IServiceCollection AddServiceConfigurationLayer(this IServiceCollection services)
{
#region FurnitureBase
services.AddScoped<IFurnitureBasePort, FurnitureBasePort>();
services.AddScoped<IComponentHandler<GetAllFurnitureBaseRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<GetFurnitureBaseByIdRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<CreateFurnitureBaseRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<UpdateFurnitureBaseRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<ChangeFurnitureBaseStatusRequest>, InventoryHandler>();
services.AddValidatorsFromAssemblyContaining<CreateFurnitureBaseValidator>();
services.AddScoped<IValidator<CreateFurnitureBaseRequest>, CreateFurnitureBaseValidator>();
services.AddValidatorsFromAssemblyContaining<UpdateFurnitureBaseValidator>();
services.AddScoped<IValidator<UpdateFurnitureBaseRequest>, UpdateFurnitureBaseValidator>();
services.AddValidatorsFromAssemblyContaining<ChangeFurnitureBaseStatusValidator>();
services.AddScoped<IValidator<ChangeFurnitureBaseStatusRequest>, ChangeFurnitureBaseStatusValidator>();
services.AddValidatorsFromAssemblyContaining<GetFurnitureBaseByIdValidator>();
services.AddScoped<IValidator<GetFurnitureBaseByIdRequest>, GetFurnitureBaseByIdValidator>();
#endregion
#region FurnitureVariant
services.AddScoped<IFurnitureVariantPort, FurnitureVariantPort>();
services.AddScoped<IComponentHandler<GetAllFurnitureVariantsByModelIdRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<GetFurnitureVariantByIdRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<CreateFurnitureVariantRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<UpdateFurnitureVariantRequest>, InventoryHandler>();
services.AddScoped<IComponentHandler<ChangeFurnitureVariantStatusRequest>, InventoryHandler>();
services.AddValidatorsFromAssemblyContaining<CreateFurnitureVariantValidator>();
services.AddScoped<IValidator<CreateFurnitureVariantRequest>, CreateFurnitureVariantValidator>();
services.AddValidatorsFromAssemblyContaining<UpdateFurnitureVariantValidator>();
services.AddScoped<IValidator<UpdateFurnitureVariantRequest>, UpdateFurnitureVariantValidator>();
services.AddValidatorsFromAssemblyContaining<ChangeFurnitureVariantStatusValidator>();
services.AddScoped<IValidator<ChangeFurnitureVariantStatusRequest>, ChangeFurnitureVariantStatusValidator>();
services.AddValidatorsFromAssemblyContaining<GetFurnitureVariantByIdValidator>();
services.AddScoped<IValidator<GetFurnitureVariantByIdRequest>, GetFurnitureVariantByIdValidator>();
services.AddValidatorsFromAssemblyContaining<GetAllFurnitureVariantsByModelIdValidator>();
services.AddScoped<IValidator<GetAllFurnitureVariantsByModelIdRequest>, GetAllFurnitureVariantsByModelIdValidator>();
#endregion
return services;
}
}
}

View File

@ -0,0 +1,98 @@
using Asp.Versioning.ApiExplorer;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Swashbuckle.AspNetCore.SwaggerUI;
namespace Core.Inventory.Service.API.Extensions
{
public static class SwaggerExtensions
{
public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddEndpointsApiExplorer();
AddSwaggerGen(services, configuration);
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
}
/// <summary>
/// Configures Swagger generation with OAuth2 security and XML comments.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configuration">The <see cref="IConfiguration"/> containing Swagger and OAuth2 configuration settings.</param>
public static void AddSwaggerGen(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
}
public static void ConfigureSwagger(this WebApplication app)
{
//Swagger Stuff Goes Here
app.UseSwagger();
app.UseSwaggerUI(options =>
{
foreach (var version in app.DescribeApiVersions().Select(version => version.GroupName))
options.SwaggerEndpoint($"/swagger/{version}/swagger.json", version);
options.DisplayRequestDuration();
options.EnableTryItOutByDefault();
options.DocExpansion(DocExpansion.None);
});
}
public static IServiceCollection AddVersioning(this IServiceCollection services)
{
services.AddApiVersioning(options => options.ReportApiVersions = true)
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
return services;
}
}
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider _provider = provider;
public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
options.SwaggerDoc(description.GroupName, new()
{
Title = AppDomain.CurrentDomain.FriendlyName,
Version = description.ApiVersion.ToString()
});
options.CustomSchemaIds(type => type.ToString().Replace("+", "."));
}
}
}

View File

@ -0,0 +1,80 @@
using Core.Blueprint.Logging.Configuration;
using Core.Inventory.External.ClientConfiguration;
using Core.Inventory.Service.API.Extensions;
using Microsoft.AspNetCore.HttpLogging;
using System.Reflection;
using System.Text.Json.Serialization;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogs(builder);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Configuration
.AddUserSecrets(Assembly.GetExecutingAssembly())
.AddEnvironmentVariables();
builder.Services.RegisterExternalLayer(builder.Configuration);
builder.Services.AddServiceConfigurationLayer();
builder.Services.AddResponseCompression();
builder.Services.AddProblemDetails();
builder.Services.AddMemoryCache();
builder.Host.ConfigureServices((context, services) =>
{
services.AddLogging();
services.AddControllers();
services.AddProblemDetails();
services.AddCors(options
=> options.AddDefaultPolicy(policyBuilder
=> policyBuilder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()));
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services
.AddEndpointsApiExplorer()
.AddVersioning()
.AddSwagger(builder.Configuration);
services.AddHealthChecks();
services.AddHttpLogging(options => options.LoggingFields = HttpLoggingFields.All);
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder =>
builder.Expire(TimeSpan.FromSeconds(10)));
options.AddPolicy("Expire20", builder =>
builder.Expire(TimeSpan.FromSeconds(20)));
options.AddPolicy("Expire30", builder =>
builder.Expire(TimeSpan.FromSeconds(30)));
});
});
var app = builder.Build();
app.UseRouting();
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseCors();
app.ConfigureSwagger();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseResponseCompression();
app.UseOutputCache();
app.UseResponseCaching();
app.UseLogging(builder.Configuration);
app.MapHealthChecks("/health");
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58212",
"sslPort": 44339
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5257",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Local"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7028;http://localhost:5257",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Local"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Local"
}
}
}
}

View File

@ -0,0 +1,13 @@
namespace Core.Inventory.Service.API
{
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"LocalGateways": {
"InventoryDAL": "https://localhost:7037"
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,46 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36212.18 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Inventory.Service.API", "Core.Inventory.Service.API\Core.Inventory.Service.API.csproj", "{877AA831-C2E1-4926-AB6B-340ACCD88474}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{FAA84161-ED65-46CF-AC03-998CBDC8FCD3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Inventory.Application", "Core.Inventory.Application\Core.Inventory.Application.csproj", "{BA083BDD-F32A-4040-88F9-DC2DE767FB7A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Inventory.External", "Core.Inventory.External\Core.Inventory.External.csproj", "{C2C165F5-7787-4EE7-A335-31B59673BFCB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{877AA831-C2E1-4926-AB6B-340ACCD88474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{877AA831-C2E1-4926-AB6B-340ACCD88474}.Debug|Any CPU.Build.0 = Debug|Any CPU
{877AA831-C2E1-4926-AB6B-340ACCD88474}.Release|Any CPU.ActiveCfg = Release|Any CPU
{877AA831-C2E1-4926-AB6B-340ACCD88474}.Release|Any CPU.Build.0 = Release|Any CPU
{BA083BDD-F32A-4040-88F9-DC2DE767FB7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA083BDD-F32A-4040-88F9-DC2DE767FB7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA083BDD-F32A-4040-88F9-DC2DE767FB7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA083BDD-F32A-4040-88F9-DC2DE767FB7A}.Release|Any CPU.Build.0 = Release|Any CPU
{C2C165F5-7787-4EE7-A335-31B59673BFCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C2C165F5-7787-4EE7-A335-31B59673BFCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2C165F5-7787-4EE7-A335-31B59673BFCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2C165F5-7787-4EE7-A335-31B59673BFCB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{877AA831-C2E1-4926-AB6B-340ACCD88474} = {FAA84161-ED65-46CF-AC03-998CBDC8FCD3}
{BA083BDD-F32A-4040-88F9-DC2DE767FB7A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{C2C165F5-7787-4EE7-A335-31B59673BFCB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E943164B-39DF-481C-8CF1-A2B1D97F90BA}
EndGlobalSection
EndGlobal