From 579302684712013ead1e2e56baaf8a1fee458fc5 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 14 Mar 2026 01:37:09 +0300 Subject: [PATCH 1/6] =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=202=20=D1=83=D1=80=D0=BE=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/InMemoryRepository.cs" | 29 +++++-- .../Controllers/EmployeesController.cs" | 75 +++++++++++++++++-- 2 files changed, 91 insertions(+), 13 deletions(-) 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..4997dd973 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,5 +1,6 @@ using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Exceptions; using System.Collections.Concurrent; namespace PromoCodeFactory.DataAccess.Repositories; @@ -13,27 +14,43 @@ public InMemoryRepository(IEnumerable data) _data = new ConcurrentDictionary(data.Select(e => new KeyValuePair(e.Id, e))); } public Task> GetAll(CancellationToken ct) - { + { return Task.FromResult((IReadOnlyCollection)_data.Values); } public Task GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + return _data.TryGetValue(id, out var result) ? + Task.FromResult((T?)result) : + Task.FromResult((T?)null); } public Task Add(T entity, CancellationToken ct) { - throw new NotImplementedException(); + if (_data.TryAdd(entity.Id, entity)) + return Task.CompletedTask; + + throw new InvalidOperationException($"Не удалось добавить элемент типа \"{typeof(T).Name}\" с Id {entity.Id}"); } public Task Update(T entity, CancellationToken ct) { - throw new NotImplementedException(); + if (_data.ContainsKey(entity.Id)) + { + if (_data.TryUpdate(entity.Id, entity, _data[entity.Id])) + return Task.CompletedTask; + + throw new InvalidOperationException($"Не удалось обновить элемент типа \"{typeof(T).Name}\" с Id {entity.Id}"); + } + + throw new EntityNotFoundException(entity.GetType(), entity.Id); } public Task Delete(Guid id, CancellationToken ct) - { - throw new NotImplementedException(); + { + if (_data.TryRemove(id, out _)) + return Task.CompletedTask; + + throw new EntityNotFoundException(typeof(T), id); } } 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..c90dfc148 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" @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models; @@ -34,7 +35,11 @@ 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); + + return employee is null ? + NotFound(new ProblemDetails { Title = "Ошибка получения", Detail = $"Сотрудник с таким id = \"{id}\" не найден." }) : + Ok( Mapper.ToEmployeeResponse(employee) ); } /// @@ -44,9 +49,23 @@ public async Task> GetById([FromRoute] Guid id, C [ProducesResponseType(typeof(EmployeeResponse), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create([FromBody] EmployeeCreateRequest request, CancellationToken ct) - { - throw new NotImplementedException(); - } + { + var roles = await roleRepository.GetAll(ct); + var role = roles.FirstOrDefault(r => r.Id == request.RoleId); + + if (role is null) + return BadRequest(new ProblemDetails { Title = "Ошибка создания", Detail = $"Роль с указанным id = \"{request.RoleId}\" не найдена." }); + try + { + await employeeRepository.Add(Mapper.ToEmployee(request, role), ct); + } + catch (InvalidOperationException ex) + { + return BadRequest(new ProblemDetails { Title = "Ошибка создания", Detail = ex.Message } ); + } + + return Created(); + } /// /// Обновить сотрудника @@ -59,8 +78,36 @@ public async Task> Update( [FromRoute] Guid id, [FromBody] EmployeeUpdateRequest request, CancellationToken ct) - { - throw new NotImplementedException(); + { + var employee = await employeeRepository.GetById(id, ct); + + if (employee is null) + return NotFound(new ProblemDetails { Title = "Ошибка обновления", Detail = $"Сотрудник с таким id = \"{id}\" не найден." } ); + + var roles = await roleRepository.GetAll(ct); + var role = roles.FirstOrDefault(r => r.Id == request.RoleId); + if (role is null) + return BadRequest(new ProblemDetails { Title = "Ошибка обновления", Detail = $"Некорректный id = \"{id}\" роли. Роль с таким id не найдена." } ); + + employee.FirstName = request.FirstName; + employee.LastName = request.LastName; + employee.Email = request.Email; + employee.Role = role; + + try + { + await employeeRepository.Update(employee, ct); + } + catch (InvalidOperationException ex) + { + return BadRequest(new ProblemDetails { Title = "Ошибка обновления", Detail = ex.Message }); + } + catch (EntityNotFoundException ex) + { + return NotFound(new ProblemDetails { Title = "Ошибка обновления", Detail = ex.Message }); + } + + return Ok(Mapper.ToEmployeeResponse(employee)); } /// @@ -73,6 +120,20 @@ public async Task Delete( [FromRoute] Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var employee = await employeeRepository.GetById(id, ct); + + if (employee is null) + return NotFound(new ProblemDetails { Title = "Ошибка удаления", Detail = $"Сотрудник с таким id = \"{id}\" не найден." } ); + + try + { + await employeeRepository.Delete(employee.Id, ct); + } + catch (EntityNotFoundException ex) + { + return NotFound(new ProblemDetails { Title = "Ошибка удаления", Detail = ex.Message }); + } + + return NoContent(); } } From f034be87698ca2ddff324d2861623be0b26ca608 Mon Sep 17 00:00:00 2001 From: Yurii <139982368+Yurii-Avdanin@users.noreply.github.com> Date: Sat, 9 May 2026 03:24:22 +0300 Subject: [PATCH 2/6] =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260505190726_InitialCreate.Designer.cs" | 272 ++++++++++++++++++ .../20260505190726_InitialCreate.cs" | 211 ++++++++++++++ ...PromoCodeFactoryDbContextModelSnapshot.cs" | 269 +++++++++++++++++ .../PromoCodeFactoryDbContext.cs" | 73 +++++ .../Repositories/EfRepository.cs" | 65 +++-- .../Controllers/CustomersController.cs" | 79 ++++- .../Controllers/PreferencesController.cs" | 11 +- .../Controllers/PromoCodesController.cs" | 87 +++++- .../Mapping/CustomersMapper.cs" | 50 ++++ .../Mapping/PromoCodesMapper.cs" | 16 ++ 10 files changed, 1106 insertions(+), 27 deletions(-) create mode 100644 "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/20260505190726_InitialCreate.Designer.cs" create mode 100644 "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/20260505190726_InitialCreate.cs" create mode 100644 "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" create mode 100644 "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" 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/20260505190726_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/20260505190726_InitialCreate.Designer.cs" new file mode 100644 index 000000000..70be47492 --- /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/20260505190726_InitialCreate.Designer.cs" @@ -0,0 +1,272 @@ +// +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("20260505190726_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("CustomerPreference"); + }); + + 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("Employee"); + }); + + 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("Role"); + }); + + 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("Customer"); + }); + + 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.ToTable("CustomerPromoCode"); + }); + + 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("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(101) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(100) + .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("PromoCode"); + }); + + 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/20260505190726_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/20260505190726_InitialCreate.cs" new file mode 100644 index 000000000..b66b6a884 --- /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/20260505190726_InitialCreate.cs" @@ -0,0 +1,211 @@ +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: "Customer", + 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_Customer", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Preference", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Preference", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Role", + 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_Role", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CustomerPreference", + columns: table => new + { + CustomersId = table.Column(type: "TEXT", nullable: false), + PreferencesId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerPreference", x => new { x.CustomersId, x.PreferencesId }); + table.ForeignKey( + name: "FK_CustomerPreference_Customer_CustomersId", + column: x => x.CustomersId, + principalTable: "Customer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPreference_Preference_PreferencesId", + column: x => x.PreferencesId, + principalTable: "Preference", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Employee", + 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_Employee", x => x.Id); + table.ForeignKey( + name: "FK_Employee_Role_RoleId", + column: x => x.RoleId, + principalTable: "Role", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "PromoCode", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Code = table.Column(type: "TEXT", maxLength: 101, 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: 100, nullable: false), + PartnerManagerId = table.Column(type: "TEXT", nullable: false), + PreferenceId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PromoCode", x => x.Id); + table.ForeignKey( + name: "FK_PromoCode_Employee_PartnerManagerId", + column: x => x.PartnerManagerId, + principalTable: "Employee", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_PromoCode_Preference_PreferenceId", + column: x => x.PreferenceId, + principalTable: "Preference", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "CustomerPromoCode", + 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_CustomerPromoCode", x => x.Id); + table.ForeignKey( + name: "FK_CustomerPromoCode_Customer_CustomerId", + column: x => x.CustomerId, + principalTable: "Customer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPromoCode_PromoCode_PromoCodeId", + column: x => x.PromoCodeId, + principalTable: "PromoCode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPreference_PreferencesId", + table: "CustomerPreference", + column: "PreferencesId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCode_CustomerId", + table: "CustomerPromoCode", + column: "CustomerId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCode_PromoCodeId", + table: "CustomerPromoCode", + column: "PromoCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Employee_RoleId", + table: "Employee", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCode_PartnerManagerId", + table: "PromoCode", + column: "PartnerManagerId"); + + migrationBuilder.CreateIndex( + name: "IX_PromoCode_PreferenceId", + table: "PromoCode", + column: "PreferenceId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CustomerPreference"); + + migrationBuilder.DropTable( + name: "CustomerPromoCode"); + + migrationBuilder.DropTable( + name: "Customer"); + + migrationBuilder.DropTable( + name: "PromoCode"); + + migrationBuilder.DropTable( + name: "Employee"); + + migrationBuilder.DropTable( + name: "Preference"); + + migrationBuilder.DropTable( + name: "Role"); + } + } +} 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..bce9f4f08 --- /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,269 @@ +// +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("CustomerPreference"); + }); + + 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("Employee"); + }); + + 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("Role"); + }); + + 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("Customer"); + }); + + 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.ToTable("CustomerPromoCode"); + }); + + 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("Preference"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BeginDate") + .HasColumnType("TEXT"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(101) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(100) + .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("PromoCode"); + }); + + 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..85dfd0b3b 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,7 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.DataAccess; @@ -10,11 +13,81 @@ public PromoCodeFactoryDbContext(DbContextOptions opt } //TODO: Добавить DbSet сущности + #region Administration + public DbSet Role { get; set; } + public DbSet Employee { get; set; } + #endregion + + #region PromoCodeManagement + public DbSet Customer { get; set; } + public DbSet CustomerPromoCode { get; set; } + public DbSet Preference { get; set; } + public DbSet PromoCode { get; set; } + #endregion protected override void OnModelCreating(ModelBuilder modelBuilder) { //TODO: Добавить маппинг моделей + modelBuilder.Entity(RoleConfigure); + modelBuilder.Entity(EmployeeConfigure); + + modelBuilder.Entity(CustomerConfigure); + modelBuilder.Entity(PreferenceConfigure); + modelBuilder.Entity(PromoCodeConfigure); base.OnModelCreating(modelBuilder); } + + /// + /// Описание ограничений сущности "Role" + /// + /// + private void RoleConfigure(EntityTypeBuilder entity) + { + entity.Property(e => e.Name).HasMaxLength(100); + entity.Property(e => e.Description).HasMaxLength(500); + } + + /// + /// Описание ограничений сущности "Employee" + /// + /// + private void EmployeeConfigure(EntityTypeBuilder entity) + { + entity.Property(e => e.FirstName).HasMaxLength(50); + entity.Property(e => e.LastName).HasMaxLength(50); + entity.Property(e => e.Email).HasMaxLength(256); + } + + + /// + /// Описание ограничений сущности "Customer" + /// + /// + private void CustomerConfigure(EntityTypeBuilder entity) + { + entity.Property(e => e.FirstName).HasMaxLength(50); + entity.Property(e => e.LastName).HasMaxLength(50); + entity.Property(e => e.Email).HasMaxLength(256); + } + + /// + /// Описание ограничений сущности "Preference" + /// + /// + private void PreferenceConfigure(EntityTypeBuilder entity) + { + entity.Property(e => e.Name).HasMaxLength(100); + } + + /// + /// Описание ограничений сущности "PromoCode" + /// + /// + private void PromoCodeConfigure(EntityTypeBuilder entity) + { + entity.Property(e => e.Code).HasMaxLength(101); + entity.Property(e => e.ServiceInfo).HasMaxLength(256); + entity.Property(e => e.PartnerName).HasMaxLength(100); + } } 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..777d3b1ad 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,46 +1,77 @@ -using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using System.Linq.Expressions; namespace PromoCodeFactory.DataAccess.Repositories; internal class EfRepository(PromoCodeFactoryDbContext context) : IRepository where T : BaseEntity { - protected virtual IQueryable ApplyIncludes(IQueryable query) => query; + 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(); + await context.Set().AddAsync(entity, ct); + + await context.SaveChangesAsync(ct); } - public Task Delete(Guid id, CancellationToken ct) - { - throw new NotImplementedException(); + public async Task Delete(Guid id, CancellationToken ct) + { + T? entity = await GetById(id, ct: ct); + + if (entity is not null) + { + context.Set().Remove(entity); + + await context.SaveChangesAsync(ct); + } } - public Task> GetAll(bool withIncludes = false, CancellationToken ct = default) + public async Task> GetAll(bool withIncludes = false, CancellationToken ct = default) { - throw new NotImplementedException(); + IQueryable entitys = context.Set(); + if (withIncludes) + entitys = ApplyIncludes(entitys); + + return await entitys.ToListAsync(ct); } - 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(); + var entitys = context.Set().Where(e => e.Id == id); + + if (withIncludes) + entitys = ApplyIncludes(entitys); + + return await entitys.FirstOrDefaultAsync(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(); + var query = context.Set().Where(e => ids.Contains(e.Id)); + + if (withIncludes) + query = ApplyIncludes(query); + + return await query.ToListAsync(ct); } - 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(); + var entitys = context.Set().Where(predicate); + + if (withIncludes) + entitys = ApplyIncludes(entitys); + + return await entitys.ToListAsync(ct); } - public Task Update(T entity, CancellationToken ct) + public async Task Update(T entity, CancellationToken ct) { - throw new NotImplementedException(); + 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.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..0811c4185 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,5 +1,8 @@ using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Mapping; using PromoCodeFactory.WebHost.Models.Customers; +using PromoCodeFactory.WebHost.Models.PromoCodes; namespace PromoCodeFactory.WebHost.Controllers; @@ -8,6 +11,20 @@ namespace PromoCodeFactory.WebHost.Controllers; /// public class CustomersController : BaseController { + private readonly IRepository _customerEfRepository; + private readonly IRepository _promoCodeEfRepository; + private readonly IRepository _preferenceEfRepository; + + public CustomersController( + IRepository customerEfRepository, + IRepository promoCodeEfRepository, + IRepository preferenceEfRepository) + { + _customerEfRepository = customerEfRepository; + _promoCodeEfRepository = promoCodeEfRepository; + _preferenceEfRepository = preferenceEfRepository; + } + /// /// Получить данные всех клиентов /// @@ -15,7 +32,8 @@ public class CustomersController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var listCustomer = await _customerEfRepository.GetAll(true, ct); + return Ok(listCustomer.Select(CustomersMapper.ToCustomerShortResponse)); } /// @@ -26,7 +44,14 @@ public async Task>> Get(Cancella [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> GetById(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await _customerEfRepository.GetById(id, true, ct); + if (customer is null) + return NotFound($"Customer с Id {id} не найден."); + + var promoCodeIds = customer.CustomerPromoCodes.Select(x => x.PromoCodeId).ToList(); + var promoCodes = await _promoCodeEfRepository.GetByRangeId(promoCodeIds, true, ct); + + return Ok(CustomersMapper.ToCustomerResponse(customer, promoCodes)); } /// @@ -37,7 +62,24 @@ public async Task> GetById(Guid id, CancellationT [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] public async Task> Create([FromBody] CustomerCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var preferences = new List(); + foreach (var id in request.PreferenceIds ) + { + var preference = await _preferenceEfRepository.GetById(id, ct: ct); + if (preference is null) + return BadRequest($"Preference с Id {id} не найден."); + + preferences.Add(preference); + } + + var customer = CustomersMapper.ToCustomer(request, preferences); + + await _customerEfRepository.Add(customer, ct); + + return CreatedAtAction( + nameof(Create), + new { id = customer.Id }, + CustomersMapper.ToCustomerShortResponse(customer)); } /// @@ -52,7 +94,28 @@ public async Task> Update( [FromBody] CustomerUpdateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await _customerEfRepository.GetById(id, true, ct); + if (customer is null) + return BadRequest($"Customer с Id {id} не найден."); + + var preferences = new List(); + foreach (var preferenceId in request.PreferenceIds) + { + var preference = await _preferenceEfRepository.GetById(preferenceId, ct: ct); + if (preference is null) + return BadRequest($"Preference с Id {preferenceId} не найден."); + + preferences.Add(preference); + } + + customer.FirstName = request.FirstName; + customer.LastName = request.LastName; + customer.Email = request.Email; + customer.Preferences = preferences; + + await _customerEfRepository.Update(customer, ct); + + return Ok(CustomersMapper.ToCustomerShortResponse(customer)); } /// @@ -63,6 +126,12 @@ public async Task> Update( [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task Delete(Guid id, CancellationToken ct) { - throw new NotImplementedException(); + var customer = await _customerEfRepository.GetById(id, true, ct); + if (customer is null) + return NotFound($"Customer с Id {id} не найден."); + + await _customerEfRepository.Delete(id, ct); + + 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/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..3dd0877ea 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; @@ -8,6 +10,12 @@ namespace PromoCodeFactory.WebHost.Controllers; /// public class PreferencesController : BaseController { + private readonly IRepository _preferenceEfRepository; + + public PreferencesController(IRepository preferenceEfRepository) + { + _preferenceEfRepository = preferenceEfRepository; + } /// /// Получить все доступные предпочтения /// @@ -15,6 +23,7 @@ public class PreferencesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var preferences = await _preferenceEfRepository.GetAll(true, ct); + return Ok(preferences.Select(PreferencesMapper.ToPreferenceShortResponse)); } } 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..71aba30ea 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; @@ -8,6 +10,27 @@ namespace PromoCodeFactory.WebHost.Controllers; /// public class PromoCodesController : BaseController { + + private readonly IRepository _employeeEfRepository; + private readonly IRepository _customerEfRepository; + private readonly IRepository _promoCodeEfRepository; + private readonly IRepository _preferenceEfRepository; + private readonly IRepository _customerPromoCodeEfRepository; + + public PromoCodesController( + IRepository employeeEfRepository, + IRepository customerEfRepository, + IRepository promoCodeEfRepository, + IRepository preferenceEfRepository, + IRepository customerPromoCodeEfRepository + ) + { + _employeeEfRepository = employeeEfRepository; + _customerEfRepository = customerEfRepository; + _promoCodeEfRepository = promoCodeEfRepository; + _preferenceEfRepository = preferenceEfRepository; + _customerPromoCodeEfRepository = customerPromoCodeEfRepository; + } /// /// Получить все промокоды /// @@ -15,7 +38,8 @@ public class PromoCodesController : BaseController [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public async Task>> Get(CancellationToken ct) { - throw new NotImplementedException(); + var promoCodes = await _promoCodeEfRepository.GetAll(true, ct); + return Ok(promoCodes.Select(PromoCodesMapper.ToPromoCodeShortResponse)); } /// @@ -26,7 +50,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 _promoCodeEfRepository.GetById(id, true, ct); + if (promoCode is null) + return NotFound($"PromoCode с Id {id} не найден."); + + return Ok(PromoCodesMapper.ToPromoCodeShortResponse(promoCode)); } /// @@ -38,7 +66,47 @@ public async Task> GetById(Guid id, Cancell [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task> Create(PromoCodeCreateRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var partnerManager = await _employeeEfRepository.GetById(request.PartnerManagerId, true, ct); + if (partnerManager is null) + return NotFound($"Employee с Id {request.PartnerManagerId} не найден."); + + var preference = await _preferenceEfRepository.GetById(request.PreferenceId, true, ct); + if (preference is null) + return NotFound($"Preference с Id {request.PartnerManagerId} не найден."); + + var customerPreference = await _customerEfRepository + .GetWhere(c => c.Preferences.Any(p => p.Id == request.PreferenceId), ct: ct); + + var promoCodeId = Guid.NewGuid(); + + var customerPromoCodes = customerPreference.Select(cp => new CustomerPromoCode + { + Id = Guid.NewGuid(), + CustomerId = cp.Id, + PromoCodeId = promoCodeId, + CreatedAt = DateTime.UtcNow, + AppliedAt = null + }).ToList(); + + var promoCode = new PromoCode + { + Id = promoCodeId, + Code = request.Code, + ServiceInfo = request.ServiceInfo, + BeginDate = request.BeginDate.UtcDateTime, + EndDate = request.EndDate.UtcDateTime, + PartnerName = request.PartnerName, + PartnerManager = partnerManager, + Preference = preference, + CustomerPromoCodes = customerPromoCodes + }; + + await _promoCodeEfRepository.Add(promoCode, ct); + + return CreatedAtAction( + nameof(Create), + new { id = promoCode.Id }, + PromoCodesMapper.ToPromoCodeShortResponse(promoCode)); } /// @@ -53,6 +121,17 @@ public async Task Apply( [FromBody] PromoCodeApplyRequest request, CancellationToken ct) { - throw new NotImplementedException(); + var customerPromoCodes = await _customerPromoCodeEfRepository + .GetWhere(cpc => cpc.PromoCodeId == id && cpc.CustomerId == request.CustomerId, ct: ct); + + var customerPromoCode = customerPromoCodes.FirstOrDefault(); + if (customerPromoCode is null) + return NotFound($"CustomerPromoCode с PromoCodeId {id} и CustomerId {request.CustomerId} не найден."); + + customerPromoCode.AppliedAt = DateTime.UtcNow; + + await _customerPromoCodeEfRepository.Update(customerPromoCode, ct); + + 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/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..09a54c354 --- /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,50 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models.Customers; + +namespace PromoCodeFactory.WebHost.Mapping; + +public static class CustomersMapper +{ + public static Customer ToCustomer(CustomerCreateRequest request, ICollection preferences) + { + return new Customer + { + Id = Guid.NewGuid(), + FirstName = request.FirstName, + LastName = request.LastName, + Email = request.Email, + Preferences = preferences + }; + } + + public static CustomerShortResponse ToCustomerShortResponse(Customer customer) + { + var preferenceShortResponse = customer.Preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList(); + + return new CustomerShortResponse( + Id: customer.Id, + FirstName: customer.FirstName, + LastName: customer.LastName, + Email: customer.Email, + Preferences: preferenceShortResponse); + } + + public static CustomerResponse ToCustomerResponse(Customer customer, IReadOnlyCollection promoCodes) + { + var preferenceShortResponse = customer.Preferences.Select(PreferencesMapper.ToPreferenceShortResponse).ToList(); + var customerPromoCodeResponse = promoCodes.Select(p => + { + var customerPromoCode = customer.CustomerPromoCodes.Single(e => e.PromoCodeId == p.Id); + return PromoCodesMapper.ToCustomerPromoCodeResponse(p, customerPromoCode); + }).ToList(); + + return new CustomerResponse( + Id: customer.Id, + FirstName: customer.FirstName, + LastName: customer.LastName, + Email: customer.Email, + Preferences: preferenceShortResponse, + PromoCodes: customerPromoCodeResponse); + + } +} 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/PromoCodesMapper.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/PromoCodesMapper.cs" index 44f8ce5d3..37bc19d0e 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/Mapping/PromoCodesMapper.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/PromoCodesMapper.cs" @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Http.HttpResults; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.WebHost.Models.PromoCodes; @@ -17,4 +18,19 @@ public static PromoCodeShortResponse ToPromoCodeShortResponse(PromoCode promoCod promoCode.PartnerManager.Id, promoCode.Preference.Id); } + + public static CustomerPromoCodeResponse ToCustomerPromoCodeResponse(PromoCode promoCode, CustomerPromoCode customerPromoCode) + { + return new CustomerPromoCodeResponse( + Id: promoCode.Id, + Code: promoCode.Code, + ServiceInfo: promoCode.ServiceInfo, + PartnerName: promoCode.PartnerName, + BeginDate: promoCode.BeginDate, + EndDate: promoCode.EndDate, + PartnerManagerId: promoCode.PartnerManager.Id, + PreferenceId: promoCode.Preference.Id, + CreatedAt: customerPromoCode.CreatedAt, + AppliedAt: customerPromoCode.AppliedAt); + } } From 4ca0c69c983519745f0121686f982c0c570fa055 Mon Sep 17 00:00:00 2001 From: Yurii <139982368+Yurii-Avdanin@users.noreply.github.com> Date: Wed, 13 May 2026 01:41:22 +0300 Subject: [PATCH 3/6] =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=E2=84=96?= =?UTF-8?q?4=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Partners/SetLimitTests.cs" | 194 +++++++++++++++ .../Controllers/PromoCodes/CreateTests.cs" | 231 ++++++++++++++++++ 2 files changed, 425 insertions(+) diff --git "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" index c84e0d8ac..eaef530a9 100644 --- "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" +++ "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" @@ -1,29 +1,223 @@ +using AwesomeAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.Core.Exceptions; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models.Partners; +using Soenneker.Utils.AutoBogus; + namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners; public class SetLimitTests { + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _partnerLimitsRepositoryMock; + + private readonly PartnersController _partnersController; + + public SetLimitTests() + { + _partnersRepositoryMock = new Mock>(); + _partnerLimitsRepositoryMock = new Mock>(); + _partnersController = new PartnersController(_partnersRepositoryMock.Object, _partnerLimitsRepositoryMock.Object); + } + [Fact] public async Task CreateLimit_WhenPartnerNotFound_ReturnsNotFound() { + #region Arrange + var partnerId = Guid.NewGuid(); + var request = new AutoFaker().Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync((Partner?)null); + #endregion + + #region Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + #endregion + + #region Assert + result.Result.Should().BeOfType(); + var notFoundResult = (NotFoundObjectResult)result.Result; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)notFoundResult.Value; + problemDetails.Title.Should().Be("Partner not found"); + problemDetails.Detail.Should().Be($"Partner with Id {partnerId} not found."); + #endregion } [Fact] public async Task CreateLimit_WhenPartnerBlocked_ReturnsUnprocessableEntity() { + #region Arrange + var partner = new AutoFaker() + .RuleFor(p => p.IsActive, _ => false ).Generate(); + + var request = new AutoFaker().Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(partner.Id, true, It.IsAny())) + .ReturnsAsync(partner); + #endregion + + #region Act + var result = await _partnersController.CreateLimit(partner.Id, request, CancellationToken.None); + #endregion + + #region Assert + result.Result.Should().BeOfType(); + var objectResult = (UnprocessableEntityObjectResult)result.Result; + objectResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)objectResult.Value!; + problemDetails.Title.Should().Be("Partner blocked"); + problemDetails.Detail.Should().Be("Cannot create limit for a blocked partner."); + #endregion } [Fact] public async Task CreateLimit_WhenValidRequest_ReturnsCreatedAndAddsLimit() { + #region Arrange + var endAt = DateTimeOffset.UtcNow.AddDays(30); + var limitAt = 5; + var request = new AutoFaker() + .RuleFor(p => p.Limit, limitAt) + .RuleFor(p => p.EndAt, endAt) + .Generate(); + + var partner = CreatePartnerWithLimit(true); + + _partnersRepositoryMock + .Setup(r => r.GetById(partner.Id, true, It.IsAny())) + .ReturnsAsync(partner); + + _partnersRepositoryMock + .Setup(r => r.Update(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + _partnerLimitsRepositoryMock + .Setup(r => r.Add(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + #endregion + + #region Act + var result = await _partnersController.CreateLimit(partner.Id, request, CancellationToken.None); + #endregion + + #region Assert + result.Result.Should().BeOfType(); + var createdAtActionResult = (CreatedAtActionResult)result.Result; + + createdAtActionResult.Value.Should().BeOfType(); + var partnerPromoCodeLimitResponse = (PartnerPromoCodeLimitResponse)createdAtActionResult.Value; + + partnerPromoCodeLimitResponse.Limit.Should().Be(limitAt); + partnerPromoCodeLimitResponse.EndAt.Should().Be(endAt); + + _partnerLimitsRepositoryMock.Verify(repo => repo.Add(It.IsAny(), It.IsAny()), Times.Once); + #endregion } [Fact] public async Task CreateLimit_WhenValidRequestWithActiveLimits_CancelsOldLimitsAndAddsNew() { + #region Arrange + var endAt = DateTimeOffset.UtcNow.AddDays(30); + var limitAt = 5; + var request = new AutoFaker() + .RuleFor(p => p.Limit, limitAt) + .RuleFor(p => p.EndAt, endAt) + .Generate(); + + var partner = CreatePartnerWithLimit(true); + + _partnersRepositoryMock + .Setup(r => r.GetById(partner.Id, true, It.IsAny())) + .ReturnsAsync(partner); + + _partnersRepositoryMock + .Setup(r => r.Update(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + _partnerLimitsRepositoryMock + .Setup(r => r.Add(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + #endregion + + #region Act + var result = await _partnersController.CreateLimit(partner.Id, request, It.IsAny()); + #endregion + + #region Assert + result.Result.Should().BeOfType(); + var createdAtActionResult = (CreatedAtActionResult)result.Result; + + // Проверяем, что Update был вызван один раз (если выл вызван метод Update, то в коллекции лимитов есть как минимум один элемент) + _partnersRepositoryMock.Verify(repo => repo.Update(It.IsAny(), It.IsAny()), Times.Once); + + // Все лимиты должны быть отменены (CanceledAt == null) => false + partner.PartnerLimits.Any(l => l.CanceledAt == null).Should().BeFalse(); + + createdAtActionResult.Value.Should().BeOfType(); + var partnerPromoCodeLimitResponse = (PartnerPromoCodeLimitResponse)createdAtActionResult.Value; + + partnerPromoCodeLimitResponse.Limit.Should().Be(limitAt); + partnerPromoCodeLimitResponse.EndAt.Should().Be(endAt); + + _partnerLimitsRepositoryMock.Verify(repo => repo.Add(It.IsAny(), It.IsAny()), Times.Once); + #endregion } [Fact] public async Task CreateLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNotFound() { + #region Arrange + var request = new AutoFaker().Generate(); + var partner = CreatePartnerWithLimit(true); + + _partnersRepositoryMock + .Setup(r => r.GetById(partner.Id, true, It.IsAny())) + .ReturnsAsync(partner); + + _partnersRepositoryMock + .Setup(r => r.Update(It.IsAny(), It.IsAny())) + .ThrowsAsync(new EntityNotFoundException(typeof(Partner), partner.Id)); + #endregion + + #region Act + var result = await _partnersController.CreateLimit(partner.Id, request, It.IsAny()); + #endregion + + #region Assert + result.Result.Should().BeOfType(); + + // Проверяем, что Update был вызван один раз + _partnersRepositoryMock.Verify(repo => repo.Update(It.IsAny(), It.IsAny()), Times.Once); + + // Проверяем, что Add не был вызван, т.к. метод прервался на Update + _partnerLimitsRepositoryMock.Verify(repo => repo.Add(It.IsAny(), It.IsAny()), Times.Never); + #endregion + } + + private static Partner CreatePartnerWithLimit(bool isActive, DateTimeOffset? canceledAt = null) + { + var partner = new AutoFaker() + .RuleFor(p => p.IsActive, isActive) + .RuleFor(p => p.PartnerLimits, new List()) + .Generate(); + + partner.PartnerLimits.Add( + new AutoFaker() + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, canceledAt) + .RuleFor(l => l.CreatedAt, DateTimeOffset.UtcNow.AddDays(-5)) + .RuleFor(l => l.EndAt, DateTimeOffset.UtcNow.AddDays(10)) + .Generate()); + + return partner; } } diff --git "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" index 8f66ae5c3..f3307fd4e 100644 --- "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" +++ "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" @@ -1,29 +1,260 @@ +using AwesomeAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models.PromoCodes; +using Soenneker.Utils.AutoBogus; + namespace PromoCodeFactory.UnitTests.WebHost.Controllers.PromoCodes; public class CreateTests { + private readonly Mock> _promoCodesRepositoryMock; + private readonly Mock> _customersRepositoryMock; + private readonly Mock> _customerPromoCodesRepositoryMock; + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _preferencesRepositoryMock; + + private readonly PromoCodesController _promoCodesController; + + public CreateTests() + { + _promoCodesRepositoryMock = new Mock>(); + _customersRepositoryMock = new Mock>(); + _customerPromoCodesRepositoryMock = new Mock>(); + _partnersRepositoryMock = new Mock>(); + _preferencesRepositoryMock = new Mock>(); + + _promoCodesController = new PromoCodesController( + _promoCodesRepositoryMock.Object, + _customersRepositoryMock.Object, + _customerPromoCodesRepositoryMock.Object, + _partnersRepositoryMock.Object, + _preferencesRepositoryMock.Object); + } + [Fact] public async Task Create_WhenPartnerNotFound_ReturnsNotFound() { + #region Arrange + var request = new AutoFaker().Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(request.PartnerId, true, It.IsAny())) + .ReturnsAsync((Partner?)null); + #endregion + + #region Act + var resultPromoCode = await _promoCodesController.Create(request, CancellationToken.None); + #endregion + + #region Assert + resultPromoCode.Result.Should().BeOfType(); + var notFoundResult = (NotFoundObjectResult)resultPromoCode.Result; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)notFoundResult.Value; + problemDetails.Title.Should().Be("Partner not found"); + problemDetails.Detail.Should().Be($"Partner with Id {request.PartnerId} not found."); + #endregion } [Fact] public async Task Create_WhenPreferenceNotFound_ReturnsNotFound() { + #region Arrange + var request = new AutoFaker().Generate(); + + var partner = new AutoFaker() + .RuleFor(p => p.Id, request.PartnerId) + .Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(request.PartnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + _preferencesRepositoryMock + .Setup(r => r.GetById(request.PreferenceId, false, It.IsAny())) + .ReturnsAsync((Preference?)null); + #endregion + + #region Act + var resultPromoCode = await _promoCodesController.Create(request, CancellationToken.None); + #endregion + + #region Assert + resultPromoCode.Result.Should().BeOfType(); + var notFoundResult = (NotFoundObjectResult)resultPromoCode.Result; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)notFoundResult.Value; + problemDetails.Title.Should().Be("Preference not found"); + problemDetails.Detail.Should().Be($"Preference with Id {request.PreferenceId} not found."); + #endregion } [Fact] public async Task Create_WhenNoActiveLimit_ReturnsUnprocessableEntity() { + #region Arrange + var request = new AutoFaker().Generate(); + + var partner = new AutoFaker() + .RuleFor(p => p.Id, request.PartnerId) + .Generate(); + partner.PartnerLimits.Add( + new AutoFaker() + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, DateTimeOffset.UtcNow.AddDays(-4)) + .RuleFor(l => l.CreatedAt, DateTimeOffset.UtcNow.AddDays(-5)) + .RuleFor(l => l.EndAt, DateTimeOffset.UtcNow.AddDays(1)) + .Generate()); + + var preference = new AutoFaker() + .RuleFor(p => p.Id, request.PreferenceId) + .Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(request.PartnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + _preferencesRepositoryMock + .Setup(r => r.GetById(request.PreferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + + _customersRepositoryMock + .Setup(r => r.GetWhere(c => c.Preferences.Any(p => p.Id == request.PreferenceId), false, It.IsAny())) + .ReturnsAsync(new List()); + #endregion + + #region Act + var resultPromoCode = await _promoCodesController.Create(request, CancellationToken.None); + #endregion + + #region Assert + resultPromoCode.Result.Should().BeOfType(); + var objectResult = (ObjectResult)resultPromoCode.Result; + objectResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)objectResult.Value; + problemDetails.Title.Should().Be("No active limit"); + problemDetails.Detail.Should().Be("Partner has no active promo code limit."); + #endregion } [Fact] public async Task Create_WhenLimitExceeded_ReturnsUnprocessableEntity() { + #region Arrange + var request = new AutoFaker().Generate(); + + var partner = new AutoFaker() + .RuleFor(p => p.Id, request.PartnerId) + .Generate(); + + var partnerPromoCodeLimit = new AutoFaker() + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, (DateTimeOffset?)null) + .RuleFor(l => l.CreatedAt, DateTimeOffset.UtcNow.AddDays(-5)) + .RuleFor(l => l.EndAt, DateTimeOffset.UtcNow.AddDays(1)) + .RuleFor(l => l.Limit, 2) + .RuleFor(l => l.IssuedCount, 2) + .Generate(); + + partner.PartnerLimits.Add(partnerPromoCodeLimit); + + var preference = new AutoFaker() + .RuleFor(p => p.Id, request.PreferenceId) + .Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(request.PartnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + _preferencesRepositoryMock + .Setup(r => r.GetById(request.PreferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + + _customersRepositoryMock + .Setup(r => r.GetWhere(c => c.Preferences.Any(p => p.Id == request.PreferenceId), false, It.IsAny())) + .ReturnsAsync(new List()); + #endregion + + #region Act + var resultPromoCode = await _promoCodesController.Create(request, CancellationToken.None); + #endregion + + #region Assert + resultPromoCode.Result.Should().BeOfType(); + var objectResult = (ObjectResult)resultPromoCode.Result; + objectResult.Value.Should().BeOfType(); + var problemDetails = (ProblemDetails)objectResult.Value; + problemDetails.Title.Should().Be("Limit exceeded"); + problemDetails.Detail.Should().Be($"Cannot create promo code. Limit would be exceeded (current: {partnerPromoCodeLimit.IssuedCount}/{partnerPromoCodeLimit.Limit})."); + #endregion } [Fact] public async Task Create_WhenValidRequest_ReturnsCreatedAndIncrementsIssuedCount() { + #region Arrange + var request = new AutoFaker().Generate(); + + var partner = new AutoFaker() + .RuleFor(p => p.Id, request.PartnerId) + .Generate(); + + var activeLimit = new AutoFaker() + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, (DateTimeOffset?)null) + .RuleFor(l => l.CreatedAt, DateTimeOffset.UtcNow.AddDays(-5)) + .RuleFor(l => l.EndAt, DateTimeOffset.UtcNow.AddDays(1)) + .RuleFor(l => l.Limit, 2) + .RuleFor(l => l.IssuedCount, 0) + .Generate(); + + partner.PartnerLimits.Add(activeLimit); + + var preference = new AutoFaker() + .RuleFor(p => p.Id, request.PreferenceId) + .Generate(); + + _partnersRepositoryMock + .Setup(r => r.GetById(request.PartnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + _preferencesRepositoryMock + .Setup(r => r.GetById(request.PreferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + + _customersRepositoryMock + .Setup(r => r.GetWhere(c => c.Preferences.Any(p => p.Id == request.PreferenceId), false, It.IsAny())) + .ReturnsAsync(new List() { new AutoFaker().RuleFor(c => c.Preferences, new List { preference }).Generate() } ); + + _promoCodesRepositoryMock + .Setup(r => r.Add(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + + _partnersRepositoryMock + .Setup(r => r.Update(It.IsAny(), It.IsAny())) + .Returns(Task.CompletedTask); + #endregion + + #region Act + var resultPromoCode = await _promoCodesController.Create(request, CancellationToken.None); + #endregion + + #region Assert + resultPromoCode.Result.Should().BeOfType(); + var createdAtActionResult = (CreatedAtActionResult)resultPromoCode.Result; + + _promoCodesRepositoryMock.Verify(r => r.Add(It.IsAny(), It.IsAny()), Times.Once); + _partnersRepositoryMock.Verify(r => r.Update(It.IsAny(), It.IsAny()), Times.Once); + + createdAtActionResult.Value.Should().BeOfType(); + var promoCodeShortResponse = (PromoCodeShortResponse)createdAtActionResult.Value; + + promoCodeShortResponse.PartnerId.Should().Be(request.PartnerId); + activeLimit.IssuedCount.Should().Be(1); + #endregion } } From 30e48e32176ac9c68f2f9407d1daea5e36668d4f Mon Sep 17 00:00:00 2001 From: Yurii <139982368+Yurii-Avdanin@users.noreply.github.com> Date: Wed, 13 May 2026 09:24:31 +0300 Subject: [PATCH 4/6] =?UTF-8?q?=D0=94=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D1=8F?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=E2=84=965=20(#?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/.dockerignore" | 9 +++++++ .../src/Dockerfile" | 16 ++++++++++++ .../src/docker-compose.yaml" | 25 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 "Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/.dockerignore" create mode 100644 "Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/Dockerfile" create mode 100644 "Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/docker-compose.yaml" diff --git "a/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/.dockerignore" "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/.dockerignore" new file mode 100644 index 000000000..3ca30ab60 --- /dev/null +++ "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/.dockerignore" @@ -0,0 +1,9 @@ +**/*.sqlite +**/bin +**/obj + +**/.vs +**/.dockerignore +**/.editorconfig +**/Dockerfile* +**/docker-compose* diff --git "a/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/Dockerfile" "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/Dockerfile" new file mode 100644 index 000000000..ec7d03900 --- /dev/null +++ "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/Dockerfile" @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +ARG BUILD_CONFIGURATION=Debug +WORKDIR /src +COPY . . +RUN dotnet restore ./PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +RUN dotnet build ./PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj -c $BUILD_CONFIGURATION --no-restore +RUN dotnet publish ./PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false --no-restore --no-build + + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final +LABEL maintainer="Yurii Avdanin" +LABEL description="PromoCode Factory API" +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 8091 +ENTRYPOINT ["dotnet", "PromoCodeFactory.WebHost.dll"] diff --git "a/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/docker-compose.yaml" "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/docker-compose.yaml" new file mode 100644 index 000000000..fbbe54bc6 --- /dev/null +++ "b/Homeworks/05 \320\243\320\277\320\260\320\272\320\276\320\262\320\272\320\260 \320\262 docker/src/docker-compose.yaml" @@ -0,0 +1,25 @@ +volumes: + db: + +services: + promocode-factory-api: + container_name: promocode-factory-api + build: + context: . + dockerfile: Dockerfile + args: + BUILD_CONFIGURATION: Release + + image: promocode-factory-api:1.0.0 + pull_policy: build + + ports: + - "8091:5000" + + volumes: + - db:/app/db + + environment: + - ASPNETCORE_HTTP_PORTS=5000 + - ASPNETCORE_ENVIRONMENT=Development + - ConnectionStrings__PromocodeFactoryDb=Data Source=/app/db/PromoCodeFactoryDb.sqlite From 8c591869230e6cbab3c40a636da796f0112381c0 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 18 May 2026 03:01:38 +0300 Subject: [PATCH 5/6] fix --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3f86cfd70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs +/.gitignore +/.gitignore From 1d055950367873d55c62827a7c5d98a5d38ec6ed Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 18 May 2026 03:09:04 +0300 Subject: [PATCH 6/6] work 6 --- .github/workflows/ci.yml | 52 +++++++++++++++++++ .../docker-compose.yaml" | 15 ++++++ .../src/Dockerfile" | 29 ++++++++--- 3 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 "Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/docker-compose.yaml" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..0e76b52a8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: + - main + + paths: + - "Homeworks/06 Настройка CI/**" + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + TAG: latest + +jobs: + build-test-push: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Convert repository name to lowercase + run: | + REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "IMAGE_NAME=${REPO_LOWER}/promocode-factory-api" >> $GITHUB_ENV + + - name: build of test + uses: docker/build-push-action@v5 + with: + context: "Homeworks/06 Настройка CI/src" + file: "Homeworks/06 Настройка CI/src/Dockerfile" + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} + + - name: Print image name + run: | + echo "Образ успешно собран и запушен:" + echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}" diff --git "a/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/docker-compose.yaml" "b/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/docker-compose.yaml" new file mode 100644 index 000000000..281360d82 --- /dev/null +++ "b/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/docker-compose.yaml" @@ -0,0 +1,15 @@ +services: + promocode-factory-api: + container_name: 'promocode-factory-api' + image: ghcr.io/yurii-avdanin/asp.net/promocode-factory-api:latest + restart: always + ports: + - "8091:8080" + volumes: + - db:/app/db + environment: + - "ConnectionStrings:PromocodeFactoryDb=Filename=/app/db/PromoCodeFactoryDb.sqlite" + - "ASPNETCORE_ENVIRONMENT=Development" + +volumes: + db: \ No newline at end of file diff --git "a/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/src/Dockerfile" "b/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/src/Dockerfile" index df94e514f..614c727b3 100644 --- "a/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/src/Dockerfile" +++ "b/Homeworks/06 \320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\260 CI/src/Dockerfile" @@ -1,18 +1,35 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS restore +WORKDIR /src +COPY PromoCodeFactory.Core/PromoCodeFactory.Core.csproj PromoCodeFactory.Core/PromoCodeFactory.Core.csproj +COPY PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +COPY PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +COPY PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj +RUN dotnet restore PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +RUN dotnet restore PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj + +FROM restore AS test ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY . . -RUN dotnet restore -RUN dotnet build -c $BUILD_CONFIGURATION --no-restore +RUN dotnet build -c $BUILD_CONFIGURATION PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj --no-restore +RUN dotnet test PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj -c $BUILD_CONFIGURATION --no-build --verbosity normal + + +FROM test AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +RUN dotnet build -c $BUILD_CONFIGURATION PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj --no-restore + FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish ./PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj \ - -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false --no-build +WORKDIR /src +RUN dotnet publish PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false --no-restore --no-build + FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final WORKDIR /app COPY --from=publish /app/publish . EXPOSE 8080 -ENTRYPOINT ["dotnet", "PromoCodeFactory.WebHost.dll"] +ENTRYPOINT ["dotnet", "PromoCodeFactory.WebHost.dll"] \ No newline at end of file