diff --git a/E-Commerce.Domain/E-Commerce.Domain.csproj b/E-Commerce.Domain/E-Commerce.Domain.csproj index 8c5a65c..eae74cc 100644 --- a/E-Commerce.Domain/E-Commerce.Domain.csproj +++ b/E-Commerce.Domain/E-Commerce.Domain.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/E-Commerce.Domain/IdentityModule/Address.cs b/E-Commerce.Domain/IdentityModule/Address.cs new file mode 100644 index 0000000..f2f64b6 --- /dev/null +++ b/E-Commerce.Domain/IdentityModule/Address.cs @@ -0,0 +1,15 @@ +namespace E_Commerce.Domain.IdentityModule +{ + public class Address + { + public int Id { get; set; } // PK + public string City { get; set; } = default!; + public string Street { get; set; } = default!; + public string Country { get; set; } = default!; + public string FirstName { get; set; } = default!; + public string LastName { get; set; } = default!; + + public ApplicationUser User { get; set; } = default!; + public string UserId { get; set; } = default!; + } +} \ No newline at end of file diff --git a/E-Commerce.Domain/IdentityModule/ApplicationUser.cs b/E-Commerce.Domain/IdentityModule/ApplicationUser.cs new file mode 100644 index 0000000..7944095 --- /dev/null +++ b/E-Commerce.Domain/IdentityModule/ApplicationUser.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Domain.IdentityModule +{ + public class ApplicationUser : IdentityUser + { + + public string DisplayName { get; set; } = default!; + public Address? Address { get; set; } + + } +} diff --git a/E-Commerce.Persistance/IdentityData/DbContexts/StoreIdentityDbContext.cs b/E-Commerce.Persistance/IdentityData/DbContexts/StoreIdentityDbContext.cs new file mode 100644 index 0000000..bc550aa --- /dev/null +++ b/E-Commerce.Persistance/IdentityData/DbContexts/StoreIdentityDbContext.cs @@ -0,0 +1,29 @@ +using E_Commerce.Domain.IdentityModule; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Persistance.IdentityData.DbContexts +{ + public class StoreIdentityDbContext : IdentityDbContext + { + public StoreIdentityDbContext(DbContextOptions options) : base(options) + { + + } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + builder.Entity
().ToTable("Addresses"); + builder.Entity().ToTable("Users"); + builder.Entity().ToTable("Roles"); + builder.Entity>().ToTable("UserRoles"); + } + + } +} diff --git a/E-Commerce.Persistance/IdentityData/IdentityData/IdentityDataInitializer.cs b/E-Commerce.Persistance/IdentityData/IdentityData/IdentityDataInitializer.cs new file mode 100644 index 0000000..023ac3a --- /dev/null +++ b/E-Commerce.Persistance/IdentityData/IdentityData/IdentityDataInitializer.cs @@ -0,0 +1,68 @@ +using E_Commerce.Domain.Contracts; +using E_Commerce.Domain.IdentityModule; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Persistance.IdentityData.IdentityData +{ + public class IdentityDataInitializer : IDataInitializer + { + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + private readonly ILogger _logger; + + public IdentityDataInitializer(UserManager userManager, + RoleManager roleManager, + ILogger logger) + { + _userManager = userManager; + _roleManager = roleManager; + _logger = logger; + } + public async Task InitializeAsync() + { + try + { + + if(!_roleManager.Roles.Any()) + { + await _roleManager.CreateAsync(new IdentityRole("Admin")); + await _roleManager.CreateAsync(new IdentityRole("SuperAdmin")); + } + + if(_userManager.Users.Any()) + { + var User01 = new ApplicationUser() + { + DisplayName = "Mohamed Tarek" , + UserName = "MohamedTarek" , + Email = "MohamedTarek@gmail.com" , + PhoneNumber = "0123456789" + }; + var User02 = new ApplicationUser() + { + DisplayName = "Salma Tarek", + UserName = "SalmaTarek", + Email = "SalmaTarek@gmail.com", + PhoneNumber = "0123456559" + }; + + await _userManager.CreateAsync(User01, "P@ssw0rd"); + await _userManager.CreateAsync(User02, "P@ssw0rd"); + + await _userManager.AddToRoleAsync(User01, "Admin"); + await _userManager.AddToRoleAsync(User02, "SuperAdmin"); + } + } + catch (Exception ex) + { + _logger.LogError($"Error During Seeding Identity DataBase : {ex}"); + } + } + } +} diff --git a/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.Designer.cs b/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.Designer.cs new file mode 100644 index 0000000..48ba385 --- /dev/null +++ b/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.Designer.cs @@ -0,0 +1,339 @@ +// +using System; +using E_Commerce.Persistance.IdentityData.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace E_Commerce.Persistance.IdentityData.Migrations +{ + [DbContext(typeof(StoreIdentityDbContext))] + [Migration("20251120174639_IdentityTablesInitialCreate")] + partial class IdentityTablesInitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Addresses", (string)null); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.Address", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", "User") + .WithOne("Address") + .HasForeignKey("E_Commerce.Domain.IdentityModule.Address", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + 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("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", 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("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.ApplicationUser", b => + { + b.Navigation("Address"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.cs b/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.cs new file mode 100644 index 0000000..e03b1a6 --- /dev/null +++ b/E-Commerce.Persistance/IdentityData/Migrations/20251120174639_IdentityTablesInitialCreate.cs @@ -0,0 +1,258 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace E_Commerce.Persistance.IdentityData.Migrations +{ + /// + public partial class IdentityTablesInitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + DisplayName = table.Column(type: "nvarchar(max)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Addresses", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + City = table.Column(type: "nvarchar(max)", nullable: false), + Street = table.Column(type: "nvarchar(max)", nullable: false), + Country = table.Column(type: "nvarchar(max)", nullable: false), + FirstName = table.Column(type: "nvarchar(max)", nullable: false), + LastName = table.Column(type: "nvarchar(max)", nullable: false), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Addresses", x => x.Id); + table.ForeignKey( + name: "FK_Addresses_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_UserRoles_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_UserRoles_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Addresses_UserId", + table: "Addresses", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "Roles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_UserRoles_RoleId", + table: "UserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "Users", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "Users", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Addresses"); + + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "UserRoles"); + + migrationBuilder.DropTable( + name: "Roles"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/E-Commerce.Persistance/IdentityData/Migrations/StoreIdentityDbContextModelSnapshot.cs b/E-Commerce.Persistance/IdentityData/Migrations/StoreIdentityDbContextModelSnapshot.cs new file mode 100644 index 0000000..29a1dd9 --- /dev/null +++ b/E-Commerce.Persistance/IdentityData/Migrations/StoreIdentityDbContextModelSnapshot.cs @@ -0,0 +1,336 @@ +// +using System; +using E_Commerce.Persistance.IdentityData.DbContexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace E_Commerce.Persistance.IdentityData.Migrations +{ + [DbContext(typeof(StoreIdentityDbContext))] + partial class StoreIdentityDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Addresses", (string)null); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("Roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("UserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.Address", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", "User") + .WithOne("Address") + .HasForeignKey("E_Commerce.Domain.IdentityModule.Address", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + 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("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", 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("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("E_Commerce.Domain.IdentityModule.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("E_Commerce.Domain.IdentityModule.ApplicationUser", b => + { + b.Navigation("Address"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/E-Commerce.Presentaion/Controllers/ApiBaseController.cs b/E-Commerce.Presentaion/Controllers/ApiBaseController.cs new file mode 100644 index 0000000..c3acacb --- /dev/null +++ b/E-Commerce.Presentaion/Controllers/ApiBaseController.cs @@ -0,0 +1,80 @@ +using E_Commerce.Shared.CommonResult; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Presentaion.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class ApiBaseController : ControllerBase + { + protected IActionResult HandleResult(Result result) + { + + if (result.IsSuccess) + return NoContent(); + else + return HandleProblem(result.Errors); + + } + + protected ActionResult HandleResult(Result result) + { + if (result.IsSuccess) + return Ok(result.Value); + else + return HandleProblem(result.Errors); + + } + + private ActionResult HandleProblem(IReadOnlyList errors) + { + // if no errors are provided ,return 500 error + if (errors.Count == 0) + return Problem(statusCode: StatusCodes.Status500InternalServerError, title: "An Unexpected Error"); + + // if all errors are validation errors , handle them as validation problem + if (errors.All(e => e.Type == ErrorType.Validation)) + return HandleValidationProblem(errors); + + // if there id only one error , handle it as a single error problem + return HandleSingleErrorProblem(errors[0]); + + } + private ActionResult HandleSingleErrorProblem(Error error) + { + + return Problem( + title: error.Code, + detail: error.Descreption, + type: error.Type.ToString(), + statusCode: MapErrorTypeTostatusCode(error.Type) + ); + } + private static int MapErrorTypeTostatusCode(ErrorType errorType) => errorType switch + { + ErrorType.Failure => StatusCodes.Status500InternalServerError, + ErrorType.Validation => StatusCodes.Status400BadRequest, + ErrorType.InvalidCredentials => StatusCodes.Status400BadRequest, + ErrorType.Unauthorized => StatusCodes.Status401Unauthorized, + ErrorType.NotFound => StatusCodes.Status404NotFound, + ErrorType.Forbidden => StatusCodes.Status403Forbidden, + _ => StatusCodes.Status500InternalServerError + + }; + + private ActionResult HandleValidationProblem(IReadOnlyList errors) + { + var modelstate = new ModelStateDictionary(); + foreach (var error in errors) + modelstate.AddModelError(error.Code, error.Descreption); + return ValidationProblem(modelstate); + } + } +} diff --git a/E-Commerce.Presentaion/Controllers/AuthenticationController.cs b/E-Commerce.Presentaion/Controllers/AuthenticationController.cs new file mode 100644 index 0000000..22d3ad7 --- /dev/null +++ b/E-Commerce.Presentaion/Controllers/AuthenticationController.cs @@ -0,0 +1,61 @@ +using E_Commerce.Services.Abstraction; +using E_Commerce.Shared.DTOs.IdentityDTOs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Presentaion.Controllers +{ + public class AuthenticationController : ApiBaseController + { + private readonly IAuthenticationService _authenticationService; + + public AuthenticationController(IAuthenticationService authenticationService) + { + this._authenticationService = authenticationService; + } + + //Login + // Post : BaseUrl/api/Authentication/Login + [HttpPost("Login")] + public async Task> Login(LoginDTO loginDTO) + { + var Result = await _authenticationService.LoginAsync(loginDTO); + return HandleResult(Result); + } + + // Register + // Post : BaseUrl/api/Authentication/Register + [HttpPost("Register")] + public async Task> Register(RegisterDTO registerDTO) + { + var Result = await _authenticationService.RegisterAsync(registerDTO); + return HandleResult(Result); + } + + + // Get : Get/api/Authentication/emailexists + [HttpGet("emailExists")] + public async Task> CheckEmail(string email) + { + var Result = await _authenticationService.CheckEmailAsync(email); + return Ok(Result); + } + + // Get : Get/api/Authentication/CurrentUser + [Authorize] + [HttpGet("CurrentUser")] + public async Task> GetCurrentUser() + { + var Email = User.FindFirstValue(ClaimValueTypes.Email)!; + var Result = await _authenticationService.GetUserByEmailAsync(Email); + return HandleResult(Result); + } + + } +} diff --git a/E-Commerce.Presentaion/Controllers/ProductController.cs b/E-Commerce.Presentaion/Controllers/ProductController.cs index 9e215e6..92ad1f5 100644 --- a/E-Commerce.Presentaion/Controllers/ProductController.cs +++ b/E-Commerce.Presentaion/Controllers/ProductController.cs @@ -2,6 +2,7 @@ using E_Commerce.Services.Abstraction; using E_Commerce.Shared; using E_Commerce.Shared.DTOs.ProductDTOs; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; @@ -12,9 +13,8 @@ namespace E_Commerce.Presentaion.Controllers { - [ApiController] - [Route("api/[controller]")] - public class ProductController :ControllerBase + + public class ProductController :ApiBaseController { private readonly IProductService _productService; @@ -25,6 +25,7 @@ public ProductController(IProductService productService) //GetAllProducts //Get : BaseUrl/api/Products + [Authorize] [HttpGet] [RedisCache] public async Task>> GetAllProducts([FromQuery] ProductQueryParams queryParams) @@ -39,9 +40,10 @@ public async Task>> GetAllProducts([Fro public async Task> GetProduct(int id) { //throw new Exception(); - var Product = await _productService.GetProductByIdAsync(id); + var Result = await _productService.GetProductByIdAsync(id); - return Ok(Product); + return HandleResult(Result); + } //GetAllProductTypes diff --git a/E-Commerce.Services.Abstraction/IAuthenticationService.cs b/E-Commerce.Services.Abstraction/IAuthenticationService.cs new file mode 100644 index 0000000..8be7060 --- /dev/null +++ b/E-Commerce.Services.Abstraction/IAuthenticationService.cs @@ -0,0 +1,22 @@ +using E_Commerce.Shared.CommonResult; +using E_Commerce.Shared.DTOs.IdentityDTOs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Services.Abstraction +{ + public interface IAuthenticationService + { + //Login + Task> LoginAsync(LoginDTO loginDTO); + + //Register + Task> RegisterAsync(RegisterDTO registerDTO); + + Task CheckEmailAsync(string email); + Task> GetUserByEmailAsync(string email); + } +} diff --git a/E-Commerce.Services.Abstraction/IProductService.cs b/E-Commerce.Services.Abstraction/IProductService.cs index bdf3ae3..40cbc25 100644 --- a/E-Commerce.Services.Abstraction/IProductService.cs +++ b/E-Commerce.Services.Abstraction/IProductService.cs @@ -1,4 +1,5 @@ using E_Commerce.Shared; +using E_Commerce.Shared.CommonResult; using E_Commerce.Shared.DTOs.ProductDTOs; using System; using System.Collections.Generic; @@ -14,7 +15,7 @@ public interface IProductService Task> GetAllProductAsync(ProductQueryParams queryParams); // Get Product By ID Return ProductDOT - Task GetProductByIdAsync(int id); + Task> GetProductByIdAsync(int id); // Get All Brands Return IEumerable of BrandDTO Task> GetAllBransdAsync(); diff --git a/E-Commerce.Services/AuthenticationService.cs b/E-Commerce.Services/AuthenticationService.cs new file mode 100644 index 0000000..428313c --- /dev/null +++ b/E-Commerce.Services/AuthenticationService.cs @@ -0,0 +1,114 @@ +using E_Commerce.Domain.IdentityModule; +using E_Commerce.Services.Abstraction; +using E_Commerce.Shared.CommonResult; +using E_Commerce.Shared.DTOs.IdentityDTOs; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Configuration; +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; +using System.Threading.Tasks; + +namespace E_Commerce.Services +{ + public class AuthenticationService : IAuthenticationService + { + private readonly UserManager _userManager; + private readonly IConfiguration _configuration; + + public AuthenticationService(UserManager userManager, IConfiguration configuration) + { + _userManager = userManager; + _configuration = configuration; + } + + public async Task CheckEmailAsync(string email) + { + var User = await _userManager.FindByEmailAsync(email); + return User != null; + } + + public async Task> GetUserByEmailAsync(string email) + { + var User = await _userManager.FindByEmailAsync(email); + if (User is null) + return Error.NotFound("User.NotFound", $"No User With Email {email} Was Found"); + return new UserDTO(User.Email!, User.DisplayName, await CreateTokenAsync(User)); + } + + public async Task> LoginAsync(LoginDTO loginDTO) + { + + var User = await _userManager.FindByEmailAsync(loginDTO.Email); + if (User == null) + return Error.InvalidCredentials("User.InvalidCredentials"); + + var IsPasswordValid = await _userManager.CheckPasswordAsync(User, loginDTO.Password); + if (!IsPasswordValid) + return Error.InvalidCredentials("User.InvalidCredentials"); + var Token = await CreateTokenAsync(User); + return new UserDTO(User.Email!, User.DisplayName, Token); + + } + + public async Task> RegisterAsync(RegisterDTO registerDTO) + { + var User = new ApplicationUser() + { + Email = registerDTO.Email, + DisplayName = registerDTO.DisplayName, + PhoneNumber = registerDTO.PhoneNumber, + UserName = registerDTO.UserName + }; + var IdentityResult = await _userManager.CreateAsync(User, registerDTO.Password); + + if (IdentityResult.Succeeded) + { + var Token = await CreateTokenAsync(User); + return new UserDTO(User.Email, User.DisplayName, Token); + } + return IdentityResult.Errors.Select(e => Error.Validation(e.Code, e.Description)).ToList(); + } + + private async Task CreateTokenAsync(ApplicationUser user) + { + // To Create Token [Issuer - Audience - Claims - Expires - SigningCredentials] + + var Claims = new List() + { + new Claim(JwtRegisteredClaimNames.Email, user.Email!), + new Claim(JwtRegisteredClaimNames.Name, user.UserName!) + }; + + var Roles = await _userManager.GetRolesAsync(user); + foreach (var role in Roles) + { + Claims.Add(new Claim(ClaimTypes.Role, role)); + } + + var SecretKey = _configuration["JWTOptions:SecretKey"]; + + var Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey!)); + var SigningCredentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256); + + var Token = new JwtSecurityToken + ( + issuer: _configuration["JWTOptions:Issuer"], + audience: _configuration["JWTOptions:Audience"], + expires: DateTime.UtcNow.AddHours(1), + claims: Claims, + signingCredentials: SigningCredentials + ); + + return new JwtSecurityTokenHandler().WriteToken(Token); + + + } + + + } +} diff --git a/E-Commerce.Services/E-Commerce.Services.csproj b/E-Commerce.Services/E-Commerce.Services.csproj index 826d180..b85673c 100644 --- a/E-Commerce.Services/E-Commerce.Services.csproj +++ b/E-Commerce.Services/E-Commerce.Services.csproj @@ -9,6 +9,7 @@ + diff --git a/E-Commerce.Services/ProductService.cs b/E-Commerce.Services/ProductService.cs index d7ea8e2..b097ee8 100644 --- a/E-Commerce.Services/ProductService.cs +++ b/E-Commerce.Services/ProductService.cs @@ -5,6 +5,7 @@ using E_Commerce.Services.Exceptions; using E_Commerce.Services.Specifications; using E_Commerce.Shared; +using E_Commerce.Shared.CommonResult; using E_Commerce.Shared.DTOs.ProductDTOs; using System; using System.Collections.Generic; @@ -55,15 +56,15 @@ public async Task> GetAllTypesdAsync() } - public async Task GetProductByIdAsync(int id) + public async Task> GetProductByIdAsync(int id) { var spec = new ProductWithTypeAndBrandSpecification(id); var Product = await _unitOfWork.GetRepository().GetByIdAsync(spec); + + if (Product is null) + return (Error.NotFound("Product.NotFound",$"Product With {id} is Not Found")); - if (Product == null) - throw new ProductNotFoundException(id); - - return _mapper.Map(Product); + return _mapper.Map(Product); // Implicit Casting To Resutl.Fail - Resutl.OK } } } diff --git a/E-Commerce.Shared/CommonResult/Error.cs b/E-Commerce.Shared/CommonResult/Error.cs new file mode 100644 index 0000000..319bd27 --- /dev/null +++ b/E-Commerce.Shared/CommonResult/Error.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Shared.CommonResult +{ + public class Error + { + + public string Code { get; } + public string Descreption { get; } + public ErrorType Type { get; } + private Error(string code, string descreption, ErrorType type) + { + Code = code; + Descreption = descreption; + Type = type; + } + + #region Static Factory Methods + + public static Error Failure(string Code = "General.Failure", string Descreption = "An unexpected error occurred. Please try again later") + { + return new Error(Code, Descreption, ErrorType.Failure); + } + public static Error Validation(string Code = "General.Validation", string Descreption = "Validation Error Has Occured") + { + return new Error(Code, Descreption, ErrorType.Validation); + } + public static Error NotFound(string Code = "General.NotFound", string Descreption = "The Requested Resource Was Not Found") + { + return new Error(Code, Descreption, ErrorType.NotFound); + } + public static Error Unauthorized(string Code = "General.Unauthorized", string Descreption = "You Are Not Authorized To This Process") + { + return new Error(Code, Descreption, ErrorType.Unauthorized); + } + public static Error Forbidden(string Code = "General.Forbidden", string Descreption = "You Do Not Have Permission To This Process") + { + return new Error(Code, Descreption, ErrorType.Forbidden); + } + public static Error InvalidCredentials(string Code = "General.InvalidCredentials", string Descreption = "The provided credentials are incorrect") + { + return new Error(Code, Descreption, ErrorType.InvalidCredentials); + } + #endregion + + } +} diff --git a/E-Commerce.Shared/CommonResult/ErrorType.cs b/E-Commerce.Shared/CommonResult/ErrorType.cs new file mode 100644 index 0000000..a20976d --- /dev/null +++ b/E-Commerce.Shared/CommonResult/ErrorType.cs @@ -0,0 +1,12 @@ +namespace E_Commerce.Shared.CommonResult +{ + public enum ErrorType + { + Failure = 0, + Validation = 1, + NotFound = 2, + Unauthorized = 3, + Forbidden = 4, + InvalidCredentials = 5 + } +} \ No newline at end of file diff --git a/E-Commerce.Shared/CommonResult/Result.cs b/E-Commerce.Shared/CommonResult/Result.cs new file mode 100644 index 0000000..0e186d9 --- /dev/null +++ b/E-Commerce.Shared/CommonResult/Result.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace E_Commerce.Shared.CommonResult +{ + public class Result + { + private readonly List _errors = []; + public IReadOnlyList Errors => _errors; + public bool IsSuccess => _errors.Count == 0; // true + public bool IsFailure => !IsSuccess; // false + + //Ok - Success + protected Result() { } + //Fail With Error + public Result(Error error) + { + _errors.Add(error); + } + //Fail With List + public Result(List errors) + { + _errors = errors; + } + + + + + + + } + + public class Result : Result + { + private readonly TValue _value; + public TValue Value => IsSuccess ? _value : throw new InvalidOperationException("Can Not Access To The Value"); + + //Ok - Success With Value + protected Result(TValue value) : base() + { + _value = value; + } + + //Fail With Error With Value + public Result(Error error) : base() + { + _value = default!; + } + + //Fail With List With Value + public Result(List errors) : base() + { + _value = default!; + + } + + public static Result OK(TValue value) => new Result(value); + public static Result Fail(Error error) => new Result(error); + public static Result Fail(List errors) => new Result(errors); + + + public static implicit operator Result(TValue value) => OK(value); + public static implicit operator Result(Error error) => Fail(error); + public static implicit operator Result(List errors) => Fail(errors); + + + } +} diff --git a/E-Commerce.Shared/DTOs/IdentityDTOs/LoginDTO.cs b/E-Commerce.Shared/DTOs/IdentityDTOs/LoginDTO.cs new file mode 100644 index 0000000..f63fd6c --- /dev/null +++ b/E-Commerce.Shared/DTOs/IdentityDTOs/LoginDTO.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Shared.DTOs.IdentityDTOs +{ + public record LoginDTO([EmailAddress]string Email, string Password); + +} diff --git a/E-Commerce.Shared/DTOs/IdentityDTOs/RegisterDTO.cs b/E-Commerce.Shared/DTOs/IdentityDTOs/RegisterDTO.cs new file mode 100644 index 0000000..6e0b2a3 --- /dev/null +++ b/E-Commerce.Shared/DTOs/IdentityDTOs/RegisterDTO.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Shared.DTOs.IdentityDTOs +{ + public record RegisterDTO([EmailAddress] string Email, string DisplayName,string UserName,string Password,[Phone]string PhoneNumber); + +} diff --git a/E-Commerce.Shared/DTOs/IdentityDTOs/UserDTO.cs b/E-Commerce.Shared/DTOs/IdentityDTOs/UserDTO.cs new file mode 100644 index 0000000..80ce750 --- /dev/null +++ b/E-Commerce.Shared/DTOs/IdentityDTOs/UserDTO.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace E_Commerce.Shared.DTOs.IdentityDTOs +{ + public record UserDTO(string Email,string DisplayName,string Token); + + + +} diff --git a/E-CommerceProject/CustomMiddleWares/ExceptionHandlerMiddleWare.cs b/E-CommerceProject/CustomMiddleWares/ExceptionHandlerMiddleWare.cs index f334e4e..006068c 100644 --- a/E-CommerceProject/CustomMiddleWares/ExceptionHandlerMiddleWare.cs +++ b/E-CommerceProject/CustomMiddleWares/ExceptionHandlerMiddleWare.cs @@ -1,4 +1,5 @@ -using E_Commerce.Services.Exceptions; +using Azure; +using E_Commerce.Services.Exceptions; using Microsoft.AspNetCore.Mvc; namespace E_CommerceProject.CustomMiddleWares @@ -48,16 +49,16 @@ public async Task Invoke(HttpContext httpcontext) private static async Task HandleNotFoundEndPointAsync(HttpContext httpcontext) { - if (httpcontext.Response.StatusCode == StatusCodes.Status404NotFound) + if (httpcontext.Response.StatusCode == StatusCodes.Status404NotFound && !httpcontext.Response.HasStarted) { - var Problem = new ProblemDetails() + var Response = new ProblemDetails() { Title = "Error Will Processing The Http Request - EndPoint Not Found !", Status = StatusCodes.Status404NotFound, Detail = $"EndPoint {httpcontext.Request.Path} Not Found", Instance = httpcontext.Request.Path }; - await httpcontext.Response.WriteAsJsonAsync(Problem); + await httpcontext.Response.WriteAsJsonAsync(Response); } } } diff --git a/E-CommerceProject/Extentions/WebApplicationRegistration.cs b/E-CommerceProject/Extentions/WebApplicationRegistration.cs index b00212f..0586df2 100644 --- a/E-CommerceProject/Extentions/WebApplicationRegistration.cs +++ b/E-CommerceProject/Extentions/WebApplicationRegistration.cs @@ -1,5 +1,6 @@ using E_Commerce.Domain.Contracts; using E_Commerce.Persistance.Data.Contexts; +using E_Commerce.Persistance.IdentityData.DbContexts; using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; @@ -8,6 +9,15 @@ namespace E_CommerceProject.Extentions public static class WebApplicationRegistration { public static async Task MigrateDatabaseAsync(this WebApplication app) + { + await using var scope = app.Services.CreateAsyncScope(); + var dbContextService = scope.ServiceProvider.GetRequiredService(); + var PendingMigtations = await dbContextService.Database.GetPendingMigrationsAsync(); + if (PendingMigtations.Any()) + await dbContextService.Database.MigrateAsync(); + return app; + } + public static async Task MigrateIdentityDatabaseAsync(this WebApplication app) { await using var scope = app.Services.CreateAsyncScope(); var dbContextService = scope.ServiceProvider.GetRequiredService(); @@ -21,11 +31,21 @@ public static async Task SeedDatabaseAsync(this WebApplication a { await using var scope = app.Services.CreateAsyncScope(); - var DataInitializerService = scope.ServiceProvider.GetRequiredService(); + var DataInitializerService = scope.ServiceProvider.GetRequiredKeyedService("Default"); await DataInitializerService.InitializeAsync(); return app; } + public static async Task SeedIdentityDatabaseAsync(this WebApplication app) + { + await using var scope = app.Services.CreateAsyncScope(); + + var DataInitializerService = scope.ServiceProvider.GetRequiredKeyedService("Identity"); + await DataInitializerService.InitializeAsync(); + + return app; + } + } } diff --git a/E-CommerceProject/Program.cs b/E-CommerceProject/Program.cs index 68e0784..9442b9a 100644 --- a/E-CommerceProject/Program.cs +++ b/E-CommerceProject/Program.cs @@ -1,7 +1,10 @@ using E_Commerce.Domain.Contracts; +using E_Commerce.Domain.IdentityModule; using E_Commerce.Persistance.Data.Contexts; using E_Commerce.Persistance.Data.DataSeed; +using E_Commerce.Persistance.IdentityData.DbContexts; +using E_Commerce.Persistance.IdentityData.IdentityData; using E_Commerce.Persistance.Repositories; using E_Commerce.Services; using E_Commerce.Services.Abstraction; @@ -9,10 +12,14 @@ using E_CommerceProject.CustomMiddleWares; using E_CommerceProject.Extentions; using E_CommerceProject.Factories; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.IdentityModel.Tokens; using StackExchange.Redis; using System.Reflection; +using System.Text; using System.Threading.Tasks; namespace E_CommerceProject @@ -33,7 +40,9 @@ public static async Task Main(string[] args) { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); - builder.Services.AddScoped(); + builder.Services.AddKeyedScoped("Default"); + builder.Services.AddKeyedScoped("Identity"); + builder.Services.AddScoped(); //builder.Services.AddAutoMapper(x=>x.AddProfile()); @@ -57,6 +66,43 @@ public static async Task Main(string[] args) options.InvalidModelStateResponseFactory = ApiResponseFactory.GenerateApiValidationResponse; }); + + builder.Services.AddDbContext(options => + { + + options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection")); + + }); + + builder.Services.AddIdentityCore() + .AddRoles() + .AddEntityFrameworkStores(); + builder.Services.AddScoped(); + + builder.Services.AddAuthentication(Options => + { + Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddJwtBearer(Options => + { + + Options.SaveToken = true; + Options.TokenValidationParameters = new TokenValidationParameters() + { + + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidIssuer = builder.Configuration["JWTOptions:Issuer"], + ValidAudience = builder.Configuration["JWTOptions:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWTOptions:SecretKey"])) + + }; + + }); + + + #endregion #region Redis Connection @@ -67,30 +113,15 @@ public static async Task Main(string[] args) #region Data Seeding - Pending Migations await app.MigrateDatabaseAsync(); // Method In Extention Folder In E-Commerce.Web + await app.MigrateIdentityDatabaseAsync(); // Method In Extention Folder In E-Commerce.Web await app.SeedDatabaseAsync(); // Method In Extention Folder In E-Commerce.Web + await app.SeedIdentityDatabaseAsync(); // Method In Extention Folder In E-Commerce.Web #endregion #region Configure the HTTP request pipeline. - //app.Use(async (Context, Next) => - //{ - // try - // { - // await Next.Invoke(Context); - - // } - // catch (Exception ex) - // { - // Console.WriteLine(ex.Message); - // Context.Response.StatusCode = StatusCodes.Status500InternalServerError; - // await Context.Response.WriteAsJsonAsync(new - // { - // StatusCode = StatusCodes.Status500InternalServerError, - // Error = $" An Unexpected Error Occurred : {ex.Message}" - // }); - // } - //}); + app.UseMiddleware(); @@ -103,7 +134,7 @@ public static async Task Main(string[] args) app.UseHttpsRedirection(); app.UseStaticFiles(); - + app.UseAuthentication(); app.UseAuthorization(); diff --git a/E-CommerceProject/appsettings.Development.json b/E-CommerceProject/appsettings.Development.json index 7da8afb..5628a6d 100644 --- a/E-CommerceProject/appsettings.Development.json +++ b/E-CommerceProject/appsettings.Development.json @@ -7,9 +7,8 @@ }, "ConnectionStrings": { "DefaultConnection": " Server =.; Database =Ecommerce; Trusted_Connection=True;TrustServerCertificate=True;", - "RedisConnection": "localhost" - //"RedisConnection": "127.0.0.1:6379" - + "RedisConnection": "localhost", + "IdentityConnection": " Server =.; Database =Ecommerce.Identity; Trusted_Connection=True;TrustServerCertificate=True;" @@ -17,6 +16,11 @@ }, "URLs": { "BaseUrl": "https://localhost:7195/" + }, + "JWTOptions": { + "SecretKey": "47735f00f7edd4401b2c1b7fa358007a", + "Issuer": "https://localhost:7195/", + "Audience": "https://localhost:7195/" } }