diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..72bdaa25
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# Remove unused using directives
+dotnet_diagnostic.CS8019.severity = error
+
+# Unused variables
+dotnet_diagnostic.IDE0059.severity = error
+
+# Unused expressions (e.g., method call with no effect)
+dotnet_diagnostic.IDE0058.severity = error
+
+# Parameter is never used
+dotnet_diagnostic.IDE0060.severity = warning
\ No newline at end of file
diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index 1a45a91c..b1963adf 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 5.0.202
+ dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..ade64d94
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,61 @@
+# AGENTS Setup Guide
+
+This repository requires specific environment configuration to build and run .NET 9.0 projects. **All dependencies must be installed during the setup script phase,** especially in restricted environments (e.g., OpenAI Codex).
+
+## Setup Steps (`setup.sh`)
+
+1. **Update and upgrade the system:**
+
+ `apt-get update && apt-get upgrade -y`
+
+2. **Install essential dependencies (`wget`, `apt-transport-https`, `ca-certificates`):**
+
+ `apt-get install -y wget apt-transport-https ca-certificates`
+
+3. **Configure Microsoft package source and update package lists:**
+
+ `wget https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb -O packages-microsoft-prod.deb`
+
+ `dpkg -i packages-microsoft-prod.deb`
+
+ `rm packages-microsoft-prod.deb`
+
+ `apt-get update`
+
+4. **Install .NET SDK 9.0:**
+
+ `apt-get install -y dotnet-sdk-9.0`
+
+5. **Verify installation:**
+
+ `dotnet --version`
+
+6. **(Optional) Remove obsolete packages:**
+
+ `apt-get autoremove -y`
+
+
+---
+
+## ⚠️ Notes for Codex and Similar Cloud Environments
+
+- **Internet access is only available during the setup script phase.** All dependencies must be installed in this initial script.
+- **A network proxy is always active:** Standard environment variables like `http_proxy`, `https_proxy`, and the proxy certificate (`$CODEX_PROXY_CERT`) are automatically set. These should be respected by all package managers and CLI tools.
+- **If you encounter connectivity issues:**
+ - Ensure your setup script is running during the setup/initialization phase (not in the regular terminal).
+ - Check that all required dependencies are installed up front.
+ - Make sure environment variables for the proxy/certificate are being used by your tools.
+
+---
+
+## General Recommendations
+
+- Use the latest version of **Visual Studio** or **Visual Studio Code** for development.
+- Always install the correct SDK and runtime versions as required by this project.
+- The project can be built and run in Visual Studio Code on Windows, Linux, or macOS.
+
+---
+
+**IMPORTANT:**
+
+If using a cloud development platform or automated environment, ensure all tools and dependencies are installed during the setup script phase. No additional internet access will be available after environment initialization. Plan accordingly.
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
deleted file mode 100644
index b1535c81..00000000
--- a/CODE_OF_CONDUCT.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
-
-## Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
-
-## Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at falecom@eduardopires.net.br. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see
-https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index c6112993..00000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1 +0,0 @@
-If want contribute start opening an issue to discuss first
diff --git a/Equinox.sln b/Equinox.sln
index 91228308..bc6aaf89 100644
--- a/Equinox.sln
+++ b/Equinox.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31025.218
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34622.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1 - Presentation", "1 - Presentation", "{BA4C44CC-65BC-4CE0-9B44-68BA231FAC73}"
EndProject
@@ -17,23 +17,27 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.1 - Data", "5.1 - Data",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.2 - CrossCutting", "5.2 - CrossCutting", "{DF6C4BDE-F3C5-4E53-A5D5-9D27B2D3E38F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.UI.Web", "src\Equinox.UI.Web\Equinox.UI.Web.csproj", "{490517BA-F3C3-44B4-82F8-1E3A4ED6777A}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.UI.Web", "src\Equinox.UI.Web\Equinox.UI.Web.csproj", "{490517BA-F3C3-44B4-82F8-1E3A4ED6777A}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Services.Api", "src\Equinox.Services.Api\Equinox.Services.Api.csproj", "{EA966EC3-85A6-4B57-82C1-5120E3390243}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Services.Api", "src\Equinox.Services.Api\Equinox.Services.Api.csproj", "{EA966EC3-85A6-4B57-82C1-5120E3390243}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Application", "src\Equinox.Application\Equinox.Application.csproj", "{851E7338-2397-4E8D-8199-FD1EAD109418}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Application", "src\Equinox.Application\Equinox.Application.csproj", "{851E7338-2397-4E8D-8199-FD1EAD109418}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Domain", "src\Equinox.Domain\Equinox.Domain.csproj", "{BF28C988-9C1B-41F5-BD58-D2FCAD7E80BA}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Domain", "src\Equinox.Domain\Equinox.Domain.csproj", "{BF28C988-9C1B-41F5-BD58-D2FCAD7E80BA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Domain.Core", "src\Equinox.Domain.Core\Equinox.Domain.Core.csproj", "{9CC884B7-FA70-49E1-92A6-2B566E00FAB9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Domain.Core", "src\Equinox.Domain.Core\Equinox.Domain.Core.csproj", "{9CC884B7-FA70-49E1-92A6-2B566E00FAB9}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Infra.Data", "src\Equinox.Infra.Data\Equinox.Infra.Data.csproj", "{F0DDF87D-98A4-4237-91C9-FD865ED78ABB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Infra.Data", "src\Equinox.Infra.Data\Equinox.Infra.Data.csproj", "{F0DDF87D-98A4-4237-91C9-FD865ED78ABB}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Infra.CrossCutting.Bus", "src\Equinox.Infra.CrossCutting.Bus\Equinox.Infra.CrossCutting.Bus.csproj", "{91F0D76D-2BEA-43D2-B123-DC29F2E87792}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Infra.CrossCutting.Bus", "src\Equinox.Infra.CrossCutting.Bus\Equinox.Infra.CrossCutting.Bus.csproj", "{91F0D76D-2BEA-43D2-B123-DC29F2E87792}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Infra.CrossCutting.Identity", "src\Equinox.Infra.CrossCutting.Identity\Equinox.Infra.CrossCutting.Identity.csproj", "{788030D0-561B-4136-96B4-D5ABFB5CFD07}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Infra.CrossCutting.Identity", "src\Equinox.Infra.CrossCutting.Identity\Equinox.Infra.CrossCutting.Identity.csproj", "{788030D0-561B-4136-96B4-D5ABFB5CFD07}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Infra.CrossCutting.IoC", "src\Equinox.Infra.CrossCutting.IoC\Equinox.Infra.CrossCutting.IoC.csproj", "{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Equinox.Infra.CrossCutting.IoC", "src\Equinox.Infra.CrossCutting.IoC\Equinox.Infra.CrossCutting.IoC.csproj", "{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6 - Tests", "6 - Tests", "{7426F6BA-3DAD-411E-9956-3980D42DC36F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Equinox.Tests.Architecture", "tests\Equinox.Tests.Architecture\Equinox.Tests.Architecture.csproj", "{C4036D98-A669-440C-9970-05DB45815949}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -77,6 +81,10 @@ Global
{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C4036D98-A669-440C-9970-05DB45815949}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C4036D98-A669-440C-9970-05DB45815949}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C4036D98-A669-440C-9970-05DB45815949}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C4036D98-A669-440C-9970-05DB45815949}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -93,6 +101,7 @@ Global
{91F0D76D-2BEA-43D2-B123-DC29F2E87792} = {DF6C4BDE-F3C5-4E53-A5D5-9D27B2D3E38F}
{788030D0-561B-4136-96B4-D5ABFB5CFD07} = {DF6C4BDE-F3C5-4E53-A5D5-9D27B2D3E38F}
{B3000AD2-5EAA-49A2-8FC4-10DF329B15B0} = {DF6C4BDE-F3C5-4E53-A5D5-9D27B2D3E38F}
+ {C4036D98-A669-440C-9970-05DB45815949} = {7426F6BA-3DAD-411E-9956-3980D42DC36F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4050E145-6791-440A-A2E5-75B05ACD4AFE}
diff --git a/README.md b/README.md
index 6be9720b..c60d0865 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-
-
+
What is the Equinox Project?
=====================
@@ -7,8 +6,6 @@ The Equinox Project is a open-source project written in .NET Core
The goal of this project is implement the most common used technologies and share with the technical community the best way to develop great applications with .NET
-[](https://ci.appveyor.com/project/EduardoPires/equinoxproject)
-
[](LICENSE)
[](https://huboard.com/EduardoPires/EquinoxProject/)
@@ -19,8 +16,8 @@ If you liked the project or if Equinox helped you, please give a star ;)
Check my online courses at [desenvolvedor.io](https://desenvolvedor.io)
## How to use:
-- You will need the latest Visual Studio 2019 and the latest .NET Core SDK.
-- ***Please check if you have installed the same runtime version (SDK) described in global.json***
+- You will need the latest version of Visual Studio and the latest .NET Core SDK.
+- ***Please check if you have installed the runtime version***
- The latest SDK and tools can be downloaded from https://dot.net/core.
Also you can run the Equinox Project in Visual Studio Code (Windows, Linux or MacOS).
@@ -29,18 +26,16 @@ To know more about how to setup your enviroment visit the [Microsoft .NET Downlo
## Technologies implemented:
-- ASP.NET 5.0 (with .NET Core 5.0)
+- ASP.NET 9.0
- ASP.NET MVC Core
- ASP.NET WebApi Core with JWT Bearer Authentication
- ASP.NET Identity Core
-- Entity Framework Core 5.0
-- .NET Core Native DI
-- AutoMapper
+- Entity Framework Core 9.0
+- Custom Automatic Mapping (no more AutoMapper)
- FluentValidator
-- MediatR
+- NetDevPack.SimpleMediator (no more MediatR)
+- NetDevPack (DDD, CQRS, UOW and more)
- Swagger UI with JWT support
-- .NET DevPack
-- .NET DevPack.Identity
## Architecture:
@@ -56,6 +51,25 @@ To know more about how to setup your enviroment visit the [Microsoft .NET Downlo
## News
+**v1.10 - 04/08/2025**
+- Migrated to .NET 9.0
+- Replaced MediatR with [NetDevPack.SimpleMediator](https://github.com/NetDevPack/SimpleMediator) for lighter and native CQRS handling
+- Removed AutoMapper in favor of lightweight custom mapping extensions
+- Architecture Tests with [NetArchTest.Rules](https://github.com/BenMorris/NetArchTest)
+- Added built-in SQLite support with automatic EF Core migrations (just run and go — no setup required)
+- Updated all dependencies to the latest stable versions
+
+
+**v1.9 - 06/31/2024**
+- Migrated for .NET 8.0
+- Full refactoring of Web and Api configuration
+- Now all ASP.NET Identity configurations are inside the project, without external dependencies
+- All dependencies is up to date
+
+**v1.8 - 03/22/2022**
+- Migrated for .NET 6.0
+- All dependencies is up to date
+
**v1.7 - 04/06/2021**
- Migrated for .NET 5.0
- All dependencies is up to date
@@ -97,9 +111,6 @@ To know more about how to setup your enviroment visit the [Microsoft .NET Downlo
- Beware to use in production way
- Maybe you don't need a lot of implementations that is included, try avoid the **over engineering**
-## About the next versions
-Watch our [RoadMap](https://github.com/EduardoPires/EquinoxProject/wiki/RoadMap) to know the new changes
-
## Pull-Requests
Make a contact! Don't submit PRs for extra features, all the new features are planned
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 0d4876af..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-version: 1.0.{build}
-image: Visual Studio 2019
-dotnet_csproj:
- patch: true
- file: '**\*.csproj'
- version: '{version}'
- package_version: '{version}'
-before_build:
-- cmd: >-
- dotnet restore
-
- dotnet build
-build:
- verbosity: minimal
diff --git a/docs/Architecture.dgml b/docs/Architecture.dgml
new file mode 100644
index 00000000..025b9a8f
--- /dev/null
+++ b/docs/Architecture.dgml
@@ -0,0 +1,503 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/global.json b/global.json
deleted file mode 100644
index 47473c4a..00000000
--- a/global.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "projects": ["src", "test"],
- "sdk": {
- "version": "^5.0.202"
- }
-}
diff --git a/src/Equinox.Application/AutoMapper/DomainToViewModelMappingProfile.cs b/src/Equinox.Application/AutoMapper/DomainToViewModelMappingProfile.cs
deleted file mode 100644
index 08f8a0d4..00000000
--- a/src/Equinox.Application/AutoMapper/DomainToViewModelMappingProfile.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using AutoMapper;
-using Equinox.Application.ViewModels;
-using Equinox.Domain.Models;
-
-namespace Equinox.Application.AutoMapper
-{
- public class DomainToViewModelMappingProfile : Profile
- {
- public DomainToViewModelMappingProfile()
- {
- CreateMap();
- }
- }
-}
diff --git a/src/Equinox.Application/AutoMapper/ViewModelToDomainMappingProfile.cs b/src/Equinox.Application/AutoMapper/ViewModelToDomainMappingProfile.cs
deleted file mode 100644
index 4d1e73ae..00000000
--- a/src/Equinox.Application/AutoMapper/ViewModelToDomainMappingProfile.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using AutoMapper;
-using Equinox.Application.ViewModels;
-using Equinox.Domain.Commands;
-
-namespace Equinox.Application.AutoMapper
-{
- public class ViewModelToDomainMappingProfile : Profile
- {
- public ViewModelToDomainMappingProfile()
- {
- CreateMap()
- .ConstructUsing(c => new RegisterNewCustomerCommand(c.Name, c.Email, c.BirthDate));
- CreateMap()
- .ConstructUsing(c => new UpdateCustomerCommand(c.Id, c.Name, c.Email, c.BirthDate));
- }
- }
-}
diff --git a/src/Equinox.Application/Equinox.Application.csproj b/src/Equinox.Application/Equinox.Application.csproj
index 8e83b43f..298ec90b 100644
--- a/src/Equinox.Application/Equinox.Application.csproj
+++ b/src/Equinox.Application/Equinox.Application.csproj
@@ -1,16 +1,10 @@
-
-
+
- net5.0
+ net9.0
+ disable
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Equinox.Application/Extensions/CustomerExtensions.cs b/src/Equinox.Application/Extensions/CustomerExtensions.cs
new file mode 100644
index 00000000..f7a8ab4c
--- /dev/null
+++ b/src/Equinox.Application/Extensions/CustomerExtensions.cs
@@ -0,0 +1,50 @@
+using Equinox.Application.ViewModels;
+using Equinox.Domain.Commands;
+using Equinox.Domain.Models;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Equinox.Application.Extensions
+{
+ public static class CustomerExtensions
+ {
+ public static CustomerViewModel ToViewModel(this Customer customer)
+ {
+ if (customer == null) return null;
+
+ return new CustomerViewModel
+ {
+ Id = customer.Id,
+ Name = customer.Name,
+ Email = customer.Email,
+ BirthDate = customer.BirthDate
+ };
+ }
+
+ public static IEnumerable ToViewModel(this IEnumerable customers)
+ {
+ return customers?.Select(c => c.ToViewModel());
+ }
+
+ public static Customer ToEntity(this CustomerViewModel customer)
+ {
+ if (customer == null) return null;
+
+ return new Customer(customer.Id, customer.Name, customer.Email, customer.BirthDate);
+ }
+
+ public static RegisterNewCustomerCommand ToRegisterCommand(this CustomerViewModel customer)
+ {
+ if (customer == null) return null;
+
+ return new RegisterNewCustomerCommand(customer.Name, customer.Email, customer.BirthDate);
+ }
+
+ public static UpdateCustomerCommand ToUpdateCommand(this CustomerViewModel customer)
+ {
+ if (customer == null) return null;
+
+ return new UpdateCustomerCommand(customer.Id, customer.Name, customer.Email, customer.BirthDate);
+ }
+ }
+}
diff --git a/src/Equinox.Application/Services/CustomerAppService.cs b/src/Equinox.Application/Services/CustomerAppService.cs
index 3cb6e36d..14399397 100644
--- a/src/Equinox.Application/Services/CustomerAppService.cs
+++ b/src/Equinox.Application/Services/CustomerAppService.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
-using AutoMapper;
using Equinox.Application.EventSourcedNormalizers;
+using Equinox.Application.Extensions;
using Equinox.Application.Interfaces;
using Equinox.Application.ViewModels;
using Equinox.Domain.Commands;
@@ -13,43 +13,33 @@
namespace Equinox.Application.Services
{
- public class CustomerAppService : ICustomerAppService
+ public class CustomerAppService(ICustomerRepository customerRepository,
+ IMediatorHandler mediator,
+ IEventStoreRepository eventStoreRepository) : ICustomerAppService
{
- private readonly IMapper _mapper;
- private readonly ICustomerRepository _customerRepository;
- private readonly IEventStoreRepository _eventStoreRepository;
- private readonly IMediatorHandler _mediator;
-
- public CustomerAppService(IMapper mapper,
- ICustomerRepository customerRepository,
- IMediatorHandler mediator,
- IEventStoreRepository eventStoreRepository)
- {
- _mapper = mapper;
- _customerRepository = customerRepository;
- _mediator = mediator;
- _eventStoreRepository = eventStoreRepository;
- }
+ private readonly ICustomerRepository _customerRepository = customerRepository;
+ private readonly IEventStoreRepository _eventStoreRepository = eventStoreRepository;
+ private readonly IMediatorHandler _mediator = mediator;
public async Task> GetAll()
{
- return _mapper.Map>(await _customerRepository.GetAll());
+ return (await _customerRepository.GetAll()).ToViewModel();
}
public async Task GetById(Guid id)
{
- return _mapper.Map(await _customerRepository.GetById(id));
+ return (await _customerRepository.GetById(id)).ToViewModel();
}
public async Task Register(CustomerViewModel customerViewModel)
{
- var registerCommand = _mapper.Map(customerViewModel);
+ var registerCommand = customerViewModel.ToRegisterCommand();
return await _mediator.SendCommand(registerCommand);
}
public async Task Update(CustomerViewModel customerViewModel)
{
- var updateCommand = _mapper.Map(customerViewModel);
+ var updateCommand = customerViewModel.ToUpdateCommand();
return await _mediator.SendCommand(updateCommand);
}
diff --git a/src/Equinox.Domain.Core/Equinox.Domain.Core.csproj b/src/Equinox.Domain.Core/Equinox.Domain.Core.csproj
index a3b76027..39081ba9 100644
--- a/src/Equinox.Domain.Core/Equinox.Domain.Core.csproj
+++ b/src/Equinox.Domain.Core/Equinox.Domain.Core.csproj
@@ -1,11 +1,9 @@
-
- net5.0
+ net9.0
+ disable
-
-
+
-
-
+
\ No newline at end of file
diff --git a/src/Equinox.Domain.Core/Models/ValueObject.cs b/src/Equinox.Domain.Core/Models/ValueObject.cs
deleted file mode 100644
index 5ea96d73..00000000
--- a/src/Equinox.Domain.Core/Models/ValueObject.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace Equinox.Domain.Core.Models
-{
- public abstract class ValueObject where T : ValueObject
- {
- public override bool Equals(object obj)
- {
- var valueObject = obj as T;
- return EqualsCore(valueObject);
- }
-
- protected abstract bool EqualsCore(T other);
-
- public override int GetHashCode()
- {
- return GetHashCodeCore();
- }
-
- protected abstract int GetHashCodeCore();
-
- public static bool operator ==(ValueObject a, ValueObject b)
- {
- if (a is null && b is null)
- return true;
-
- if (a is null || b is null)
- return false;
-
- return a.Equals(b);
- }
-
- public static bool operator !=(ValueObject a, ValueObject b)
- {
- return !(a == b);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Equinox.Domain/Commands/CustomerCommandHandler.cs b/src/Equinox.Domain/Commands/CustomerCommandHandler.cs
index ad2ead57..67a9b7ce 100644
--- a/src/Equinox.Domain/Commands/CustomerCommandHandler.cs
+++ b/src/Equinox.Domain/Commands/CustomerCommandHandler.cs
@@ -5,22 +5,17 @@
using Equinox.Domain.Interfaces;
using Equinox.Domain.Models;
using FluentValidation.Results;
-using MediatR;
using NetDevPack.Messaging;
+using NetDevPack.SimpleMediator.Core.Interfaces;
namespace Equinox.Domain.Commands
{
- public class CustomerCommandHandler : CommandHandler,
- IRequestHandler,
- IRequestHandler,
- IRequestHandler
+ public class CustomerCommandHandler(ICustomerRepository customerRepository) : CommandHandler,
+ IRequestHandler,
+ IRequestHandler,
+ IRequestHandler
{
- private readonly ICustomerRepository _customerRepository;
-
- public CustomerCommandHandler(ICustomerRepository customerRepository)
- {
- _customerRepository = customerRepository;
- }
+ private readonly ICustomerRepository _customerRepository = customerRepository;
public async Task Handle(RegisterNewCustomerCommand message, CancellationToken cancellationToken)
{
diff --git a/src/Equinox.Domain/Equinox.Domain.csproj b/src/Equinox.Domain/Equinox.Domain.csproj
index 9988d37b..15b9fdb6 100644
--- a/src/Equinox.Domain/Equinox.Domain.csproj
+++ b/src/Equinox.Domain/Equinox.Domain.csproj
@@ -1,19 +1,16 @@
-
- net5.0
+ net9.0
+ disable
-
-
-
+
-
-
+
\ No newline at end of file
diff --git a/src/Equinox.Domain/Events/CustomerEventHandler.cs b/src/Equinox.Domain/Events/CustomerEventHandler.cs
index 34fa4d5f..0b93ce36 100644
--- a/src/Equinox.Domain/Events/CustomerEventHandler.cs
+++ b/src/Equinox.Domain/Events/CustomerEventHandler.cs
@@ -1,6 +1,6 @@
-using System.Threading;
+using NetDevPack.SimpleMediator.Core.Interfaces;
+using System.Threading;
using System.Threading.Tasks;
-using MediatR;
namespace Equinox.Domain.Events
{
diff --git a/src/Equinox.Infra.CrossCutting.Bus/Equinox.Infra.CrossCutting.Bus.csproj b/src/Equinox.Infra.CrossCutting.Bus/Equinox.Infra.CrossCutting.Bus.csproj
index f7549b3e..8a22cd53 100644
--- a/src/Equinox.Infra.CrossCutting.Bus/Equinox.Infra.CrossCutting.Bus.csproj
+++ b/src/Equinox.Infra.CrossCutting.Bus/Equinox.Infra.CrossCutting.Bus.csproj
@@ -1,15 +1,9 @@
-
- net5.0
+ net9.0
+ disable
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Bus/InMemoryBus.cs b/src/Equinox.Infra.CrossCutting.Bus/InMemoryBus.cs
index 2bb5b517..8d2cc3f2 100644
--- a/src/Equinox.Infra.CrossCutting.Bus/InMemoryBus.cs
+++ b/src/Equinox.Infra.CrossCutting.Bus/InMemoryBus.cs
@@ -1,22 +1,16 @@
using System.Threading.Tasks;
using Equinox.Domain.Core.Events;
using FluentValidation.Results;
-using MediatR;
using NetDevPack.Mediator;
using NetDevPack.Messaging;
+using NetDevPack.SimpleMediator.Core.Interfaces;
namespace Equinox.Infra.CrossCutting.Bus
{
- public sealed class InMemoryBus : IMediatorHandler
+ public sealed class InMemoryBus(IEventStore eventStore, IMediator mediator) : IMediatorHandler
{
- private readonly IMediator _mediator;
- private readonly IEventStore _eventStore;
-
- public InMemoryBus(IEventStore eventStore, IMediator mediator)
- {
- _eventStore = eventStore;
- _mediator = mediator;
- }
+ private readonly IMediator _mediator = mediator;
+ private readonly IEventStore _eventStore = eventStore;
public async Task PublishEvent(T @event) where T : Event
{
diff --git a/src/Equinox.Infra.CrossCutting.Identity/API/AppJwtSettings.cs b/src/Equinox.Infra.CrossCutting.Identity/API/AppJwtSettings.cs
new file mode 100644
index 00000000..26fb6627
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/API/AppJwtSettings.cs
@@ -0,0 +1,10 @@
+namespace Equinox.Infra.CrossCutting.Identity.API
+{
+ public class AppJwtSettings
+ {
+ public string SecretKey { get; set; }
+ public int Expiration { get; set; } = 1;
+ public string Issuer { get; set; } = "Equinox.Api";
+ public string Audience { get; set; } = "Api";
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/API/JwtBuilder.cs b/src/Equinox.Infra.CrossCutting.Identity/API/JwtBuilder.cs
new file mode 100644
index 00000000..89840296
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/API/JwtBuilder.cs
@@ -0,0 +1,117 @@
+using Equinox.Infra.CrossCutting.Identity.Models;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+
+namespace Equinox.Infra.CrossCutting.Identity.API
+{
+ public class JwtBuilder where TIdentityUser : IdentityUser where TKey : IEquatable
+ {
+ private UserManager _userManager;
+ private AppJwtSettings _appJwtSettings;
+ private TIdentityUser _user;
+ private ICollection _userClaims;
+ private ICollection _jwtClaims;
+ private ClaimsIdentity _identityClaims;
+
+ public JwtBuilder WithUserManager(UserManager userManager)
+ {
+ _userManager = userManager ?? throw new ArgumentException(nameof(userManager));
+ return this;
+ }
+
+ public JwtBuilder WithJwtSettings(AppJwtSettings appJwtSettings)
+ {
+ _appJwtSettings = appJwtSettings ?? throw new ArgumentException(nameof(appJwtSettings));
+ return this;
+ }
+
+ public JwtBuilder WithEmail(string email)
+ {
+ if (string.IsNullOrEmpty(email)) throw new ArgumentException(nameof(email));
+
+ _user = _userManager.FindByEmailAsync(email).Result;
+ _userClaims = new List();
+ _jwtClaims = new List();
+ _identityClaims = new ClaimsIdentity();
+
+ return this;
+ }
+
+ public JwtBuilder WithJwtClaims()
+ {
+ _jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Sub, _user.Id.ToString()));
+ _jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Email, _user.Email));
+ _jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
+ _jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Nbf, ToUnixEpochDate(DateTime.UtcNow).ToString()));
+ _jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(DateTime.UtcNow).ToString(), ClaimValueTypes.Integer64));
+
+ _identityClaims.AddClaims(_jwtClaims);
+
+ return this;
+ }
+
+ public JwtBuilder WithUserClaims()
+ {
+ _userClaims = _userManager.GetClaimsAsync(_user).Result;
+ _identityClaims.AddClaims(_userClaims);
+
+ return this;
+ }
+
+ public JwtBuilder WithUserRoles()
+ {
+ var userRoles = _userManager.GetRolesAsync(_user).Result;
+ userRoles.ToList().ForEach(r => _identityClaims.AddClaim(new Claim("role", r)));
+
+ return this;
+ }
+
+ public string BuildToken()
+ {
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var key = Encoding.ASCII.GetBytes(_appJwtSettings.SecretKey);
+ var token = tokenHandler.CreateToken(new SecurityTokenDescriptor
+ {
+ Issuer = _appJwtSettings.Issuer,
+ Audience = _appJwtSettings.Audience,
+ Subject = _identityClaims,
+ Expires = DateTime.UtcNow.AddHours(_appJwtSettings.Expiration),
+ SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
+ SecurityAlgorithms.HmacSha256Signature)
+ });
+
+ return tokenHandler.WriteToken(token);
+ }
+
+ public UserResponse BuildUserResponse()
+ {
+ var user = new UserResponse
+ {
+ AccessToken = BuildToken(),
+ ExpiresIn = TimeSpan.FromHours(_appJwtSettings.Expiration).TotalSeconds,
+ UserToken = new UserToken
+ {
+ Id = _user.Id,
+ Email = _user.Email,
+ Claims = _userClaims.Select(c => new UserClaim { Type = c.Type, Value = c.Value })
+ }
+ };
+
+ return user;
+ }
+
+ private static long ToUnixEpochDate(DateTime date)
+ => (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero))
+ .TotalSeconds);
+ }
+
+ public class JwtBuilder : JwtBuilder where TIdentityUser : IdentityUser { }
+
+ public sealed class JwtBuilder : JwtBuilder { }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/ApiIdentityConfig.cs b/src/Equinox.Infra.CrossCutting.Identity/ApiIdentityConfig.cs
deleted file mode 100644
index 99fda35b..00000000
--- a/src/Equinox.Infra.CrossCutting.Identity/ApiIdentityConfig.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using NetDevPack.Identity;
-using NetDevPack.Identity.Jwt;
-
-namespace Equinox.Infra.CrossCutting.Identity
-{
- public static class ApiIdentityConfig
- {
- public static void AddApiIdentityConfiguration(this IServiceCollection services, IConfiguration configuration)
- {
- // Default EF Context for Identity (inside of the NetDevPack.Identity)
- services.AddIdentityEntityFrameworkContextConfiguration(options =>
- options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
- b => b.MigrationsAssembly("Equinox.Infra.CrossCutting.Identity")));
-
- // Default Identity configuration from NetDevPack.Identity
- services.AddIdentityConfiguration();
-
- // Default JWT configuration from NetDevPack.Identity
- services.AddJwtConfiguration(configuration, "AppSettings");
- }
- }
-}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizationValidation.cs b/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizationValidation.cs
new file mode 100644
index 00000000..3c7be705
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizationValidation.cs
@@ -0,0 +1,17 @@
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+
+namespace Equinox.Infra.CrossCutting.Identity.Authorization
+{
+ public static class CustomAuthorizationValidation
+ {
+ public static bool UserHasValidClaim(HttpContext context, string claimName, string claimValue)
+ {
+ return context.User.Identity.IsAuthenticated &&
+ context.User.Claims.Any(c =>
+ c.Type == claimName &&
+ c.Value.Split(',').Select(v => v.Trim()).Contains(claimValue));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizeAttribute.cs b/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizeAttribute.cs
new file mode 100644
index 00000000..7e946872
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Authorization/CustomAuthorizeAttribute.cs
@@ -0,0 +1,13 @@
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Equinox.Infra.CrossCutting.Identity.Authorization
+{
+ public class CustomAuthorizeAttribute : TypeFilterAttribute
+ {
+ public CustomAuthorizeAttribute(string claimName, string claimValue) : base(typeof(RequerimentClaimFilter))
+ {
+ Arguments = new object[] { new Claim(claimName, claimValue) };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Authorization/RequerimentClaimFilter.cs b/src/Equinox.Infra.CrossCutting.Identity/Authorization/RequerimentClaimFilter.cs
new file mode 100644
index 00000000..680606cf
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Authorization/RequerimentClaimFilter.cs
@@ -0,0 +1,30 @@
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace Equinox.Infra.CrossCutting.Identity.Authorization
+{
+ internal class RequerimentClaimFilter : IAuthorizationFilter
+ {
+ private readonly Claim _claim;
+
+ public RequerimentClaimFilter(Claim claim)
+ {
+ _claim = claim;
+ }
+
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ if (!context.HttpContext.User.Identity.IsAuthenticated)
+ {
+ context.Result = new StatusCodeResult(401);
+ return;
+ }
+
+ if (!CustomAuthorizationValidation.UserHasValidClaim(context.HttpContext, _claim.Type, _claim.Value))
+ {
+ context.Result = new StatusCodeResult(403);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Configuration/AspNetIdentityConfig.cs b/src/Equinox.Infra.CrossCutting.Identity/Configuration/AspNetIdentityConfig.cs
new file mode 100644
index 00000000..369c1f25
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Configuration/AspNetIdentityConfig.cs
@@ -0,0 +1,133 @@
+using Equinox.Infra.CrossCutting.Identity.API;
+using Equinox.Infra.CrossCutting.Identity.Data;
+using Equinox.Infra.CrossCutting.Identity.User;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Text;
+
+namespace Equinox.Infra.CrossCutting.Identity.Configuration
+{
+ public static class AspNetIdentityConfig
+ {
+ public static WebApplicationBuilder AddApiIdentityConfiguration(this WebApplicationBuilder builder)
+ {
+ builder.AddIdentityDbContext()
+ .AddIdentityApiSupport()
+ .AddJwtSupport()
+ .AddAspNetUserSupport();
+
+ return builder;
+ }
+
+ public static WebApplicationBuilder AddWebIdentityConfiguration(this WebApplicationBuilder builder)
+ {
+ builder.AddIdentityDbContext()
+ .AddIdentityWebUISupport()
+ .AddAspNetUserSupport()
+ .AddSocialAuthenticationSupport();
+
+ return builder;
+ }
+
+ private static WebApplicationBuilder AddIdentityDbContext(this WebApplicationBuilder builder)
+ {
+ if (builder.Environment.IsDevelopment())
+ {
+
+ builder.Services.AddDbContext(options =>
+ options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
+
+ return builder;
+ }
+
+ builder.Services.AddDbContext(options =>
+ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
+
+ return builder;
+ }
+
+ private static WebApplicationBuilder AddIdentityApiSupport(this WebApplicationBuilder builder)
+ {
+ builder.Services.AddIdentityApiEndpoints()
+ .AddRoles()
+ .AddEntityFrameworkStores()
+ .AddSignInManager()
+ .AddRoleManager>()
+ .AddDefaultTokenProviders();
+
+ return builder;
+ }
+
+ private static WebApplicationBuilder AddIdentityWebUISupport(this WebApplicationBuilder builder)
+ {
+ builder.Services.AddIdentity()
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders()
+ .AddDefaultUI();
+
+ return builder;
+ }
+
+ private static WebApplicationBuilder AddJwtSupport(this WebApplicationBuilder builder)
+ {
+ var appSettingsSection = builder.Configuration.GetSection("AppSettings");
+ builder.Services.Configure(appSettingsSection);
+
+ var appSettings = appSettingsSection.Get();
+ var key = Encoding.ASCII.GetBytes(appSettings.SecretKey);
+
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.RequireHttpsMetadata = true;
+ options.SaveToken = true;
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuerSigningKey = true,
+ IssuerSigningKey = new SymmetricSecurityKey(key),
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidAudience = appSettings.Audience,
+ ValidIssuer = appSettings.Issuer
+ };
+ });
+
+ return builder;
+ }
+
+ public static WebApplicationBuilder AddAspNetUserSupport(this WebApplicationBuilder builder)
+ {
+ builder.Services.AddSingleton();
+ builder.Services.AddScoped();
+
+ return builder;
+ }
+
+ public static WebApplicationBuilder AddSocialAuthenticationSupport(this WebApplicationBuilder builder)
+ {
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
+
+ builder.Services.AddAuthentication()
+ .AddFacebook(o =>
+ {
+ o.AppId = builder.Configuration["Authentication:Facebook:AppId"];
+ o.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"];
+ })
+ .AddGoogle(googleOptions =>
+ {
+ googleOptions.ClientId = builder.Configuration["Authentication:Google:ClientId"];
+ googleOptions.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Data/EquinoxIdentityContext.cs b/src/Equinox.Infra.CrossCutting.Identity/Data/EquinoxIdentityContext.cs
new file mode 100644
index 00000000..999d781f
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Data/EquinoxIdentityContext.cs
@@ -0,0 +1,43 @@
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.Extensions.Configuration;
+using System.IO;
+using System;
+
+namespace Equinox.Infra.CrossCutting.Identity.Data
+{
+ public class EquinoxIdentityContext : IdentityDbContext
+ {
+ public EquinoxIdentityContext(DbContextOptions options) : base(options) { }
+ }
+
+ public class EquinoxIdentityContextFactory : IDesignTimeDbContextFactory
+ {
+ public EquinoxIdentityContext CreateDbContext(string[] args)
+ {
+ var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development";
+
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: true)
+ .AddJsonFile($"appsettings.{environment}.json", optional: true)
+ .Build();
+
+ var builder = new DbContextOptionsBuilder();
+
+ var connectionString = configuration.GetConnectionString("DefaultConnection");
+
+ if (environment == "Development")
+ {
+ builder.UseSqlite(connectionString);
+ }
+ else
+ {
+ builder.UseSqlServer(connectionString);
+ }
+
+ return new EquinoxIdentityContext(builder.Options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Equinox.Infra.CrossCutting.Identity.csproj b/src/Equinox.Infra.CrossCutting.Identity/Equinox.Infra.CrossCutting.Identity.csproj
index 2f873ae2..effa8d87 100644
--- a/src/Equinox.Infra.CrossCutting.Identity/Equinox.Infra.CrossCutting.Identity.csproj
+++ b/src/Equinox.Infra.CrossCutting.Identity/Equinox.Infra.CrossCutting.Identity.csproj
@@ -1,17 +1,26 @@
-
-
+
- net5.0
+ net9.0
+ disable
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Extensions/ClaimsPrincipalExtensions.cs b/src/Equinox.Infra.CrossCutting.Identity/Extensions/ClaimsPrincipalExtensions.cs
new file mode 100644
index 00000000..3f3182b6
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Extensions/ClaimsPrincipalExtensions.cs
@@ -0,0 +1,62 @@
+using System.Security.Claims;
+using System;
+using System.IdentityModel.Tokens.Jwt;
+
+namespace Equinox.Infra.CrossCutting.Identity.Extensions
+{
+ public static class ClaimsPrincipalExtensions
+ {
+ public static string GetUserId(this ClaimsPrincipal principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentException(nameof(principal));
+ }
+
+ var claim = principal.FindFirst(JwtRegisteredClaimNames.Sub);
+ if (claim is null)
+ claim = principal.FindFirst(ClaimTypes.NameIdentifier);
+
+ return claim?.Value;
+ }
+
+ public static string GetUserEmail(this ClaimsPrincipal principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentException(nameof(principal));
+ }
+ var claim = principal.FindFirst(JwtRegisteredClaimNames.Sub);
+ if (claim is null)
+ claim = principal.FindFirst(ClaimTypes.Email);
+
+ return claim?.Value;
+ }
+ public static string GetUserId(this ClaimsIdentity principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentException(nameof(principal));
+ }
+
+ var claim = principal.FindFirst(JwtRegisteredClaimNames.Sub);
+ if (claim is null)
+ claim = principal.FindFirst(ClaimTypes.NameIdentifier);
+
+ return claim?.Value;
+ }
+
+ public static string GetUserEmail(this ClaimsIdentity principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentException(nameof(principal));
+ }
+ var claim = principal.FindFirst(JwtRegisteredClaimNames.Sub);
+ if (claim is null)
+ claim = principal.FindFirst(ClaimTypes.Email);
+
+ return claim?.Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.Designer.cs b/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.Designer.cs
new file mode 100644
index 00000000..b69eadbf
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.Designer.cs
@@ -0,0 +1,268 @@
+//
+using System;
+using Equinox.Infra.CrossCutting.Identity.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Equinox.Infra.CrossCutting.Identity.Migrations
+{
+ [DbContext(typeof(EquinoxIdentityContext))]
+ [Migration("20250408033115_SQLite")]
+ partial class SQLite
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.cs b/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.cs
new file mode 100644
index 00000000..6b943d96
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Migrations/20250408033115_SQLite.cs
@@ -0,0 +1,222 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Equinox.Infra.CrossCutting.Identity.Migrations
+{
+ ///
+ public partial class SQLite : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUsers",
+ columns: table => new
+ {
+ Id = table.Column(type: "TEXT", nullable: false),
+ UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ Email = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ EmailConfirmed = table.Column(type: "INTEGER", nullable: false),
+ PasswordHash = table.Column(type: "TEXT", nullable: true),
+ SecurityStamp = table.Column(type: "TEXT", nullable: true),
+ ConcurrencyStamp = table.Column(type: "TEXT", nullable: true),
+ PhoneNumber = table.Column(type: "TEXT", nullable: true),
+ PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false),
+ TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false),
+ LockoutEnd = table.Column(type: "TEXT", nullable: true),
+ LockoutEnabled = table.Column(type: "INTEGER", nullable: false),
+ AccessFailedCount = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUsers", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ RoleId = table.Column(type: "TEXT", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column(type: "TEXT", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ ProviderKey = table.Column(type: "TEXT", nullable: false),
+ ProviderDisplayName = table.Column(type: "TEXT", nullable: true),
+ UserId = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(type: "TEXT", nullable: false),
+ RoleId = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(type: "TEXT", nullable: false),
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ Value = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUsers");
+ }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Migrations/EquinoxIdentityContextModelSnapshot.cs b/src/Equinox.Infra.CrossCutting.Identity/Migrations/EquinoxIdentityContextModelSnapshot.cs
new file mode 100644
index 00000000..ffea074a
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Migrations/EquinoxIdentityContextModelSnapshot.cs
@@ -0,0 +1,265 @@
+//
+using System;
+using Equinox.Infra.CrossCutting.Identity.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Equinox.Infra.CrossCutting.Identity.Migrations
+{
+ [DbContext(typeof(EquinoxIdentityContext))]
+ partial class EquinoxIdentityContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Models/LoginUser.cs b/src/Equinox.Infra.CrossCutting.Identity/Models/LoginUser.cs
new file mode 100644
index 00000000..349d64e9
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Models/LoginUser.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Equinox.Infra.CrossCutting.Identity.Models
+{
+ public class LoginUser
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+
+ [Required]
+ [StringLength(100, MinimumLength = 6)]
+ public string Password { get; set; }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Models/RegisterUser.cs b/src/Equinox.Infra.CrossCutting.Identity/Models/RegisterUser.cs
new file mode 100644
index 00000000..bb0a12e0
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Models/RegisterUser.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Equinox.Infra.CrossCutting.Identity.Models
+{
+ public class RegisterUser
+ {
+ [Required]
+ [EmailAddress]
+ public string Email { get; set; }
+
+ [Required]
+ [StringLength(100, MinimumLength = 6)]
+ public string Password { get; set; }
+
+ [Compare("Password")]
+ public string ConfirmPassword { get; set; }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Models/UserClaim.cs b/src/Equinox.Infra.CrossCutting.Identity/Models/UserClaim.cs
new file mode 100644
index 00000000..c37ab8e6
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Models/UserClaim.cs
@@ -0,0 +1,8 @@
+namespace Equinox.Infra.CrossCutting.Identity.Models
+{
+ public class UserClaim
+ {
+ public string Value { get; set; }
+ public string Type { get; set; }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Models/UserResponse.cs b/src/Equinox.Infra.CrossCutting.Identity/Models/UserResponse.cs
new file mode 100644
index 00000000..60c03a4d
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Models/UserResponse.cs
@@ -0,0 +1,10 @@
+namespace Equinox.Infra.CrossCutting.Identity.Models
+{
+ public class UserResponse
+ {
+ public string AccessToken { get; set; }
+ public double ExpiresIn { get; set; }
+ public UserToken UserToken { get; set; }
+ public string RefreshToken { get; set; }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/Models/UserToken.cs b/src/Equinox.Infra.CrossCutting.Identity/Models/UserToken.cs
new file mode 100644
index 00000000..2897becc
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/Models/UserToken.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Equinox.Infra.CrossCutting.Identity.Models
+{
+ public class UserToken
+ {
+ public dynamic Id { get; set; }
+ public string Email { get; set; }
+ public IEnumerable Claims { get; set; }
+ }
+}
diff --git a/src/Equinox.Infra.CrossCutting.Identity/User/AspNetUser.cs b/src/Equinox.Infra.CrossCutting.Identity/User/AspNetUser.cs
new file mode 100644
index 00000000..f04447fe
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/User/AspNetUser.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using Equinox.Infra.CrossCutting.Identity.Extensions;
+using Microsoft.AspNetCore.Http;
+
+namespace Equinox.Infra.CrossCutting.Identity.User
+{
+ public class AspNetUser : IAspNetUser
+ {
+ private readonly IHttpContextAccessor _accessor;
+
+ public AspNetUser(IHttpContextAccessor accessor)
+ {
+ _accessor = accessor;
+ }
+
+ public string Name => _accessor.HttpContext.User.Identity.Name;
+
+ public Guid GetUserId()
+ {
+ return IsAuthenticated() ? Guid.Parse(_accessor.HttpContext.User.GetUserId()) : Guid.Empty;
+ }
+
+ public string GetUserEmail()
+ {
+ return IsAuthenticated() ? _accessor.HttpContext.User.GetUserEmail() : "";
+ }
+
+ public bool IsAuthenticated()
+ {
+ return _accessor.HttpContext.User.Identity.IsAuthenticated;
+ }
+
+ public bool IsInRole(string role)
+ {
+ return _accessor.HttpContext.User.IsInRole(role);
+ }
+
+ public IEnumerable GetUserClaims()
+ {
+ return _accessor.HttpContext.User.Claims;
+ }
+
+ public HttpContext GetHttpContext()
+ {
+ return _accessor.HttpContext;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/User/IAspNetUser.cs b/src/Equinox.Infra.CrossCutting.Identity/User/IAspNetUser.cs
new file mode 100644
index 00000000..ca8fbeb4
--- /dev/null
+++ b/src/Equinox.Infra.CrossCutting.Identity/User/IAspNetUser.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Http;
+
+namespace Equinox.Infra.CrossCutting.Identity.User
+{
+ public interface IAspNetUser
+ {
+ string Name { get; }
+ Guid GetUserId();
+ string GetUserEmail();
+ bool IsAuthenticated();
+ bool IsInRole(string role);
+ IEnumerable GetUserClaims();
+ HttpContext GetHttpContext();
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.Identity/WebAppIdentityConfig.cs b/src/Equinox.Infra.CrossCutting.Identity/WebAppIdentityConfig.cs
deleted file mode 100644
index e2ca0b7b..00000000
--- a/src/Equinox.Infra.CrossCutting.Identity/WebAppIdentityConfig.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using NetDevPack.Identity;
-
-namespace Equinox.Infra.CrossCutting.Identity
-{
- public static class WebAppIdentityConfig
- {
- public static void AddWebAppIdentityConfiguration(this IServiceCollection services, IConfiguration configuration)
- {
- // Default EF Context for Identity (inside of the NetDevPack.Identity)
- services.AddIdentityEntityFrameworkContextConfiguration(options =>
- SqlServerDbContextOptionsExtensions.UseSqlServer(options, configuration.GetConnectionString("DefaultConnection"),
- b => b.MigrationsAssembly("Equinox.Infra.CrossCutting.Identity")));
-
- // Default Identity configuration from NetDevPack.Identity
- services.AddIdentityConfiguration();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Equinox.Infra.CrossCutting.IoC/Equinox.Infra.CrossCutting.IoC.csproj b/src/Equinox.Infra.CrossCutting.IoC/Equinox.Infra.CrossCutting.IoC.csproj
index 835f9d27..282c7371 100644
--- a/src/Equinox.Infra.CrossCutting.IoC/Equinox.Infra.CrossCutting.IoC.csproj
+++ b/src/Equinox.Infra.CrossCutting.IoC/Equinox.Infra.CrossCutting.IoC.csproj
@@ -1,13 +1,13 @@
- net5.0
+ net9.0
+ disable
-
diff --git a/src/Equinox.Infra.CrossCutting.IoC/NativeInjectorBootStrapper.cs b/src/Equinox.Infra.CrossCutting.IoC/NativeInjectorBootStrapper.cs
index 885f9a7d..e425dede 100644
--- a/src/Equinox.Infra.CrossCutting.IoC/NativeInjectorBootStrapper.cs
+++ b/src/Equinox.Infra.CrossCutting.IoC/NativeInjectorBootStrapper.cs
@@ -10,40 +10,43 @@
using Equinox.Infra.Data.Repository;
using Equinox.Infra.Data.Repository.EventSourcing;
using FluentValidation.Results;
-using MediatR;
+using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using NetDevPack.Mediator;
+using NetDevPack.SimpleMediator.Core.Implementation;
+using NetDevPack.SimpleMediator.Core.Interfaces;
namespace Equinox.Infra.CrossCutting.IoC
{
public static class NativeInjectorBootStrapper
{
- public static void RegisterServices(IServiceCollection services)
+ public static void RegisterServices(WebApplicationBuilder builder)
{
// Domain Bus (Mediator)
- services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
// Application
- services.AddScoped();
+ builder.Services.AddScoped();
// Domain - Events
- services.AddScoped, CustomerEventHandler>();
- services.AddScoped, CustomerEventHandler>();
- services.AddScoped, CustomerEventHandler>();
+ builder.Services.AddScoped, CustomerEventHandler>();
+ builder.Services.AddScoped, CustomerEventHandler>();
+ builder.Services.AddScoped, CustomerEventHandler>();
// Domain - Commands
- services.AddScoped, CustomerCommandHandler>();
- services.AddScoped, CustomerCommandHandler>();
- services.AddScoped, CustomerCommandHandler>();
+ builder.Services.AddScoped, CustomerCommandHandler>();
+ builder.Services.AddScoped, CustomerCommandHandler>();
+ builder.Services.AddScoped, CustomerCommandHandler>();
// Infra - Data
- services.AddScoped();
- services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
// Infra - Data EventSourcing
- services.AddScoped();
- services.AddScoped();
- services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
+ builder.Services.AddScoped();
}
}
}
\ No newline at end of file
diff --git a/src/Equinox.Infra.Data/Equinox.Infra.Data.csproj b/src/Equinox.Infra.Data/Equinox.Infra.Data.csproj
index ab40863d..287f0346 100644
--- a/src/Equinox.Infra.Data/Equinox.Infra.Data.csproj
+++ b/src/Equinox.Infra.Data/Equinox.Infra.Data.csproj
@@ -1,19 +1,22 @@
-
- net5.0
+ net9.0
+ disable
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/Equinox.Infra.Data/EventSourcing/SqlEventStore.cs b/src/Equinox.Infra.Data/EventSourcing/SqlEventStore.cs
index dac91b12..90c2deeb 100644
--- a/src/Equinox.Infra.Data/EventSourcing/SqlEventStore.cs
+++ b/src/Equinox.Infra.Data/EventSourcing/SqlEventStore.cs
@@ -1,6 +1,6 @@
using Equinox.Domain.Core.Events;
+using Equinox.Infra.CrossCutting.Identity.User;
using Equinox.Infra.Data.Repository.EventSourcing;
-using NetDevPack.Identity.User;
using NetDevPack.Messaging;
using Newtonsoft.Json;
diff --git a/src/Equinox.Infra.Data/Migrations/20161213130431_Initial.Designer.cs b/src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.Designer.cs
similarity index 55%
rename from src/Equinox.Infra.Data/Migrations/20161213130431_Initial.Designer.cs
rename to src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.Designer.cs
index 6244cea2..eaeb01c7 100644
--- a/src/Equinox.Infra.Data/Migrations/20161213130431_Initial.Designer.cs
+++ b/src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.Designer.cs
@@ -1,44 +1,50 @@
-using System;
+//
+using System;
+using Equinox.Infra.Data.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
-using Equinox.Infra.Data.Context;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
namespace Equinox.Infra.Data.Migrations
{
[DbContext(typeof(EquinoxContext))]
- [Migration("20161213130431_Initial")]
- partial class Initial
+ [Migration("20250408031104_SQLite")]
+ partial class SQLite
{
+ ///
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
- modelBuilder
- .HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
modelBuilder.Entity("Equinox.Domain.Models.Customer", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
.HasColumnName("Id");
- b.Property("BirthDate");
+ b.Property("BirthDate")
+ .HasColumnType("TEXT");
b.Property("Email")
.IsRequired()
- .HasColumnType("varchar(100)")
- .HasMaxLength(11);
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
b.Property("Name")
.IsRequired()
- .HasColumnType("varchar(100)")
- .HasMaxLength(100);
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
b.HasKey("Id");
b.ToTable("Customers");
});
+#pragma warning restore 612, 618
}
}
}
diff --git a/src/Equinox.Infra.Data/Migrations/20161213130431_Initial.cs b/src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.cs
similarity index 67%
rename from src/Equinox.Infra.Data/Migrations/20161213130431_Initial.cs
rename to src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.cs
index c972c1d0..98241cd0 100644
--- a/src/Equinox.Infra.Data/Migrations/20161213130431_Initial.cs
+++ b/src/Equinox.Infra.Data/Migrations/20250408031104_SQLite.cs
@@ -1,20 +1,24 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
+#nullable disable
+
namespace Equinox.Infra.Data.Migrations
{
- public partial class Initial : Migration
+ ///
+ public partial class SQLite : Migration
{
+ ///
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Customers",
columns: table => new
{
- Id = table.Column(nullable: false),
- BirthDate = table.Column(nullable: false),
- Email = table.Column(type: "varchar(100)", maxLength: 11, nullable: false),
- Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false)
+ Id = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "varchar(100)", maxLength: 100, nullable: false),
+ Email = table.Column(type: "varchar(100)", maxLength: 100, nullable: false),
+ BirthDate = table.Column(type: "TEXT", nullable: false)
},
constraints: table =>
{
@@ -22,6 +26,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
});
}
+ ///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
diff --git a/src/Equinox.Infra.Data/Migrations/EquinoxContextModelSnapshot.cs b/src/Equinox.Infra.Data/Migrations/EquinoxContextModelSnapshot.cs
index 2d4e9a17..e6371a74 100644
--- a/src/Equinox.Infra.Data/Migrations/EquinoxContextModelSnapshot.cs
+++ b/src/Equinox.Infra.Data/Migrations/EquinoxContextModelSnapshot.cs
@@ -1,8 +1,11 @@
-using System;
+//
+using System;
+using Equinox.Infra.Data.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Equinox.Infra.Data.Context;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
namespace Equinox.Infra.Data.Migrations
{
@@ -11,32 +14,34 @@ partial class EquinoxContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
- modelBuilder
- .HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
modelBuilder.Entity("Equinox.Domain.Models.Customer", b =>
{
b.Property("Id")
.ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
.HasColumnName("Id");
- b.Property("BirthDate");
+ b.Property("BirthDate")
+ .HasColumnType("TEXT");
b.Property("Email")
.IsRequired()
- .HasColumnType("varchar(100)")
- .HasMaxLength(11);
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
b.Property("Name")
.IsRequired()
- .HasColumnType("varchar(100)")
- .HasMaxLength(100);
+ .HasMaxLength(100)
+ .HasColumnType("varchar(100)");
b.HasKey("Id");
b.ToTable("Customers");
});
+#pragma warning restore 612, 618
}
}
}
diff --git a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.Designer.cs b/src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.Designer.cs
deleted file mode 100644
index 9bed57c4..00000000
--- a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.Designer.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Equinox.Infra.Data.Context;
-
-namespace Equinox.Infra.Data.Migrations.EventStoreSQL
-{
- [DbContext(typeof(EventStoreSqlContext))]
- [Migration("20161213130520_Initial")]
- partial class Initial
- {
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
- modelBuilder
- .HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
-
- modelBuilder.Entity("Equinox.Domain.Core.Events.StoredEvent", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd();
-
- b.Property("AggregateId");
-
- b.Property("Data");
-
- b.Property("MessageType")
- .HasColumnName("Action")
- .HasColumnType("varchar(100)");
-
- b.Property("Timestamp")
- .HasColumnName("CreationDate");
-
- b.Property("User");
-
- b.HasKey("Id");
-
- b.ToTable("StoredEvent");
- });
- }
- }
-}
diff --git a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/EventStoreSQLContextModelSnapshot.cs b/src/Equinox.Infra.Data/Migrations/EventStoreSQL/EventStoreSQLContextModelSnapshot.cs
index d156d92d..530d0ee8 100644
--- a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/EventStoreSQLContextModelSnapshot.cs
+++ b/src/Equinox.Infra.Data/Migrations/EventStoreSQL/EventStoreSQLContextModelSnapshot.cs
@@ -1,42 +1,50 @@
-using System;
+//
+using System;
+using Equinox.Infra.Data.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Equinox.Infra.Data.Context;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
-namespace Equinox.Infra.Data.Migrations.EventStoreSQL
+namespace Equinox.Infra.Data.Migrations.EventStoreSql
{
[DbContext(typeof(EventStoreSqlContext))]
- partial class EventStoreSQLContextModelSnapshot : ModelSnapshot
+ partial class EventStoreSqlContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
- modelBuilder
- .HasAnnotation("ProductVersion", "1.1.0-rtm-22752")
- .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
modelBuilder.Entity("Equinox.Domain.Core.Events.StoredEvent", b =>
{
b.Property("Id")
- .ValueGeneratedOnAdd();
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
- b.Property("AggregateId");
+ b.Property("AggregateId")
+ .HasColumnType("TEXT");
- b.Property("Data");
+ b.Property("Data")
+ .HasColumnType("TEXT");
b.Property("MessageType")
- .HasColumnName("Action")
- .HasColumnType("varchar(100)");
+ .HasColumnType("varchar(100)")
+ .HasColumnName("Action");
b.Property("Timestamp")
+ .HasColumnType("TEXT")
.HasColumnName("CreationDate");
- b.Property("User");
+ b.Property("User")
+ .HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("StoredEvent");
});
+#pragma warning restore 612, 618
}
}
}
diff --git a/src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.Designer.cs b/src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.Designer.cs
new file mode 100644
index 00000000..79736d5d
--- /dev/null
+++ b/src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.Designer.cs
@@ -0,0 +1,53 @@
+//
+using System;
+using Equinox.Infra.Data.Context;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Equinox.Infra.Data.Migrations.EventStoreSql
+{
+ [DbContext(typeof(EventStoreSqlContext))]
+ [Migration("20250408031128_SQLite")]
+ partial class SQLite
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.3");
+
+ modelBuilder.Entity("Equinox.Domain.Core.Events.StoredEvent", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("AggregateId")
+ .HasColumnType("TEXT");
+
+ b.Property("Data")
+ .HasColumnType("TEXT");
+
+ b.Property("MessageType")
+ .HasColumnType("varchar(100)")
+ .HasColumnName("Action");
+
+ b.Property("Timestamp")
+ .HasColumnType("TEXT")
+ .HasColumnName("CreationDate");
+
+ b.Property("User")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("StoredEvent");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.cs b/src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.cs
similarity index 55%
rename from src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.cs
rename to src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.cs
index 843e8a4b..176b3cf8 100644
--- a/src/Equinox.Infra.Data/Migrations/EventStoreSQL/20161213130520_Initial.cs
+++ b/src/Equinox.Infra.Data/Migrations/EventStoreSql/20250408031128_SQLite.cs
@@ -1,22 +1,26 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
-namespace Equinox.Infra.Data.Migrations.EventStoreSQL
+#nullable disable
+
+namespace Equinox.Infra.Data.Migrations.EventStoreSql
{
- public partial class Initial : Migration
+ ///
+ public partial class SQLite : Migration
{
+ ///
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "StoredEvent",
columns: table => new
{
- Id = table.Column(nullable: false),
- AggregateId = table.Column(nullable: false),
- Data = table.Column(nullable: true),
+ Id = table.Column(type: "TEXT", nullable: false),
+ Data = table.Column(type: "TEXT", nullable: true),
+ User = table.Column(type: "TEXT", nullable: true),
Action = table.Column(type: "varchar(100)", nullable: true),
- CreationDate = table.Column(nullable: false),
- User = table.Column(nullable: true)
+ AggregateId = table.Column(type: "TEXT", nullable: false),
+ CreationDate = table.Column(type: "TEXT", nullable: false)
},
constraints: table =>
{
@@ -24,6 +28,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
});
}
+ ///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
diff --git a/src/Equinox.Services.Api/Configurations/ApiConfig.cs b/src/Equinox.Services.Api/Configurations/ApiConfig.cs
new file mode 100644
index 00000000..80fba996
--- /dev/null
+++ b/src/Equinox.Services.Api/Configurations/ApiConfig.cs
@@ -0,0 +1,20 @@
+namespace Equinox.Services.Api.Configurations
+{
+ public static class ApiConfig
+ {
+ public static WebApplicationBuilder AddApiConfiguration(this WebApplicationBuilder builder)
+ {
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
+
+ builder.Configuration
+ .SetBasePath(builder.Environment.ContentRootPath)
+ .AddJsonFile("appsettings.json", true, true)
+ .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
+ .AddEnvironmentVariables();
+
+ builder.Services.AddControllers();
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Equinox.Services.Api/Configurations/AutoMapperConfig.cs b/src/Equinox.Services.Api/Configurations/AutoMapperConfig.cs
deleted file mode 100644
index 1d7efcba..00000000
--- a/src/Equinox.Services.Api/Configurations/AutoMapperConfig.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using Equinox.Application.AutoMapper;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Equinox.Services.Api.Configurations
-{
- public static class AutoMapperConfig
- {
- public static void AddAutoMapperConfiguration(this IServiceCollection services)
- {
- if (services == null) throw new ArgumentNullException(nameof(services));
-
- services.AddAutoMapper(typeof(DomainToViewModelMappingProfile), typeof(ViewModelToDomainMappingProfile));
- }
- }
-}
\ No newline at end of file
diff --git a/src/Equinox.Services.Api/Configurations/DatabaseConfig.cs b/src/Equinox.Services.Api/Configurations/DatabaseConfig.cs
index adbafe60..a6eb3071 100644
--- a/src/Equinox.Services.Api/Configurations/DatabaseConfig.cs
+++ b/src/Equinox.Services.Api/Configurations/DatabaseConfig.cs
@@ -1,22 +1,21 @@
-using System;
-using Equinox.Infra.Data.Context;
+using Equinox.Infra.Data.Context;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
namespace Equinox.Services.Api.Configurations
{
public static class DatabaseConfig
{
- public static void AddDatabaseConfiguration(this IServiceCollection services, IConfiguration configuration)
+ public static WebApplicationBuilder AddDatabaseConfiguration(this WebApplicationBuilder builder)
{
- if (services == null) throw new ArgumentNullException(nameof(services));
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
- services.AddDbContext(options =>
- options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
+ builder.Services.AddDbContext(options =>
+ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
- services.AddDbContext(options =>
- options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
+ builder.Services.AddDbContext(options =>
+ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
+
+ return builder;
}
}
}
\ No newline at end of file
diff --git a/src/Equinox.Services.Api/Configurations/DependencyInjectionConfig.cs b/src/Equinox.Services.Api/Configurations/DependencyInjectionConfig.cs
index 9b54a449..39f0d026 100644
--- a/src/Equinox.Services.Api/Configurations/DependencyInjectionConfig.cs
+++ b/src/Equinox.Services.Api/Configurations/DependencyInjectionConfig.cs
@@ -1,16 +1,16 @@
-using System;
-using Equinox.Infra.CrossCutting.IoC;
-using Microsoft.Extensions.DependencyInjection;
+using Equinox.Infra.CrossCutting.IoC;
namespace Equinox.Services.Api.Configurations
{
public static class DependencyInjectionConfig
{
- public static void AddDependencyInjectionConfiguration(this IServiceCollection services)
+ public static WebApplicationBuilder AddDependencyInjectionConfiguration(this WebApplicationBuilder builder)
{
- if (services == null) throw new ArgumentNullException(nameof(services));
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
- NativeInjectorBootStrapper.RegisterServices(services);
+ NativeInjectorBootStrapper.RegisterServices(builder);
+
+ return builder;
}
}
}
\ No newline at end of file
diff --git a/src/Equinox.Services.Api/Configurations/SwaggerConfig.cs b/src/Equinox.Services.Api/Configurations/SwaggerConfig.cs
index 60fd1cdd..d52185cc 100644
--- a/src/Equinox.Services.Api/Configurations/SwaggerConfig.cs
+++ b/src/Equinox.Services.Api/Configurations/SwaggerConfig.cs
@@ -1,17 +1,16 @@
-using System;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Models;
namespace Equinox.Services.Api.Configurations
{
public static class SwaggerConfig
{
- public static void AddSwaggerConfiguration(this IServiceCollection services)
+ public static WebApplicationBuilder AddSwaggerConfiguration(this WebApplicationBuilder builder)
{
- if (services == null) throw new ArgumentNullException(nameof(services));
+ if (builder == null) throw new ArgumentNullException(nameof(builder));
- services.AddSwaggerGen(s =>
+ builder.Services.AddEndpointsApiExplorer();
+
+ builder.Services.AddSwaggerGen(s =>
{
s.SwaggerDoc("v1", new OpenApiInfo
{
@@ -47,10 +46,42 @@ public static void AddSwaggerConfiguration(this IServiceCollection services)
}
});
+ // Excluding ASP.NET Identity endpoints
+ s.DocInclusionPredicate((docName, apiDesc) =>
+ {
+ var relativePath = apiDesc.RelativePath;
+
+ // List of avoid patches
+ var identityEndpoints = new[]
+ {
+ "register",
+ "manage",
+ "refresh",
+ "login",
+ "confirmEmail",
+ "resendConfirmationEmail",
+ "forgotPassword",
+ "resetPassword"
+ };
+
+ // Validating if the endpoint is avoided
+ foreach (var endpoint in identityEndpoints)
+ {
+ if (relativePath.Contains(endpoint, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
});
+
+ return builder;
}
- public static void UseSwaggerSetup(this IApplicationBuilder app)
+ public static IApplicationBuilder UseSwaggerSetup(this IApplicationBuilder app)
{
if (app == null) throw new ArgumentNullException(nameof(app));
@@ -59,6 +90,8 @@ public static void UseSwaggerSetup(this IApplicationBuilder app)
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
});
+
+ return app;
}
}
}
\ No newline at end of file
diff --git a/src/Equinox.Services.Api/Controllers/AccountController.cs b/src/Equinox.Services.Api/Controllers/AccountController.cs
index 07416c97..6617d497 100644
--- a/src/Equinox.Services.Api/Controllers/AccountController.cs
+++ b/src/Equinox.Services.Api/Controllers/AccountController.cs
@@ -1,13 +1,12 @@
-using System.Threading.Tasks;
+using Equinox.Infra.CrossCutting.Identity.API;
+using Equinox.Infra.CrossCutting.Identity.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
-using NetDevPack.Identity.Jwt;
-using NetDevPack.Identity.Model;
namespace Equinox.Services.Api.Controllers
{
- [Route("api/[controller]")]
+ [Route("account")]
[ApiController]
public class AccountController : ApiController
{
@@ -26,7 +25,7 @@ public AccountController(
}
[HttpPost]
- [Route("register")]
+ [Route("new")]
public async Task Register(RegisterUser registerUser)
{
if (!ModelState.IsValid) return CustomResponse(ModelState);
@@ -54,7 +53,7 @@ public async Task Register(RegisterUser registerUser)
}
[HttpPost]
- [Route("login")]
+ [Route("enter")]
public async Task Login(LoginUser loginUser)
{
if (!ModelState.IsValid) return CustomResponse(ModelState);
diff --git a/src/Equinox.Services.Api/Controllers/ApiController.cs b/src/Equinox.Services.Api/Controllers/ApiController.cs
index 6f518ae2..8266fbf9 100644
--- a/src/Equinox.Services.Api/Controllers/ApiController.cs
+++ b/src/Equinox.Services.Api/Controllers/ApiController.cs
@@ -1,6 +1,4 @@
-using System.Collections.Generic;
-using System.Linq;
-using FluentValidation.Results;
+using FluentValidation.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
diff --git a/src/Equinox.Services.Api/Controllers/CustomerController.cs b/src/Equinox.Services.Api/Controllers/CustomerController.cs
index cc32d374..e900dc17 100644
--- a/src/Equinox.Services.Api/Controllers/CustomerController.cs
+++ b/src/Equinox.Services.Api/Controllers/CustomerController.cs
@@ -1,12 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
using Equinox.Application.EventSourcedNormalizers;
using Equinox.Application.Interfaces;
using Equinox.Application.ViewModels;
+using Equinox.Infra.CrossCutting.Identity.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using NetDevPack.Identity.Authorization;
namespace Equinox.Services.Api.Controllers
{
@@ -21,42 +18,42 @@ public CustomerController(ICustomerAppService customerAppService)
}
[AllowAnonymous]
- [HttpGet("customer-management")]
+ [HttpGet("customer")]
public async Task> Get()
{
return await _customerAppService.GetAll();
}
[AllowAnonymous]
- [HttpGet("customer-management/{id:guid}")]
+ [HttpGet("customer/{id:guid}")]
public async Task Get(Guid id)
{
return await _customerAppService.GetById(id);
}
[CustomAuthorize("Customers", "Write")]
- [HttpPost("customer-management")]
+ [HttpPost("customer")]
public async Task