diff --git "a/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" "b/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" index 77989cf91..c1b03e693 100644 --- "a/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" +++ "b/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" @@ -1,6 +1,7 @@ +using System.Collections.Concurrent; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; -using System.Collections.Concurrent; +using PromoCodeFactory.Core.Exceptions; namespace PromoCodeFactory.DataAccess.Repositories; @@ -19,21 +20,40 @@ public Task> GetAll(CancellationToken ct) public Task GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + _data.TryGetValue(id, out var entity); + return Task.FromResult(entity); } public Task Add(T entity, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + if (!_data.TryAdd(entity.Id, entity)) + throw new InvalidOperationException($"Объект с id = '{entity.Id}' уже существует."); + + return Task.CompletedTask; } public Task Update(T entity, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + if (!_data.ContainsKey(entity.Id)) + throw new EntityNotFoundException(entity.Id); + + _data[entity.Id] = entity; + return Task.CompletedTask; } public Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + if (!_data.TryRemove(id, out _)) + throw new EntityNotFoundException(id); + + return Task.CompletedTask; } } diff --git "a/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" "b/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" index 0de8444f1..888c20378 100644 --- "a/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" +++ "b/Homeworks/02 \320\227\320\275\320\260\320\272\320\276\320\274\321\201\321\202\320\262\320\276 \321\201 \320\277\321\200\320\276\320\265\320\272\321\202\320\276\320\274 \320\270 \321\200\320\265\320\260\320\273\320\270\320\267\320\260\321\206\320\270\321\217 CRUD-\320\276\320\277\320\265\321\200\320\260\321\206\320\270\320\271/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs" @@ -34,7 +34,13 @@ public async Task>> Get(Cancella [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById([FromRoute] Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var employee = await employeeRepository.GetById(id, ct); + + if (employee == null) + return NotFound(new ProblemDetails { Title = "Сотрудник не найден" }); + + var model = Mapper.ToEmployeeResponse(employee); + return Ok(model); } /// @@ -45,7 +51,21 @@ public async Task> GetById([FromRoute] Guid id, C [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create([FromBody] EmployeeCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + + var role = await roleRepository.GetById(request.RoleId, ct); + if (role == null) + return BadRequest(new ProblemDetails { Title = $"Роль с id '{request.RoleId}' не найдена." }); + + var employee = Mapper.ToEmployee(request, role); + + await employeeRepository.Add(employee, ct); + + var response = Mapper.ToEmployeeResponse(employee); + + return CreatedAtAction(nameof(GetById), new { id = employee.Id }, response); } /// @@ -60,7 +80,32 @@ public async Task> Update( [FromBody] EmployeeUpdateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var employee = await employeeRepository.GetById(id, ct); + if (employee == null) + return NotFound(new ProblemDetails { Title = "Сотрудник не найден" }); + + var role = await roleRepository.GetById(request.RoleId, ct); + if (role == null) + return BadRequest($"Роль с id '{request.RoleId}' не найдена."); + + employee.FirstName = request.FirstName; + employee.LastName = request.LastName; + employee.Email = request.Email; + employee.Role = role; + + try + { + await employeeRepository.Update(employee, ct); + } + catch (EntityNotFoundException) + { + return NotFound(new ProblemDetails { Title = "Сотрудник не найден" }); + } + + return Ok(Mapper.ToEmployeeResponse(employee)); } /// @@ -73,6 +118,14 @@ public async Task Delete( [FromRoute] Guid id, CancellationToken ct) { - throw new NotImplementedException(); + try + { + await employeeRepository.Delete(id, ct); + return NoContent(); + } + catch (EntityNotFoundException) + { + return NotFound(new ProblemDetails { Title = "Сотрудник не найден" }); + } } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" index 778473901..67e71b9ac 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs" @@ -6,7 +6,7 @@ namespace PromoCodeFactory.Core.Abstractions.Repositories; public interface IRepository where T : BaseEntity { - Task> GetAll(bool withIncludes = false, CancellationToken ct = default); + Task> GetAll(CancellationToken ct = default, bool withIncludes = false); Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default); diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.Designer.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.Designer.cs" new file mode 100644 index 000000000..f9053e7a4 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.Designer.cs" @@ -0,0 +1,275 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PromoCodeFactory.DataAccess; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + [DbContext(typeof(PromoCodeFactoryDbContext))] + [Migration("20260319175216_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("CustomerPreference", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PreferencesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PreferencesId"); + + b.HasIndex("PreferencesId"); + + b.ToTable("CustomerPreferences", (string)null); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("PromoCodeId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PromoCodeId"); + + b.HasIndex("CustomerId", "PromoCodeId") + .IsUnique(); + + b.ToTable("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartnerManagerId"); + + b.HasIndex("PreferenceId"); + + b.ToTable("PromoCodes"); + }); + + modelBuilder.Entity("CustomerPreference", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", null) + .WithMany() + .HasForeignKey("PreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("PromoCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Employee", "PartnerManager") + .WithMany() + .HasForeignKey("PartnerManagerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", "Preference") + .WithMany() + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartnerManager"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Navigation("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Navigation("CustomerPromoCodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.cs" new file mode 100644 index 000000000..a550cf185 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/20260319175216_InitialCreate.cs" @@ -0,0 +1,217 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + LastName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Preferences", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Preferences", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Roles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 500, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Roles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CustomerPreferences", + columns: table => new + { + CustomersId = table.Column(type: "TEXT", nullable: false), + PreferencesId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerPreferences", x => new { x.CustomersId, x.PreferencesId }); + table.ForeignKey( + name: "FK_CustomerPreferences_Customers_CustomersId", + column: x => x.CustomersId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPreferences_Preferences_PreferencesId", + column: x => x.PreferencesId, + principalTable: "Preferences", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Employees", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + FirstName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + LastName = table.Column(type: "TEXT", maxLength: 50, nullable: false), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Employees", x => x.Id); + table.ForeignKey( + name: "FK_Employees_Roles_RoleId", + column: x => x.RoleId, + principalTable: "Roles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PromoCodes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Code = table.Column(type: "TEXT", maxLength: 256, nullable: false), + ServiceInfo = table.Column(type: "TEXT", maxLength: 256, nullable: false), + BeginDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: false), + PartnerName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + PartnerManagerId = table.Column(type: "TEXT", nullable: false), + PreferenceId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PromoCodes", x => x.Id); + table.ForeignKey( + name: "FK_PromoCodes_Employees_PartnerManagerId", + column: x => x.PartnerManagerId, + principalTable: "Employees", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PromoCodes_Preferences_PreferenceId", + column: x => x.PreferenceId, + principalTable: "Preferences", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CustomerPromoCodes", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + CustomerId = table.Column(type: "TEXT", nullable: false), + PromoCodeId = table.Column(type: "TEXT", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false), + AppliedAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerPromoCodes", x => x.Id); + table.ForeignKey( + name: "FK_CustomerPromoCodes_Customers_CustomerId", + column: x => x.CustomerId, + principalTable: "Customers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPromoCodes_PromoCodes_PromoCodeId", + column: x => x.PromoCodeId, + principalTable: "PromoCodes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPreferences_PreferencesId", + table: "CustomerPreferences", + column: "PreferencesId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_CustomerId", + table: "CustomerPromoCodes", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_CustomerId_PromoCodeId", + table: "CustomerPromoCodes", + columns: new[] { "CustomerId", "PromoCodeId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCodes_PromoCodeId", + table: "CustomerPromoCodes", + column: "PromoCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Employees_RoleId", + table: "Employees", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCodes_PartnerManagerId", + table: "PromoCodes", + column: "PartnerManagerId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCodes_PreferenceId", + table: "PromoCodes", + column: "PreferenceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CustomerPreferences"); + + migrationBuilder.DropTable( + name: "CustomerPromoCodes"); + + migrationBuilder.DropTable( + name: "Customers"); + + migrationBuilder.DropTable( + name: "PromoCodes"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "Preferences"); + + migrationBuilder.DropTable( + name: "Roles"); + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" new file mode 100644 index 000000000..f53a7b6f7 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Migrations/PromoCodeFactoryDbContextModelSnapshot.cs" @@ -0,0 +1,272 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using PromoCodeFactory.DataAccess; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + [DbContext(typeof(PromoCodeFactoryDbContext))] + partial class PromoCodeFactoryDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.3"); + + modelBuilder.Entity("CustomerPreference", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PreferencesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PreferencesId"); + + b.HasIndex("PreferencesId"); + + b.ToTable("CustomerPreferences", (string)null); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("PromoCodeId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.HasIndex("PromoCodeId"); + + b.HasIndex("CustomerId", "PromoCodeId") + .IsUnique(); + + b.ToTable("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PartnerManagerId"); + + b.HasIndex("PreferenceId"); + + b.ToTable("PromoCodes"); + }); + + modelBuilder.Entity("CustomerPreference", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", null) + .WithMany() + .HasForeignKey("PreferencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany("CustomerPromoCodes") + .HasForeignKey("PromoCodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Employee", "PartnerManager") + .WithMany() + .HasForeignKey("PartnerManagerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Preference", "Preference") + .WithMany() + .HasForeignKey("PreferenceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PartnerManager"); + + b.Navigation("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Navigation("CustomerPromoCodes"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Navigation("CustomerPromoCodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" index 6b123ea6c..a2ed63355 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/PromoCodeFactoryDbContext.cs" @@ -1,4 +1,6 @@ using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.DataAccess; @@ -9,11 +11,143 @@ public PromoCodeFactoryDbContext(DbContextOptions opt { } - //TODO: Добавить DbSet сущности + public DbSet Employees => Set(); + public DbSet Roles => Set(); + + public DbSet Customers => Set(); + public DbSet Preferences => Set(); + public DbSet PromoCodes => Set(); + public DbSet CustomerPromoCodes => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { - //TODO: Добавить маппинг моделей + // Role + modelBuilder.Entity(r => + { + r.HasKey(x => x.Id); + + r.Property(x => x.Name) + .IsRequired() + .HasMaxLength(100); + + r.Property(x => x.Description) + .HasMaxLength(500); + }); + + // Employee + modelBuilder.Entity(e => + { + e.HasKey(x => x.Id); + + e.Property(x => x.FirstName) + .IsRequired() + .HasMaxLength(50); + + e.Property(x => x.LastName) + .IsRequired() + .HasMaxLength(50); + + e.Property(x => x.Email) + .IsRequired() + .HasMaxLength(256); + + e.HasOne(x => x.Role) + .WithMany() + .IsRequired(); + }); + + // Preference + modelBuilder.Entity(p => + { + p.HasKey(x => x.Id); + + p.Property(x => x.Name) + .IsRequired() + .HasMaxLength(100); + }); + + // Customer + modelBuilder.Entity(c => + { + c.HasKey(x => x.Id); + + c.Property(x => x.FirstName) + .IsRequired() + .HasMaxLength(50); + + c.Property(x => x.LastName) + .IsRequired() + .HasMaxLength(50); + + c.Property(x => x.Email) + .IsRequired() + .HasMaxLength(256); + + c.HasMany(x => x.Preferences) + .WithMany(x => x.Customers) + .UsingEntity(j => + { + j.ToTable("CustomerPreferences"); + }); + + c.HasMany(x => x.CustomerPromoCodes) + .WithOne() + .HasForeignKey(x => x.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // PromoCode + modelBuilder.Entity(p => + { + p.HasKey(x => x.Id); + + p.Property(x => x.Code) + .IsRequired() + .HasMaxLength(256); + + p.Property(x => x.ServiceInfo) + .IsRequired() + .HasMaxLength(256); + + p.Property(x => x.PartnerName) + .IsRequired() + .HasMaxLength(256); + + p.Property(x => x.BeginDate).IsRequired(); + p.Property(x => x.EndDate).IsRequired(); + + p.HasOne(x => x.PartnerManager) + .WithMany() + .IsRequired(); + + p.HasOne(x => x.Preference) + .WithMany() + .IsRequired(); + + p.HasMany(x => x.CustomerPromoCodes) + .WithOne() + .HasForeignKey(x => x.PromoCodeId) + .OnDelete(DeleteBehavior.Cascade); + }); + + // CustomerPromoCode (таблица выдачи/использования промокодов клиентами) + modelBuilder.Entity(cpc => + { + cpc.HasKey(x => x.Id); + + cpc.Property(x => x.CreatedAt) + .IsRequired(); + + cpc.Property(x => x.AppliedAt); + + // Индексы + cpc.HasIndex(x => x.CustomerId); + cpc.HasIndex(x => x.PromoCodeId); + + // Уникальность промро + cpc.HasIndex(x => new { x.CustomerId, x.PromoCodeId }) + .IsUnique(); + }); base.OnModelCreating(modelBuilder); } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" index 748b927b3..2b96e9256 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs" @@ -1,6 +1,8 @@ using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Exceptions; namespace PromoCodeFactory.DataAccess.Repositories; @@ -8,39 +10,85 @@ internal class EfRepository(PromoCodeFactoryDbContext context) : IRepository< { protected virtual IQueryable ApplyIncludes(IQueryable query) => query; - public Task Add(T entity, CancellationToken ct) + public async Task Add(T entity, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + await context.Set().AddAsync(entity, ct); + await context.SaveChangesAsync(ct); } - public Task Delete(Guid id, CancellationToken ct) + public async Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + var entity = await context.Set().FirstOrDefaultAsync(x => x.Id == id, ct); + if (entity == null) + throw new EntityNotFoundException(id); + + context.Set().Remove(entity); + await context.SaveChangesAsync(ct); } - public Task> GetAll(bool withIncludes = false, CancellationToken ct = default) + public async Task> GetAll(CancellationToken ct = default, bool withIncludes = false) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + IQueryable query = context.Set().AsNoTracking(); + if (withIncludes) + query = ApplyIncludes(query); + + var list = await query.ToListAsync(ct); + return list; } - public Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default) + public async Task GetById(Guid id, bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + IQueryable query = context.Set().AsNoTracking(); + if (withIncludes) + query = ApplyIncludes(query); + + return await query.FirstOrDefaultAsync(x => x.Id == id, ct); } - public Task> GetByRangeId(IEnumerable ids, bool withIncludes = false, CancellationToken ct = default) + public async Task> GetByRangeId(IEnumerable ids, bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + var idList = ids as ICollection ?? ids.ToList(); + + IQueryable query = context.Set().AsNoTracking(); + if (withIncludes) + query = ApplyIncludes(query); + + var list = await query.Where(x => idList.Contains(x.Id)).ToListAsync(ct); + return list; } - public Task> GetWhere(Expression> predicate, bool withIncludes = false, CancellationToken ct = default) + public async Task> GetWhere(Expression> predicate, bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + IQueryable query = context.Set().AsNoTracking().Where(predicate); + if (withIncludes) + query = ApplyIncludes(query); + + var list = await query.ToListAsync(ct); + return list; } - public Task Update(T entity, CancellationToken ct) + public async Task Update(T entity, CancellationToken ct) { - throw new NotImplementedException(); + ct.ThrowIfCancellationRequested(); + + var exists = await context.Set().AsNoTracking().AnyAsync(x => x.Id == entity.Id, ct); + if (!exists) + throw new EntityNotFoundException(entity.Id); + + context.Set().Update(entity); + await context.SaveChangesAsync(ct); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" index 3a93c83b7..1a8a97803 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs" @@ -15,7 +15,7 @@ public InMemoryRepository(IEnumerable data) _data = new ConcurrentDictionary(data.Select(e => new KeyValuePair(e.Id, e))); } - public Task> GetAll(bool withIncludes = false, CancellationToken ct = default) + public Task> GetAll(CancellationToken ct = default, bool withIncludes = false) { return Task.FromResult((IReadOnlyCollection)_data.Values); } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" index 1fbcd4f8f..853a286b6 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs" @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Customers; namespace PromoCodeFactory.WebHost.Controllers; @@ -6,7 +8,12 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Клиенты /// -public class CustomersController : BaseController +public class CustomersController( + IRepository customerRepository, + IRepository preferenceRepository, + IRepository promoCodeRepository//, + //IRepository customerPromoCodeRepository +) : BaseController { /// /// Получить данные всех клиентов @@ -15,7 +22,9 @@ public class CustomersController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var customers = await customerRepository.GetAll(withIncludes: true, ct: ct); + var models = customers.Select(CustomersMapper.ToCustomerShortResponse).ToList(); + return Ok(models); } /// @@ -26,7 +35,32 @@ public async Task>> Get(Cancella [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await customerRepository.GetById(id, withIncludes: true, ct: ct); + if (customer == null) + return NotFound(); + + var promoCodeIds = customer.CustomerPromoCodes.Select(x => x.PromoCodeId).Distinct().ToList(); + + var promoCodes = promoCodeIds.Count == 0 + ? Array.Empty() + : await promoCodeRepository.GetByRangeId(promoCodeIds, withIncludes: true, ct: ct); + + var promoCodeById = promoCodes.ToDictionary(x => x.Id, x => x); + + var promoCodeResponses = customer.CustomerPromoCodes + .Where(link => promoCodeById.ContainsKey(link.PromoCodeId)) + .Select(link => CustomerPromoCodesMapper.ToCustomerPromoCodeResponse(promoCodeById[link.PromoCodeId], link)) + .ToList(); + + var response = new CustomerResponse( + customer.Id, + customer.FirstName, + customer.LastName, + customer.Email, + customer.Preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList(), + promoCodeResponses); + + return Ok(response); } /// @@ -37,7 +71,20 @@ public async Task> GetById(Guid id, CancellationT [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create([FromBody] CustomerCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var preferences = await preferenceRepository.GetByRangeId(request.PreferenceIds, withIncludes: false, ct: ct); + if (preferences.Count != request.PreferenceIds.Distinct().Count()) + return BadRequest("PreferenceId не найдены."); + + var customer = CustomersMapper.ToCustomer(request, preferences); + + await customerRepository.Add(customer, ct); + + var response = CustomersMapper.ToCustomerShortResponse(customer); + + return CreatedAtAction(nameof(GetById), new { id = customer.Id }, response); } /// @@ -52,7 +99,29 @@ public async Task> Update( [FromBody] CustomerUpdateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var customer = await customerRepository.GetById(id, withIncludes: true, ct: ct); + if (customer == null) + return NotFound(); + + var preferences = await preferenceRepository.GetByRangeId(request.PreferenceIds, withIncludes: false, ct: ct); + if (preferences.Count != request.PreferenceIds.Distinct().Count()) + return BadRequest("PreferenceId не найдены."); + + CustomersMapper.ApplyUpdate(customer, request, preferences); + + try + { + await customerRepository.Update(customer, ct); + } + catch (EntityNotFoundException) + { + return NotFound(); + } + + return Ok(CustomersMapper.ToCustomerShortResponse(customer)); } /// @@ -63,6 +132,14 @@ public async Task> Update( [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + try + { + await customerRepository.Delete(id, ct); + return NoContent(); + } + catch (EntityNotFoundException) + { + return NotFound(); + } } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" index 17f8880a2..3582b60e0 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs" @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Preferences; namespace PromoCodeFactory.WebHost.Controllers; @@ -6,7 +8,7 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Предпочтения /// -public class PreferencesController : BaseController +public class PreferencesController(IRepository preferenceRepository) : BaseController { /// /// Получить все доступные предпочтения @@ -15,6 +17,8 @@ public class PreferencesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var preferences = await preferenceRepository.GetAll(ct); + var models = preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList(); + return Ok(models); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" index e55d5b0e8..db72b0a1c 100644 --- "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Controllers/PromoCodesController.cs" @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.PromoCodes; namespace PromoCodeFactory.WebHost.Controllers; @@ -6,7 +8,13 @@ namespace PromoCodeFactory.WebHost.Controllers; /// /// Промокоды /// -public class PromoCodesController : BaseController +public class PromoCodesController( + IRepository promoCodeRepository, + IRepository employeeRepository, + IRepository preferenceRepository, + IRepository customerRepository, + IRepository customerPromoCodeRepository +) : BaseController { /// /// Получить все промокоды @@ -15,7 +23,9 @@ public class PromoCodesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var promoCodes = await promoCodeRepository.GetAll(withIncludes: true, ct: ct); + var models = promoCodes.Select(PromoCodesMapper.ToPromoCodeShortResponse).ToList(); + return Ok(models); } /// @@ -26,7 +36,11 @@ public async Task>> Get(Cancell [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var promoCode = await promoCodeRepository.GetById(id, withIncludes: true, ct: ct); + if (promoCode == null) + return NotFound(); + + return Ok(PromoCodesMapper.ToPromoCodeShortResponse(promoCode)); } /// @@ -38,7 +52,55 @@ public async Task> GetById(Guid id, Cancell [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> Create(PromoCodeCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var partnerManager = await employeeRepository.GetById(request.PartnerManagerId, withIncludes: true, ct: ct); + if (partnerManager == null) + return NotFound($"PartnerManager с id '{request.PartnerManagerId}' не найден."); + + var preference = await preferenceRepository.GetById(request.PreferenceId, withIncludes: false, ct: ct); + if (preference == null) + return NotFound($"Preference с id '{request.PreferenceId}' не найден."); + + if (request.EndDate < request.BeginDate) + return BadRequest("Дата окончания должна быть бюольше либо равна дате начала."); + + var promoCode = new PromoCode + { + Id = Guid.NewGuid(), + Code = request.Code, + ServiceInfo = request.ServiceInfo, + PartnerName = request.PartnerName, + BeginDate = request.BeginDate, + EndDate = request.EndDate, + PartnerManager = partnerManager, + Preference = preference + }; + + await promoCodeRepository.Add(promoCode, ct); + + // Выдаём промокод клиентам с указанным предпочтением + var customers = await customerRepository.GetWhere( + c => c.Preferences.Any(p => p.Id == request.PreferenceId), + withIncludes: false, + ct: ct); + + foreach (var customer in customers) + { + var link = new CustomerPromoCode + { + Id = Guid.NewGuid(), + CustomerId = customer.Id, + PromoCodeId = promoCode.Id, + CreatedAt = DateTimeOffset.UtcNow, + AppliedAt = null + }; + + await customerPromoCodeRepository.Add(link, ct); + } + + return CreatedAtAction(nameof(GetById), new { id = promoCode.Id }, PromoCodesMapper.ToPromoCodeShortResponse(promoCode)); } /// @@ -53,6 +115,42 @@ public async Task Apply( [FromBody] PromoCodeApplyRequest request, CancellationToken ct) { - throw new NotImplementedException(); + if (!ModelState.IsValid) + return BadRequest(ModelState); + + // промокод существует + var promoCode = await promoCodeRepository.GetById(id, withIncludes: false, ct: ct); + if (promoCode == null) + return NotFound(); + + // клиент существует + var customer = await customerRepository.GetById(request.CustomerId, withIncludes: false, ct: ct); + if (customer == null) + return NotFound(); + + var links = await customerPromoCodeRepository.GetWhere( + x => x.CustomerId == request.CustomerId && x.PromoCodeId == id, + withIncludes: false, + ct: ct); + + var link = links.FirstOrDefault(); + if (link == null) + return NotFound(); + + if (link.AppliedAt != null) + return BadRequest("Промо код уже был применен к данному пользователю."); + + link.AppliedAt = DateTimeOffset.UtcNow; + + try + { + await customerPromoCodeRepository.Update(link, ct); + } + catch (EntityNotFoundException) + { + return NotFound(); + } + + return NoContent(); } } diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodesMapper.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodesMapper.cs" new file mode 100644 index 000000000..26d5dda78 --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomerPromoCodesMapper.cs" @@ -0,0 +1,22 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models.PromoCodes; + +namespace PromoCodeFactory.WebHost.Mapping; + +public static class CustomerPromoCodesMapper +{ + public static CustomerPromoCodeResponse ToCustomerPromoCodeResponse(PromoCode promoCode, CustomerPromoCode link) + { + return new CustomerPromoCodeResponse( + link.Id, + promoCode.Code, + promoCode.ServiceInfo, + promoCode.PartnerName, + promoCode.BeginDate, + promoCode.EndDate, + promoCode.PartnerManager.Id, + promoCode.Preference.Id, + link.CreatedAt, + link.AppliedAt); + } +} diff --git "a/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomersMapper.cs" "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomersMapper.cs" new file mode 100644 index 000000000..4a8f24c6d --- /dev/null +++ "b/Homeworks/03 \320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\265\320\275\320\270\320\265 \320\261\320\260\320\267\321\213 \320\264\320\260\320\275\320\275\321\213\321\205 \321\207\320\265\321\200\320\265\320\267 Entity Framework/src/PromoCodeFactory.WebHost/Mapping/CustomersMapper.cs" @@ -0,0 +1,37 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models.Customers; + +namespace PromoCodeFactory.WebHost.Mapping; + +public static class CustomersMapper +{ + public static CustomerShortResponse ToCustomerShortResponse(Customer customer) + { + return new CustomerShortResponse( + customer.Id, + customer.FirstName, + customer.LastName, + customer.Email, + customer.Preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList()); + } + + public static Customer ToCustomer(CustomerCreateRequest request, IReadOnlyCollection preferences) + { + return new Customer + { + Id = Guid.NewGuid(), + FirstName = request.FirstName, + LastName = request.LastName, + Email = request.Email, + Preferences = preferences.ToList() + }; + } + + public static void ApplyUpdate(Customer customer, CustomerUpdateRequest request, IReadOnlyCollection preferences) + { + customer.FirstName = request.FirstName; + customer.LastName = request.LastName; + customer.Email = request.Email; + customer.Preferences = preferences.ToList(); + } +}