diff --git a/Orion.sln b/Orion.sln index ebe5e95..63543aa 100644 --- a/Orion.sln +++ b/Orion.sln @@ -16,7 +16,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orion.Infra.Data", "src\Ori EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orion.Croscutting.Resources", "src\Orion.Croscutting.Resources\Orion.Croscutting.Resources.csproj", "{105D1EB4-5091-493E-B75E-2131169CC9AC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devops", "Devops", "{2D8CC498-597C-4415-95BF-48E623039F90}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{2D8CC498-597C-4415-95BF-48E623039F90}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore docker-compose.yaml = docker-compose.yaml @@ -43,7 +43,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "template-config", "template EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4 - Croscutting", "4 - Croscutting", "{34D11C13-159D-47B0-A5B8-3DF32677792F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orion.Application.Core", "src\Orion.Application.Core\Orion.Application.Core.csproj", "{1F3FDF5B-999B-4D96-81F7-25F854EB40D6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orion.Application.Core", "src\Orion.Application.Core\Orion.Application.Core.csproj", "{1F3FDF5B-999B-4D96-81F7-25F854EB40D6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -87,11 +87,11 @@ Global {8C8470D6-0951-4B42-8935-779D8672F5B1} = {11C424D2-7519-493F-B4BB-A5901B33C56B} {AA8C7554-18FC-40D7-A318-123D2AAAB023} = {B333633B-8361-476B-A0A3-6B983D28904E} {7C877ABA-17F2-470A-99EB-5E172C6EA44B} = {434E301F-C36C-4203-B334-D3948130B660} + {105D1EB4-5091-493E-B75E-2131169CC9AC} = {34D11C13-159D-47B0-A5B8-3DF32677792F} {8C70C31A-892F-4C8F-B7B0-A710C97A55FA} = {2D8CC498-597C-4415-95BF-48E623039F90} + {BF0248B6-C870-4419-862D-1DCAD4561946} = {34D11C13-159D-47B0-A5B8-3DF32677792F} {9F14F904-BBC6-4616-BB80-76E485EAEC14} = {67A0BEBC-35B1-436C-9F05-AC8F60AF1688} {309303F2-F07E-4578-8079-C7D2CE2AE329} = {2D8CC498-597C-4415-95BF-48E623039F90} - {BF0248B6-C870-4419-862D-1DCAD4561946} = {34D11C13-159D-47B0-A5B8-3DF32677792F} - {105D1EB4-5091-493E-B75E-2131169CC9AC} = {34D11C13-159D-47B0-A5B8-3DF32677792F} {1F3FDF5B-999B-4D96-81F7-25F854EB40D6} = {67A0BEBC-35B1-436C-9F05-AC8F60AF1688} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/README.md b/README.md index 557366d..dd68be5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The main objective is to start projects with a clean and simple architecture, wi + CreatedAt and UpdatedAt by default; + Pagination. -+ AutoMapper ++ MediaR + Swagger + Fluent Validation + Authentication and Authorization @@ -44,14 +44,17 @@ The main objective is to start projects with a clean and simple architecture, wi 10. Docker and Docker-Compose; 11. API Version Configuration (by x-api-version header attribute); 12. Globalization; - 13. In-Memory database for Testing. + 13. In-Memory database for Testing; + 14. Mediator + CQRS + Notification Pattern + 15. Logs with Correlation Id **CI & CD** 1. Automated tests; - 2. Continuous Integration; - 3. Continuous Delivery; - 4. DockerHub Integration. + 2. Continuous Integration (GitHub CI); + 3. Continuous Delivery (GitHub CI); + 4. DockerHub Integration; + 5. Sonar Cloud Integration. # **Create your Project based on the Orion Api Template** @@ -60,13 +63,13 @@ The main objective is to start projects with a clean and simple architecture, wi dotnet new install . - dotnet new orion-api -o MyNewProject --firstEntity "EntityName" + dotnet new orion-api -o MyNewProject **Migrations** # in the src/ folder - dotnet ef migrations add MigrationName -p Orion.Data -s Orion.Api - dotnet ef database update -p Orion.Data -s Orion.Api --verbose + dotnet ef migrations add MigrationName -p Orion.Infra.Data -s Orion.Api + dotnet ef database update -p Orion.Infra.Data -s Orion.Api --verbose Author: https://github.com/vanderlan \ No newline at end of file diff --git a/src/Orion.Api/Bootstrapper.cs b/src/Orion.Api/Bootstrapper.cs index c5bdd9f..92bc2c4 100644 --- a/src/Orion.Api/Bootstrapper.cs +++ b/src/Orion.Api/Bootstrapper.cs @@ -7,6 +7,7 @@ using Orion.Application.Core; using Orion.Application.Core.Commands.UserCreate; using Orion.Croscutting.Ioc.Dependencies; +using Orion.Domain.Core.Authentication; namespace Orion.Api; @@ -77,7 +78,8 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur services.AddDomainServices(); services.AddHttpContextAccessor(); - + services.AddScoped(); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ApplicationAssembly).Assembly)); } } diff --git a/src/Orion.Api/Configuration/AuthenticationConfiguration.cs b/src/Orion.Api/Configuration/AuthenticationConfiguration.cs index d43a18e..780a933 100644 --- a/src/Orion.Api/Configuration/AuthenticationConfiguration.cs +++ b/src/Orion.Api/Configuration/AuthenticationConfiguration.cs @@ -41,9 +41,9 @@ public static (string Token, DateTime ValidTo)CreateToken(LoginWithCredentialsRe var claims = new[] { - new Claim(JwtRegisteredClaimNames.Email, loginWithCredentialsResponse.Email), - new Claim(JwtRegisteredClaimNames.GivenName, loginWithCredentialsResponse.Name), - new Claim(JwtRegisteredClaimNames.UniqueName, loginWithCredentialsResponse.PublicId), + new Claim(ClaimTypes.Email, loginWithCredentialsResponse.Email), + new Claim(ClaimTypes.GivenName, loginWithCredentialsResponse.Name), + new Claim(ClaimTypes.Sid, loginWithCredentialsResponse.PublicId), new Claim(ClaimTypes.Role, loginWithCredentialsResponse.ProfileDescription), }; diff --git a/src/Orion.Api/Controllers/Base/ApiController.cs b/src/Orion.Api/Controllers/Base/ApiController.cs index 646fc2d..cb67840 100644 --- a/src/Orion.Api/Controllers/Base/ApiController.cs +++ b/src/Orion.Api/Controllers/Base/ApiController.cs @@ -1,7 +1,5 @@ -using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; using MediatR; -using Orion.Api.Models; +using Microsoft.AspNetCore.Mvc; namespace Orion.Api.Controllers.Base; @@ -9,25 +7,11 @@ namespace Orion.Api.Controllers.Base; public abstract class ApiController : ControllerBase { protected readonly IMediator Mediator; - protected AuthUserModel AuthUser => GetAuthenticatedUser(); protected ApiController(IMediator mediator) { Mediator = mediator; } - private AuthUserModel GetAuthenticatedUser() - { - var email = ((ClaimsIdentity)User.Identity)?.FindFirst(ClaimTypes.Email); - var givenName = ((ClaimsIdentity)User.Identity)?.FindFirst(ClaimTypes.GivenName); - - return new AuthUserModel - ( - PublicId: User?.Identity?.Name, - Email: email?.Value, - Name: givenName?.Value - ); - } - protected CreatedResult Created(object entity) { return base.Created("{id}", entity); diff --git a/src/Orion.Api/Models/AuthUserModel.cs b/src/Orion.Api/Models/AuthUserModel.cs deleted file mode 100644 index 646c070..0000000 --- a/src/Orion.Api/Models/AuthUserModel.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Orion.Api.Models; - -public record AuthUserModel (string PublicId, string Email, string Name); diff --git a/src/Orion.Api/Properties/launchSettings.json b/src/Orion.Api/Properties/launchSettings.json index f643621..c83e824 100644 --- a/src/Orion.Api/Properties/launchSettings.json +++ b/src/Orion.Api/Properties/launchSettings.json @@ -3,7 +3,7 @@ "Dev": { "commandName": "Project", "launchUrl": "swagger", - "launchBrowser": true, + "launchBrowser": false, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/src/Orion.Application.Core/Notifications/UserCreated/UserCreatedNotificationHandler.cs b/src/Orion.Application.Core/Notifications/UserCreated/UserCreatedNotificationHandler.cs index 50e1f88..43daf21 100644 --- a/src/Orion.Application.Core/Notifications/UserCreated/UserCreatedNotificationHandler.cs +++ b/src/Orion.Application.Core/Notifications/UserCreated/UserCreatedNotificationHandler.cs @@ -1,23 +1,27 @@ using MediatR; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Orion.Domain.Core.Authentication; namespace Orion.Application.Core.Notifications.UserCreated { public class UserCreatedNotificationHandler : INotificationHandler { private readonly ILogger _logger; + private readonly ICurrentUser _currentUser; - public UserCreatedNotificationHandler(ILogger logger) + public UserCreatedNotificationHandler(ILogger logger, ICurrentUser currentUser) { _logger = logger; + _currentUser = currentUser; } public async Task Handle(UserCreatedNotification notification, CancellationToken cancellationToken) { - _logger.LogInformation("A notification {notificationType} has been received. Notification details: {notification}", + _logger.LogInformation("A notification {notificationType} has been received. Notification details: {notification}. Action performed by: {currentUserName}", nameof(UserCreatedNotification), - JsonConvert.SerializeObject(notification, Formatting.Indented)); + JsonConvert.SerializeObject(notification, Formatting.Indented), + _currentUser.ToString()); await Task.CompletedTask; } diff --git a/src/Orion.Domain.Core/Authentication/CurrentUser.cs b/src/Orion.Domain.Core/Authentication/CurrentUser.cs new file mode 100644 index 0000000..2864b9d --- /dev/null +++ b/src/Orion.Domain.Core/Authentication/CurrentUser.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; + +namespace Orion.Domain.Core.Authentication; + +public class CurrentUser : ICurrentUser +{ + private readonly IHttpContextAccessor _accessor; + private readonly List _claims; + + public CurrentUser(IHttpContextAccessor accessor) + { + _accessor = accessor; + _claims = _accessor.HttpContext.User.Claims.ToList(); + } + + public string Name => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value : string.Empty; + public string Id => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value: string.Empty; + public string Email => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Email)?.Value: string.Empty; + public string Profile => IsAuthenticated() ? _claims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value: string.Empty; + + public bool IsAuthenticated() + { + return _accessor.HttpContext.User.Identity.IsAuthenticated; + } + + public override string ToString() + { + if(IsAuthenticated()) + return $"Name: {Name} - Id: {Id} - Email: {Email} - Profile: {Profile}"; + + return "Anonymous User"; + } +} diff --git a/src/Orion.Domain.Core/Authentication/ICurrentUser.cs b/src/Orion.Domain.Core/Authentication/ICurrentUser.cs new file mode 100644 index 0000000..d8624e6 --- /dev/null +++ b/src/Orion.Domain.Core/Authentication/ICurrentUser.cs @@ -0,0 +1,7 @@ +namespace Orion.Domain.Core.Authentication; + +public interface ICurrentUser +{ + string Name { get; } + bool IsAuthenticated(); +} diff --git a/src/Orion.Domain.Core/Orion.Domain.Core.csproj b/src/Orion.Domain.Core/Orion.Domain.Core.csproj index 3ee1ef0..865eb14 100644 --- a/src/Orion.Domain.Core/Orion.Domain.Core.csproj +++ b/src/Orion.Domain.Core/Orion.Domain.Core.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Orion.Infra.Data/Migrations/20231030205851_Initial.Designer.cs b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.Designer.cs new file mode 100644 index 0000000..53c0fa3 --- /dev/null +++ b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.Designer.cs @@ -0,0 +1,124 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Orion.Infra.Data.Context; + +#nullable disable + +namespace Orion.Infra.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20231030205851_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Orion.Domain.Core.Entities.RefreshToken", b => + { + b.Property("Refreshtoken") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("PublicId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Refreshtoken"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("RefreshToken", (string)null); + }); + + modelBuilder.Entity("Orion.Domain.Core.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("UserId")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Profile") + .HasColumnType("int"); + + b.Property("PublicId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("UserId"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("User", (string)null); + + b.HasData( + new + { + UserId = 1, + CreatedAt = new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1590), + Email = "adm@orion-api.com", + LastUpdated = new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1592), + Name = "Orion Admin User", + Password = "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", + Profile = 1, + PublicId = "80302277-52d6-4c78-b7a7-0d458ecdea8b" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs new file mode 100644 index 0000000..0491516 --- /dev/null +++ b/src/Orion.Infra.Data/Migrations/20231030205851_Initial.cs @@ -0,0 +1,88 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Orion.Infra.Data.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RefreshToken", + columns: table => new + { + Refreshtoken = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: false), + Email = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + PublicId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + LastUpdated = table.Column(type: "datetime2", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RefreshToken", x => x.Refreshtoken); + }); + + migrationBuilder.CreateTable( + name: "User", + columns: table => new + { + UserId = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Email = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Password = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: false), + Profile = table.Column(type: "int", nullable: false), + PublicId = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + LastUpdated = table.Column(type: "datetime2", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_User", x => x.UserId); + }); + + migrationBuilder.InsertData( + table: "User", + columns: new[] { "UserId", "CreatedAt", "Email", "LastUpdated", "Name", "Password", "Profile", "PublicId" }, + values: new object[] { 1, new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1590), "adm@orion-api.com", new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1592), "Orion Admin User", "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", 1, "80302277-52d6-4c78-b7a7-0d458ecdea8b" }); + + migrationBuilder.CreateIndex( + name: "IX_RefreshToken_Email", + table: "RefreshToken", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RefreshToken_PublicId", + table: "RefreshToken", + column: "PublicId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_User_Email", + table: "User", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_User_PublicId", + table: "User", + column: "PublicId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RefreshToken"); + + migrationBuilder.DropTable( + name: "User"); + } + } +} diff --git a/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs b/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 0000000..a8f1907 --- /dev/null +++ b/src/Orion.Infra.Data/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,121 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Orion.Infra.Data.Context; + +#nullable disable + +namespace Orion.Infra.Data.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Orion.Domain.Core.Entities.RefreshToken", b => + { + b.Property("Refreshtoken") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("PublicId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Refreshtoken"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("RefreshToken", (string)null); + }); + + modelBuilder.Entity("Orion.Domain.Core.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("UserId")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("LastUpdated") + .HasColumnType("datetime2"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Profile") + .HasColumnType("int"); + + b.Property("PublicId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("UserId"); + + b.HasIndex("Email") + .IsUnique(); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("User", (string)null); + + b.HasData( + new + { + UserId = 1, + CreatedAt = new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1590), + Email = "adm@orion-api.com", + LastUpdated = new DateTime(2023, 10, 30, 20, 58, 51, 289, DateTimeKind.Utc).AddTicks(1592), + Name = "Orion Admin User", + Password = "3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2", + Profile = 1, + PublicId = "80302277-52d6-4c78-b7a7-0d458ecdea8b" + }); + }); +#pragma warning restore 612, 618 + } + } +}