From 5ca52b835b12c9cd732e986bc332e3771e61e7a5 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Sun, 14 Sep 2025 22:12:52 +0300 Subject: [PATCH 1/9] =?UTF-8?q?-=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D1=8B=20Cre?= =?UTF-8?q?ate/Update/Detele=20=D0=B2=20EmployeeController.cs=20-=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B2=20=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B9=20-=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Abstractions/Repositories/IRepository.cs | 6 + .../Repositories/InMemoryRepository.cs | 35 +++- .../EmployeeApiTests.cs | 161 ++++++++++++++++++ .../WebHostFixture.cs | 9 + .../Controllers/EmployeesController.cs | 83 ++++++++- Homeworks/Base/src/PromoCodeFactory.sln | 6 + 6 files changed, 295 insertions(+), 5 deletions(-) create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/WebHostFixture.cs diff --git a/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs b/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs index 3fe683e5..c2455aa9 100644 --- a/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs @@ -10,5 +10,11 @@ public interface IRepository where T: BaseEntity Task> GetAllAsync(); Task GetByIdAsync(Guid id); + + Task AddAsync(T entity); + + Task UpdateAsync(T entity); + + Task DeleteAsync(T entity); } } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs index 0f1e04d7..2cda2ae8 100644 --- a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Reflection.PortableExecutable; using System.Threading.Tasks; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; @@ -8,21 +10,48 @@ namespace PromoCodeFactory.DataAccess.Repositories { public class InMemoryRepository: IRepository where T: BaseEntity { - protected IEnumerable Data { get; set; } + protected ICollection Data { get; set; } - public InMemoryRepository(IEnumerable data) + public InMemoryRepository(ICollection data) { Data = data; } public Task> GetAllAsync() { - return Task.FromResult(Data); + return Task.FromResult>(Data); } public Task GetByIdAsync(Guid id) { return Task.FromResult(Data.FirstOrDefault(x => x.Id == id)); } + + /// + public Task AddAsync(T entity) + { + Data.Add(entity); + return Task.FromResult(entity); + } + + /// + public async Task UpdateAsync(T entity) + { + var oldEntity = await GetByIdAsync(entity.Id); + + if(oldEntity == null) + throw new Exception("Entity not found"); + + await DeleteAsync(oldEntity); + await AddAsync(entity); + return entity; + } + + /// + public Task DeleteAsync(T entity) + { + Data.Remove(entity); + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs new file mode 100644 index 00000000..688ed3bd --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs @@ -0,0 +1,161 @@ +using System.Net; +using System.Net.Http.Json; +using PromoCodeFactory.DataAccess.Data; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Tests; + +public class EmployeeApiTests(WebHostFixture factory) : IClassFixture +{ + private readonly HttpClient _client = factory.CreateClient(); + + [Fact] + public void FakeData_Contains_Two_Employees() + { + Assert.Equal(2, FakeDataFactory.Employees.Count); + } + + [Fact] + public void FakeData_Contains_Two_Roles() + { + Assert.Equal(2, FakeDataFactory.Roles.Count); + } + + [Fact] + public async Task EmployeeCreation_Succeed() + { + var employeeRequest = new EmployeeCreationRequest + { + FirstName = "John", + LastName = "Doe", + Email = "jd@ya.ru" + }; + + var response = await _client.PostAsJsonAsync(factory.ApiRoot + "Employees", employeeRequest); + + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + var content = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(content); + Assert.NotEqual(Guid.Empty, content.Id); + Assert.Equal(employeeRequest.Email, content.Email); + Assert.Equal($"{employeeRequest.FirstName} {employeeRequest.LastName}", content.FullName); + } + + [Fact] + public async Task Employee_Fails_On_Wrong_Body() + { + var response = await _client.PostAsJsonAsync(factory.ApiRoot + "Employees", + new + { + FullName = "John Doe", + Email = "mymail" + }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task Employee_Persists_Between_Calls() + { + var employeeRequest = new EmployeeCreationRequest + { + FirstName = "John", + LastName = "Doe", + Email = "jd@ya.ru" + }; + + var response = await _client.PostAsJsonAsync($"{factory.ApiRoot}Employees", employeeRequest); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var createdEmployee = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(createdEmployee); + Assert.NotEqual(Guid.Empty, createdEmployee.Id); + + response = await _client.GetAsync($"{factory.ApiRoot}Employees/{createdEmployee.Id:D}"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var fetchedEmployee = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(fetchedEmployee); + Assert.Equal(createdEmployee.Id, fetchedEmployee.Id); + Assert.Equal(createdEmployee.Email, fetchedEmployee.Email); + Assert.Equal(createdEmployee.FullName, fetchedEmployee.FullName); + Assert.Equal(createdEmployee.Roles, fetchedEmployee.Roles); + Assert.Equal(createdEmployee.AppliedPromocodesCount, fetchedEmployee.AppliedPromocodesCount); + } + + [Fact] + public async Task Employee_Get_Fails_On_Wrong_Id() + { + var response = await _client.GetAsync($"{factory.ApiRoot}Employees/{Guid.NewGuid():D}"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + private async Task GetFirstEmployee() + { + var response = await _client.GetAsync($"{factory.ApiRoot}Employees"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var employees = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(employees); + var employeeShort = employees.FirstOrDefault(); + Assert.NotNull(employeeShort); + + var employeeResponse = await _client.GetAsync($"{factory.ApiRoot}Employees/{employeeShort.Id:D}"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var employeeFull = await employeeResponse.Content.ReadFromJsonAsync(); + Assert.NotNull(employeeFull); + + return employeeFull; + } + + [Fact] + public async Task Employee_Get_Without_Id_Returns_Collection() + { + var response = await _client.GetAsync($"{factory.ApiRoot}Employees"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var employees = await response.Content.ReadFromJsonAsync>(); + Assert.NotNull(employees); + Assert.NotEmpty(employees); + } + + [Fact] + public async Task Employee_Get_Employee_On_Right_Id() + { + var sourceEmployee = await GetFirstEmployee(); + + var response = await _client.GetAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var employee = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(employee); + Assert.Equal(sourceEmployee.Id, employee.Id); + Assert.Equal(sourceEmployee.Email, employee.Email); + Assert.Equal(sourceEmployee.FullName, employee.FullName); + Assert.Equal(sourceEmployee.Roles, employee.Roles); + } + + [Fact] + public async Task Employee_Updates_Existing_Employee() + { + var sourceEmployee = await GetFirstEmployee(); + Assert.NotNull(sourceEmployee); + + sourceEmployee.Email += "+"; + + var response = await _client.PutAsJsonAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}", sourceEmployee); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var updatedEmployee = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(updatedEmployee); + Assert.Equal(updatedEmployee.Email, sourceEmployee.Email); + // Assert.Equal(createdEmployee.Id, fetchedEmployee.Id); + // Assert.Equal(createdEmployee.Email, fetchedEmployee.Email); + // Assert.Equal(createdEmployee.FullName, fetchedEmployee.FullName); + // Assert.Equal(createdEmployee.Roles, fetchedEmployee.Roles); + // Assert.Equal(createdEmployee.AppliedPromocodesCount, fetchedEmployee.AppliedPromocodesCount); + } + +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/WebHostFixture.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/WebHostFixture.cs new file mode 100644 index 00000000..77029096 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/WebHostFixture.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Mvc.Testing; + +namespace PromoCodeFactory.WebHost.Tests; + +public class WebHostFixture() : WebApplicationFactory +{ + public string ApiRoot = "/api/v1/"; + +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs index df1c41dd..ec3bfc37 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.Core.Abstractions.Repositories; @@ -59,16 +60,94 @@ public async Task> GetEmployeeByIdAsync(Guid id) { Id = employee.Id, Email = employee.Email, - Roles = employee.Roles.Select(x => new RoleItemResponse() + Roles = employee.Roles?.Select(x => new RoleItemResponse() { Name = x.Name, Description = x.Description - }).ToList(), + }).ToList() ?? [], FullName = employee.FullName, AppliedPromocodesCount = employee.AppliedPromocodesCount }; return employeeModel; } + + private static EmployeeResponse CreateEmployeeResponse(Employee employee) + { + return new EmployeeResponse + { + Id = employee.Id, + Email = employee.Email, + Roles = employee.Roles?.Select(x => new RoleItemResponse() + { + Name = x.Name, + Description = x.Description + }).ToList() ?? [], + FullName = employee.FullName, + AppliedPromocodesCount = employee.AppliedPromocodesCount + }; + } + + [HttpPost] + public async Task> CreateEmployee(EmployeeCreationRequest creationRequest) + { + if (creationRequest.Email == null + || creationRequest.FirstName == null + || creationRequest.LastName == null) + return BadRequest(); + + var employee = new Employee() + { + Id = Guid.NewGuid(), + FirstName = creationRequest.FirstName, + LastName = creationRequest.LastName, + Email = creationRequest.Email + }; + + await _employeeRepository.AddAsync(employee); + + var savedEmployee = await _employeeRepository.GetByIdAsync(employee.Id); + return savedEmployee != null + ? StatusCode((int)HttpStatusCode.Created, CreateEmployeeResponse(employee)) + : StatusCode((int)HttpStatusCode.InternalServerError); + } + + [HttpDelete("{id:guid}")] + public async Task> DeleteEmployee(Guid id) + { + var employee = await _employeeRepository.GetByIdAsync(id); + if (employee == null) + return NotFound(id); + + await _employeeRepository.DeleteAsync(employee); + return Ok(id); + } + + [HttpPut("{id:guid}")] + public async Task> UpdateEmployee(Guid id, EmployeeUpdateRequest updateRequest) + { + var employee = await _employeeRepository.GetByIdAsync(id); + if (employee == null) + return NotFound(id); + + employee.FirstName = updateRequest.FirstName; + employee.LastName = updateRequest.LastName; + employee.Email = updateRequest.Email; + + await _employeeRepository.UpdateAsync(employee); + + return Ok(CreateEmployeeResponse(employee)); + } + } + + public class EmployeeInformation + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } } + public class EmployeeCreationRequest : EmployeeInformation { } + + public class EmployeeUpdateRequest : EmployeeInformation { } + } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.sln b/Homeworks/Base/src/PromoCodeFactory.sln index 14085bb4..a1dba621 100644 --- a/Homeworks/Base/src/PromoCodeFactory.sln +++ b/Homeworks/Base/src/PromoCodeFactory.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoCodeFactory.DataAccess EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoCodeFactory.Core", "PromoCodeFactory.Core\PromoCodeFactory.Core.csproj", "{FA64A24E-D404-4E7D-89A8-B87E134F697F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PromoCodeFactory.WebHost.Tests", "PromoCodeFactory.WebHost.Tests\PromoCodeFactory.WebHost.Tests.csproj", "{6BA6AF5B-E700-49F2-9DC5-E1471EA632D4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Release|Any CPU.Build.0 = Release|Any CPU + {6BA6AF5B-E700-49F2-9DC5-E1471EA632D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BA6AF5B-E700-49F2-9DC5-E1471EA632D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BA6AF5B-E700-49F2-9DC5-E1471EA632D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BA6AF5B-E700-49F2-9DC5-E1471EA632D4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 82d0c1e4a988b895452fd24ecd720830e40768e6 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Sun, 14 Sep 2025 23:47:25 +0300 Subject: [PATCH 2/9] =?UTF-8?q?-=20EmployeeResponse.FullName=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BD=D0=B0?= =?UTF-8?q?=20FirstName=20=D0=B8=20LastName=20-=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/InMemoryRepository.cs | 10 ++-- .../EmployeeApiTests.cs | 54 +++++++++++++++---- .../Controllers/EmployeesController.cs | 15 ++---- .../Models/EmployeeRequest.cs | 18 +++++++ .../Models/EmployeeResponse.cs | 10 +++- 5 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs diff --git a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs index 2cda2ae8..e11111f1 100644 --- a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -38,12 +38,14 @@ public Task AddAsync(T entity) public async Task UpdateAsync(T entity) { var oldEntity = await GetByIdAsync(entity.Id); + if (oldEntity == null) + throw new Exception($"Entity with id [{entity.Id}] not found"); - if(oldEntity == null) - throw new Exception("Entity not found"); + // здесь должно быть сохранение EF контекста, + + //await DeleteAsync(oldEntity); + //await AddAsync(entity); - await DeleteAsync(oldEntity); - await AddAsync(entity); return entity; } diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs index 688ed3bd..97a57ed9 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs @@ -40,7 +40,8 @@ public async Task EmployeeCreation_Succeed() Assert.NotNull(content); Assert.NotEqual(Guid.Empty, content.Id); Assert.Equal(employeeRequest.Email, content.Email); - Assert.Equal($"{employeeRequest.FirstName} {employeeRequest.LastName}", content.FullName); + Assert.Equal(employeeRequest.FirstName, content.FirstName); + Assert.Equal(employeeRequest.LastName, content.LastName); } [Fact] @@ -79,7 +80,8 @@ public async Task Employee_Persists_Between_Calls() Assert.NotNull(fetchedEmployee); Assert.Equal(createdEmployee.Id, fetchedEmployee.Id); Assert.Equal(createdEmployee.Email, fetchedEmployee.Email); - Assert.Equal(createdEmployee.FullName, fetchedEmployee.FullName); + Assert.Equal(createdEmployee.FirstName, fetchedEmployee.FirstName); + Assert.Equal(createdEmployee.LastName, fetchedEmployee.LastName); Assert.Equal(createdEmployee.Roles, fetchedEmployee.Roles); Assert.Equal(createdEmployee.AppliedPromocodesCount, fetchedEmployee.AppliedPromocodesCount); } @@ -133,8 +135,10 @@ public async Task Employee_Get_Employee_On_Right_Id() Assert.NotNull(employee); Assert.Equal(sourceEmployee.Id, employee.Id); Assert.Equal(sourceEmployee.Email, employee.Email); - Assert.Equal(sourceEmployee.FullName, employee.FullName); - Assert.Equal(sourceEmployee.Roles, employee.Roles); + Assert.Equal(sourceEmployee.FirstName, employee.FirstName); + Assert.Equal(sourceEmployee.LastName, employee.LastName); + Assert.Equal(sourceEmployee.Roles.Count, employee.Roles.Count); + Assert.Equivalent(sourceEmployee.Roles, employee.Roles); } [Fact] @@ -144,6 +148,8 @@ public async Task Employee_Updates_Existing_Employee() Assert.NotNull(sourceEmployee); sourceEmployee.Email += "+"; + sourceEmployee.FirstName += "+"; + sourceEmployee.LastName += "+"; var response = await _client.PutAsJsonAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}", sourceEmployee); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -151,11 +157,41 @@ public async Task Employee_Updates_Existing_Employee() var updatedEmployee = await response.Content.ReadFromJsonAsync(); Assert.NotNull(updatedEmployee); Assert.Equal(updatedEmployee.Email, sourceEmployee.Email); - // Assert.Equal(createdEmployee.Id, fetchedEmployee.Id); - // Assert.Equal(createdEmployee.Email, fetchedEmployee.Email); - // Assert.Equal(createdEmployee.FullName, fetchedEmployee.FullName); - // Assert.Equal(createdEmployee.Roles, fetchedEmployee.Roles); - // Assert.Equal(createdEmployee.AppliedPromocodesCount, fetchedEmployee.AppliedPromocodesCount); + Assert.Equal(updatedEmployee.FirstName, sourceEmployee.FirstName); + Assert.Equal(updatedEmployee.LastName, sourceEmployee.LastName); + } + + + [Fact] + public async Task Employee_Delete_Existing_Employee_Returns_OK() + { + var sourceEmployee = await GetFirstEmployee(); + Assert.NotNull(sourceEmployee); + + var response = await _client.DeleteAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + } + + [Fact] + public async Task Employee_Delete_Missing_Employee_Returns_404() + { + var response = await _client.DeleteAsync($"{factory.ApiRoot}Employees/{Guid.NewGuid():D}"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task Employee_Delete_Existing_Employee_Removes_Employee() + { + var sourceEmployee = await GetFirstEmployee(); + Assert.NotNull(sourceEmployee); + + var response = await _client.DeleteAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + response = await _client.GetAsync($"{factory.ApiRoot}Employees/{sourceEmployee.Id:D}"); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs index ec3bfc37..2bc105fa 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs @@ -62,10 +62,12 @@ public async Task> GetEmployeeByIdAsync(Guid id) Email = employee.Email, Roles = employee.Roles?.Select(x => new RoleItemResponse() { + Id = x.Id, Name = x.Name, Description = x.Description }).ToList() ?? [], - FullName = employee.FullName, + FirstName = employee.FirstName, + LastName = employee.LastName, AppliedPromocodesCount = employee.AppliedPromocodesCount }; @@ -83,7 +85,8 @@ private static EmployeeResponse CreateEmployeeResponse(Employee employee) Name = x.Name, Description = x.Description }).ToList() ?? [], - FullName = employee.FullName, + FirstName = employee.FirstName, + LastName = employee.LastName, AppliedPromocodesCount = employee.AppliedPromocodesCount }; } @@ -140,14 +143,6 @@ public async Task> UpdateEmployee(Guid id, Employ } } - public class EmployeeInformation - { - public string FirstName { get; set; } - public string LastName { get; set; } - public string Email { get; set; } - } - public class EmployeeCreationRequest : EmployeeInformation { } - public class EmployeeUpdateRequest : EmployeeInformation { } } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs new file mode 100644 index 00000000..f5f673c7 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs @@ -0,0 +1,18 @@ +namespace PromoCodeFactory.WebHost.Models; + +public abstract class EmployeeRequest +{ + public string FirstName { get; init; } + public string LastName { get; init; } + public string Email { get; init; } +} + +/// +/// Запрос на создание нового пользователя +/// +public class EmployeeCreationRequest : EmployeeRequest { } + +/// +/// Запрос на изменение пользователя +/// +public class EmployeeUpdateRequest : EmployeeRequest { } \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeResponse.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeResponse.cs index 85a5f807..1879731d 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeResponse.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeResponse.cs @@ -7,7 +7,15 @@ public class EmployeeResponse { public Guid Id { get; set; } - public string FullName { get; set; } + // FullName разделён на FirstName и LastName. + // Использование полного имени вместо частей делает + // невозможным корректное обновление из фронтенда + // (FullName на бэке надо разделить, но не ясно делить + // трёх- и более сегментные ФИО: Пхе Мун Сон, Эльчин Тахир оглы и пр.) + // public string FullName { get; set; } + public string FirstName { get; set; } + + public string LastName { get; set; } public string Email { get; set; } From 58c058f4b850d020a71a9632596a0d7573ac003e Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Tue, 16 Sep 2025 19:54:02 +0300 Subject: [PATCH 3/9] =?UTF-8?q?-=20=D0=94=D0=97=20=D0=BA=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D1=83=D0=BB=D1=8E=20=E2=84=962=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/InMemoryRepository.cs | 16 ++- .../EmployeeApiTests.cs | 3 +- .../PromoCodeFactory.WebHost.Tests.csproj | 27 +++++ .../Controllers/EmployeesController.cs | 111 ++++++------------ ...EmployeeCreationRequestToEmployeeMapper.cs | 21 ++++ .../EmployeeMappers/EmployeeMappers.cs | 25 ++++ .../EmployeeToEmployeeResponseMapper.cs | 21 ++++ .../EmployeeToEmployeeShortResponseMapper.cs | 17 +++ .../EmployeeUpdateRequestToEmployeeMapper.cs | 18 +++ .../Mappers/IEmployeeMappers.cs | 12 ++ .../Mappers/IMapper.cs | 6 + .../Mappers/IRoleMappers.cs | 10 ++ .../RoleItemToRoleResponseMapper.cs | 18 +++ .../Mappers/RoleMappers/RoleMappers.cs | 13 ++ .../RoleToRoleItemResponseMapper.cs | 18 +++ .../Models/EmployeeRequest.cs | 6 +- .../src/PromoCodeFactory.WebHost/Startup.cs | 18 ++- 17 files changed, 272 insertions(+), 88 deletions(-) create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/PromoCodeFactory.WebHost.Tests.csproj create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeCreationRequestToEmployeeMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeMappers.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeResponseMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeShortResponseMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeUpdateRequestToEmployeeMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IEmployeeMappers.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IRoleMappers.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleItemToRoleResponseMapper.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleMappers.cs create mode 100644 Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleToRoleItemResponseMapper.cs diff --git a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs index e11111f1..a71e5cec 100644 --- a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.Data; using System.Linq; -using System.Reflection.PortableExecutable; using System.Threading.Tasks; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; + namespace PromoCodeFactory.DataAccess.Repositories { public class InMemoryRepository: IRepository where T: BaseEntity @@ -41,10 +40,11 @@ public async Task UpdateAsync(T entity) if (oldEntity == null) throw new Exception($"Entity with id [{entity.Id}] not found"); - // здесь должно быть сохранение EF контекста, + // stub for in-memory + await DeleteAsync(oldEntity); + await AddAsync(entity); - //await DeleteAsync(oldEntity); - //await AddAsync(entity); + // save changes return entity; } @@ -52,7 +52,11 @@ public async Task UpdateAsync(T entity) /// public Task DeleteAsync(T entity) { - Data.Remove(entity); + var storedEntity = Data.FirstOrDefault(x => x.Id == entity.Id); + if(storedEntity == null) + throw new Exception($"Entity with id [{entity.Id}] not found"); + + Data.Remove(storedEntity); return Task.CompletedTask; } } diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs index 97a57ed9..ffee7702 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/EmployeeApiTests.cs @@ -1,7 +1,6 @@ using System.Net; using System.Net.Http.Json; using PromoCodeFactory.DataAccess.Data; -using PromoCodeFactory.WebHost.Controllers; using PromoCodeFactory.WebHost.Models; namespace PromoCodeFactory.WebHost.Tests; @@ -82,7 +81,7 @@ public async Task Employee_Persists_Between_Calls() Assert.Equal(createdEmployee.Email, fetchedEmployee.Email); Assert.Equal(createdEmployee.FirstName, fetchedEmployee.FirstName); Assert.Equal(createdEmployee.LastName, fetchedEmployee.LastName); - Assert.Equal(createdEmployee.Roles, fetchedEmployee.Roles); + Assert.Equivalent(createdEmployee.Roles, fetchedEmployee.Roles); Assert.Equal(createdEmployee.AppliedPromocodesCount, fetchedEmployee.AppliedPromocodesCount); } diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/PromoCodeFactory.WebHost.Tests.csproj b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/PromoCodeFactory.WebHost.Tests.csproj new file mode 100644 index 00000000..07ae5698 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost.Tests/PromoCodeFactory.WebHost.Tests.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs index 2bc105fa..37708963 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Mappers; using PromoCodeFactory.WebHost.Models; namespace PromoCodeFactory.WebHost.Controllers @@ -15,15 +16,12 @@ namespace PromoCodeFactory.WebHost.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public class EmployeesController : ControllerBase + public class EmployeesController( + IRepository employeeRepository, + IEmployeeMappers employeeMappers + ) + : ControllerBase { - private readonly IRepository _employeeRepository; - - public EmployeesController(IRepository employeeRepository) - { - _employeeRepository = employeeRepository; - } - /// /// Получить данные всех сотрудников /// @@ -31,17 +29,8 @@ public EmployeesController(IRepository employeeRepository) [HttpGet] public async Task> GetEmployeesAsync() { - var employees = await _employeeRepository.GetAllAsync(); - - var employeesModelList = employees.Select(x => - new EmployeeShortResponse() - { - Id = x.Id, - Email = x.Email, - FullName = x.FullName, - }).ToList(); - - return employeesModelList; + var employees = await employeeRepository.GetAllAsync(); + return employees.Select(x => employeeMappers.EmployeeToEmployeeShortResponse.Map(x)).ToList(); } /// @@ -51,46 +40,20 @@ public async Task> GetEmployeesAsync() [HttpGet("{id:guid}")] public async Task> GetEmployeeByIdAsync(Guid id) { - var employee = await _employeeRepository.GetByIdAsync(id); + var employee = await employeeRepository.GetByIdAsync(id); if (employee == null) return NotFound(); - var employeeModel = new EmployeeResponse() - { - Id = employee.Id, - Email = employee.Email, - Roles = employee.Roles?.Select(x => new RoleItemResponse() - { - Id = x.Id, - Name = x.Name, - Description = x.Description - }).ToList() ?? [], - FirstName = employee.FirstName, - LastName = employee.LastName, - AppliedPromocodesCount = employee.AppliedPromocodesCount - }; - + var employeeModel = employeeMappers.EmployeeToEmployeeResponse.Map(employee); return employeeModel; } - private static EmployeeResponse CreateEmployeeResponse(Employee employee) - { - return new EmployeeResponse - { - Id = employee.Id, - Email = employee.Email, - Roles = employee.Roles?.Select(x => new RoleItemResponse() - { - Name = x.Name, - Description = x.Description - }).ToList() ?? [], - FirstName = employee.FirstName, - LastName = employee.LastName, - AppliedPromocodesCount = employee.AppliedPromocodesCount - }; - } - + /// + /// Создать нового сотрудника + /// + /// Данные для создания сотрудника + /// [HttpPost] public async Task> CreateEmployee(EmployeeCreationRequest creationRequest) { @@ -99,47 +62,45 @@ public async Task> CreateEmployee(EmployeeCreatio || creationRequest.LastName == null) return BadRequest(); - var employee = new Employee() - { - Id = Guid.NewGuid(), - FirstName = creationRequest.FirstName, - LastName = creationRequest.LastName, - Email = creationRequest.Email - }; - - await _employeeRepository.AddAsync(employee); + var employee = employeeMappers.EmployeeCreationRequestToEmployee.Map(creationRequest); + await employeeRepository.AddAsync(employee); - var savedEmployee = await _employeeRepository.GetByIdAsync(employee.Id); + var savedEmployee = await employeeRepository.GetByIdAsync(employee.Id); return savedEmployee != null - ? StatusCode((int)HttpStatusCode.Created, CreateEmployeeResponse(employee)) + ? Created($"/api/v1//employees/{employee.Id:D}", employeeMappers.EmployeeToEmployeeResponse.Map(employee)) : StatusCode((int)HttpStatusCode.InternalServerError); } + /// + /// Удаление сотрудника по его идентификатору + /// + /// Идентификатор пользователя + /// [HttpDelete("{id:guid}")] public async Task> DeleteEmployee(Guid id) { - var employee = await _employeeRepository.GetByIdAsync(id); + var employee = await employeeRepository.GetByIdAsync(id); if (employee == null) return NotFound(id); - await _employeeRepository.DeleteAsync(employee); + await employeeRepository.DeleteAsync(employee); return Ok(id); } + /// + /// Обновление данных сотрудника + /// + /// + /// + /// [HttpPut("{id:guid}")] public async Task> UpdateEmployee(Guid id, EmployeeUpdateRequest updateRequest) { - var employee = await _employeeRepository.GetByIdAsync(id); - if (employee == null) - return NotFound(id); - - employee.FirstName = updateRequest.FirstName; - employee.LastName = updateRequest.LastName; - employee.Email = updateRequest.Email; - - await _employeeRepository.UpdateAsync(employee); + var employee = employeeMappers.EmployeeUpdateRequestToEmployee.Map(updateRequest); + employee.Id = id; - return Ok(CreateEmployeeResponse(employee)); + var updatedEmployee = await employeeRepository.UpdateAsync(employee); + return Ok(employeeMappers.EmployeeToEmployeeResponse.Map(updatedEmployee)); } } diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeCreationRequestToEmployeeMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeCreationRequestToEmployeeMapper.cs new file mode 100644 index 00000000..45968d97 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeCreationRequestToEmployeeMapper.cs @@ -0,0 +1,21 @@ +using System; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.EmployeeMappers; + +public class EmployeeCreationRequestToEmployeeMapper : IMapper +{ + /// + public Core.Domain.Administration.Employee Map(EmployeeCreationRequest source) + { + return new Core.Domain.Administration.Employee + { + Id = Guid.NewGuid(), + FirstName = source.FirstName, + LastName = source.LastName, + Email = source.Email, + Roles = [], + AppliedPromocodesCount = 0 + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeMappers.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeMappers.cs new file mode 100644 index 00000000..581af6e7 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeMappers.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.EmployeeMappers; + +public class EmployeeMappers(IServiceProvider services) : IEmployeeMappers +{ + /// + public IMapper EmployeeToEmployeeResponse { get; } + = services.GetRequiredService>(); + + /// + public IMapper EmployeeToEmployeeShortResponse { get; } + = services.GetRequiredService>(); + + /// + public IMapper EmployeeCreationRequestToEmployee { get; } + = services.GetRequiredService>(); + + /// + public IMapper EmployeeUpdateRequestToEmployee { get; } + = services.GetRequiredService>(); +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeResponseMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeResponseMapper.cs new file mode 100644 index 00000000..e5ac2127 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeResponseMapper.cs @@ -0,0 +1,21 @@ +using System.Linq; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.EmployeeMappers; + +public class EmployeeToEmployeeResponseMapper(IRoleMappers roleMappers) : IMapper +{ + /// + public EmployeeResponse Map(Core.Domain.Administration.Employee source) + { + return new EmployeeResponse + { + Id = source.Id, + FirstName = source.FirstName, + LastName = source.LastName, + Email = source.Email, + Roles = source.Roles?.Select(roleMappers.RoleToRoleItemResponse.Map).ToList() ?? [], + AppliedPromocodesCount = source.AppliedPromocodesCount + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeShortResponseMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeShortResponseMapper.cs new file mode 100644 index 00000000..060791c2 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeToEmployeeShortResponseMapper.cs @@ -0,0 +1,17 @@ +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.EmployeeMappers; + +public class EmployeeToEmployeeShortResponseMapper : IMapper +{ + /// + public EmployeeShortResponse Map(Core.Domain.Administration.Employee source) + { + return new EmployeeShortResponse + { + Id = source.Id, + FullName = $"{source.FirstName} {source.LastName}", + Email = source.Email + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeUpdateRequestToEmployeeMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeUpdateRequestToEmployeeMapper.cs new file mode 100644 index 00000000..eae2625c --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/EmployeeMappers/EmployeeUpdateRequestToEmployeeMapper.cs @@ -0,0 +1,18 @@ +using PromoCodeFactory.WebHost.Models; +using PromoCodeFactory.Core.Domain.Administration; + +namespace PromoCodeFactory.WebHost.Mappers.EmployeeMappers; + +public class EmployeeUpdateRequestToEmployeeMapper : IMapper +{ + /// + public Employee Map(EmployeeUpdateRequest source) + { + return new Employee + { + FirstName = source.FirstName, + LastName = source.LastName, + Email = source.Email + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IEmployeeMappers.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IEmployeeMappers.cs new file mode 100644 index 00000000..ad9c407b --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IEmployeeMappers.cs @@ -0,0 +1,12 @@ +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers; + +public interface IEmployeeMappers +{ + IMapper EmployeeToEmployeeResponse { get; } + IMapper EmployeeToEmployeeShortResponse { get; } + IMapper EmployeeCreationRequestToEmployee { get; } + IMapper EmployeeUpdateRequestToEmployee { get; } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IMapper.cs new file mode 100644 index 00000000..d8363d8d --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IMapper.cs @@ -0,0 +1,6 @@ +namespace PromoCodeFactory.WebHost.Mappers; + +public interface IMapper +{ + TDestination Map(TSource source); +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IRoleMappers.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IRoleMappers.cs new file mode 100644 index 00000000..c5c5bd34 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/IRoleMappers.cs @@ -0,0 +1,10 @@ +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers; + +public interface IRoleMappers +{ + IMapper RoleToRoleItemResponse { get; } + +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleItemToRoleResponseMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleItemToRoleResponseMapper.cs new file mode 100644 index 00000000..2e3e5b97 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleItemToRoleResponseMapper.cs @@ -0,0 +1,18 @@ +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.RoleMappers; + +public class RoleItemToRoleResponseMapper : IMapper +{ + /// + public Role Map(RoleItemResponse source) + { + return new Role + { + Id = source.Id, + Name = source.Name, + Description = source.Description + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleMappers.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleMappers.cs new file mode 100644 index 00000000..74247956 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleMappers.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.RoleMappers; + +public class RoleMappers(IServiceProvider services) : IRoleMappers +{ + /// + public IMapper RoleToRoleItemResponse { get; } + = services.GetRequiredService>(); +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleToRoleItemResponseMapper.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleToRoleItemResponseMapper.cs new file mode 100644 index 00000000..04ba9505 --- /dev/null +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Mappers/RoleMappers/RoleToRoleItemResponseMapper.cs @@ -0,0 +1,18 @@ +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Mappers.RoleMappers; + +public class RoleToRoleItemResponseMapper : IMapper +{ + /// + public RoleItemResponse Map(Role source) + { + return new RoleItemResponse + { + Id = source.Id, + Name = source.Name, + Description = source.Description + }; + } +} \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs index f5f673c7..f5637c05 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Models/EmployeeRequest.cs @@ -1,6 +1,6 @@ namespace PromoCodeFactory.WebHost.Models; -public abstract class EmployeeRequest +public abstract record EmployeeRequest { public string FirstName { get; init; } public string LastName { get; init; } @@ -10,9 +10,9 @@ public abstract class EmployeeRequest /// /// Запрос на создание нового пользователя /// -public class EmployeeCreationRequest : EmployeeRequest { } +public record EmployeeCreationRequest : EmployeeRequest; /// /// Запрос на изменение пользователя /// -public class EmployeeUpdateRequest : EmployeeRequest { } \ No newline at end of file +public record EmployeeUpdateRequest : EmployeeRequest; \ No newline at end of file diff --git a/Homeworks/Base/src/PromoCodeFactory.WebHost/Startup.cs b/Homeworks/Base/src/PromoCodeFactory.WebHost/Startup.cs index 8151aa4a..a7ac0925 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Startup.cs @@ -6,6 +6,10 @@ using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.DataAccess.Data; using PromoCodeFactory.DataAccess.Repositories; +using PromoCodeFactory.WebHost.Mappers; +using PromoCodeFactory.WebHost.Mappers.EmployeeMappers; +using PromoCodeFactory.WebHost.Mappers.RoleMappers; +using PromoCodeFactory.WebHost.Models; namespace PromoCodeFactory.WebHost { @@ -14,9 +18,9 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddSingleton(typeof(IRepository), (x) => + services.AddSingleton(typeof(IRepository), _ => new InMemoryRepository(FakeDataFactory.Employees)); - services.AddSingleton(typeof(IRepository), (x) => + services.AddSingleton(typeof(IRepository), _ => new InMemoryRepository(FakeDataFactory.Roles)); services.AddOpenApiDocument(options => @@ -24,6 +28,16 @@ public void ConfigureServices(IServiceCollection services) options.Title = "PromoCode Factory API Doc"; options.Version = "1.0"; }); + + services.AddSingleton, RoleToRoleItemResponseMapper>(); + services.AddSingleton, RoleItemToRoleResponseMapper>(); + services.AddSingleton(); + + services.AddSingleton, EmployeeToEmployeeResponseMapper>(); + services.AddSingleton, EmployeeToEmployeeShortResponseMapper>(); + services.AddSingleton, EmployeeCreationRequestToEmployeeMapper>(); + services.AddSingleton, EmployeeUpdateRequestToEmployeeMapper>(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) From 3d98be68f17a09364a1970dbeb5a3a798217e75c Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Tue, 23 Sep 2025 22:56:08 +0300 Subject: [PATCH 4/9] EfContext added --- .../Domain/PromoCodeManagement/Customer.cs | 3 +- .../Domain/PromoCodeManagement/Preference.cs | 8 +- .../Domain/PromoCodeManagement/PromoCode.cs | 2 + .../PromoCodeFactory.DataAccess/EfContext.cs | 80 +++++++++++++++++++ .../PromoCodeFactory.DataAccess.csproj | 9 +++ .../Repositories/EfRepository.cs | 54 +++++++++++++ .../src/PromoCodeFactory.WebHost/Startup.cs | 8 ++ 7 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index 6f5c3593..2047986d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -14,6 +14,7 @@ public class Customer public string Email { get; set; } - //TODO: Списки Preferences и Promocodes + public ICollection Preferences { get; set; } = []; + public ICollection PromoCodes { get; set; } = []; } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs index 28e172a4..306354d7 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs @@ -1,8 +1,14 @@ -namespace PromoCodeFactory.Core.Domain.PromoCodeManagement +using System.Collections; +using System.Collections.Generic; + +namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { public class Preference : BaseEntity { public string Name { get; set; } + public string Value { get; set; } + + public ICollection Customers { get; set; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs index 3305a41a..a7b555d2 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs @@ -21,5 +21,7 @@ public class PromoCode public Employee PartnerManager { get; set; } public Preference Preference { get; set; } + + public Customer Customer { get; set; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs new file mode 100644 index 00000000..9ef6ee23 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs @@ -0,0 +1,80 @@ +using System.Collections; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.DataAccess.Data; +using Microsoft.EntityFrameworkCore.Sqlite; + +namespace PromoCodeFactory.DataAccess; + +public class EfContext : DbContext +{ + //Employee, Roles, Customer,Preference и PromoCode + public DbSet Employees { get; set; } + public DbSet Roles { get; set; } + public DbSet Customers { get; set; } + public DbSet Preferences { get; set; } + public DbSet PromoCodes { get; set; } + + // public EfContext(DbContextOptions options) + // { + // + // } + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite("Data Source=PromoCodeFactory.db"); + base.OnConfiguring(optionsBuilder); + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + //PromoCode имеет ссылку на Preference + //Employee имеет ссылку на Role. + //Customer имеет набор Preference, + //но так как Preference - это общий справочник и сущности связаны через Many-to-many, + //то нужно сделать маппинг через сущность CustomerPreference. + //Строковые поля должны иметь ограничения на MaxLength. + //Связь Customer и Promocode реализовать через One-To-Many, + //будем считать, что в данном примере + //промокод может быть выдан только одному клиенту из базы. + + //modelBuilder + // .Entity() + //.HasData(FakeDataFactory.Employees); + + modelBuilder + .Entity() + .HasOne(x => x.Preference); + + modelBuilder + .Entity() + .HasOne(x => x.Role) + .WithMany(); + + modelBuilder + .Entity() + .HasMany(x => x.Preferences) + .WithMany(x => x.Customers) + .UsingEntity( + "CustomerPreference", + l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("CustomersId").IsRequired(), + r => r.HasOne(typeof(Preference)).WithMany().HasForeignKey("PreferencesId").IsRequired(), + j => j.HasKey("CustomersId", "PreferencesId") + ) + ; + + modelBuilder + .Entity() + .HasMany(x => x.PromoCodes) + .WithOne(x => x.Customer); + + // modelBuilder + // .Entity() + // .HasMany(x => x.Id); + + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj index 4b290698..6608f16b 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj @@ -8,4 +8,13 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs new file mode 100644 index 00000000..b5cfbd97 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain; + +namespace PromoCodeFactory.DataAccess.Repositories; + +public class EfRepository(IDbContextFactory contextFactory) : IRepository + where T : BaseEntity +{ + /// + public async Task> GetAllAsync() + { + await using var context = await contextFactory.CreateDbContextAsync(); + var result = await context.Set().ToListAsync(); + return result; + } + + /// + public async Task GetByIdAsync(Guid id) + { + await using var context = await contextFactory.CreateDbContextAsync(); + var result = await context.Set().SingleAsync(x => x.Id == id); + return result; + } + + /// + public async Task AddAsync(T entity) + { + await using var context = await contextFactory.CreateDbContextAsync(); + var result = await context.Set().AddAsync(entity); + await context.SaveChangesAsync(); + return result.Entity; + } + + /// + public async Task UpdateAsync(T entity) + { + await using var context = await contextFactory.CreateDbContextAsync(); + var result = context.Set().Update(entity); + await context.SaveChangesAsync(); + return result.Entity; + } + + /// + public async Task DeleteAsync(T entity) + { + await using var context = await contextFactory.CreateDbContextAsync(); + context.Set().Remove(entity); + await context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs index 73a898fc..15164b84 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs @@ -1,10 +1,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.DataAccess; using PromoCodeFactory.DataAccess.Data; using PromoCodeFactory.DataAccess.Repositories; @@ -31,6 +33,12 @@ public void ConfigureServices(IServiceCollection services) options.Title = "PromoCode Factory API Doc"; options.Version = "1.0"; }); + + services.AddDbContextFactory(options => + { + options.UseSqlite("Data Source=PromoCodeFactory.db"); + }); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 6fd0b7823170b56f76f6139f4505bc0a6db61876 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Sat, 27 Sep 2025 22:44:35 +0300 Subject: [PATCH 5/9] seeding ok --- .../Abstractions/Repositories/IRepository.cs | 2 + .../Domain/Administration/Employee.cs | 9 +- .../Domain/Administration/Role.cs | 6 + .../Domain/PromoCodeManagement/Customer.cs | 11 +- .../Domain/PromoCodeManagement/Preference.cs | 4 + .../Domain/PromoCodeManagement/PromoCode.cs | 4 + .../Data/FakeDataFactory.cs | 3 +- .../PromoCodeFactory.DataAccess/EfContext.cs | 111 +++++++++++++----- .../PromoCodeFactory.DataAccess.csproj | 4 + .../Repositories/EfRepository.cs | 25 +++- .../Repositories/InMemoryRepository.cs | 7 ++ .../Controllers/CustomersController.cs | 68 +++++++++-- .../PromoCodeFactory.WebHost.csproj | 4 + .../src/PromoCodeFactory.WebHost/Startup.cs | 46 ++++++-- 14 files changed, 245 insertions(+), 59 deletions(-) diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs index a1eb21fe..15941fc3 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.Core.Abstractions.Repositories { @@ -11,5 +12,6 @@ public interface IRepository Task> GetAllAsync(); Task GetByIdAsync(Guid id); + Task AddAsync(Customer customer); } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs index 28099d61..8ee1473d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs @@ -1,17 +1,24 @@ using PromoCodeFactory.Core.Domain; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace PromoCodeFactory.Core.Domain.Administration { public class Employee : BaseEntity { + [StringLength(50)] public string FirstName { get; set; } + + [StringLength(50)] public string LastName { get; set; } - + + [NotMapped] public string FullName => $"{FirstName} {LastName}"; + [StringLength(50)] public string Email { get; set; } public Role Role { get; set; } diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Role.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Role.cs index f0fadc21..8eeff651 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Role.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Role.cs @@ -1,13 +1,19 @@ using PromoCodeFactory.Core.Domain; using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace PromoCodeFactory.Core.Domain.Administration { public class Role : BaseEntity { + [StringLength(50)] public string Name { get; set; } + [StringLength(150)] public string Description { get; set; } + + public ICollection Employees { get; set; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index 2047986d..c205bb3d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -1,20 +1,27 @@ using PromoCodeFactory.Core.Domain; using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { public class Customer : BaseEntity { + [StringLength(50)] public string FirstName { get; set; } + + [StringLength(50)] public string LastName { get; set; } + [NotMapped] public string FullName => $"{FirstName} {LastName}"; + [StringLength(50)] public string Email { get; set; } - public ICollection Preferences { get; set; } = []; - public ICollection PromoCodes { get; set; } = []; + public IEnumerable Preferences { get; set; } = []; + public IEnumerable PromoCodes { get; set; } = []; } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs index 306354d7..3ad1cc0e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs @@ -1,12 +1,16 @@ using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { public class Preference : BaseEntity { + [StringLength(50)] public string Name { get; set; } + + [StringLength(150)] public string Value { get; set; } public ICollection Customers { get; set; } diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs index a7b555d2..0170812d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations; using System.Runtime; using PromoCodeFactory.Core.Domain; using PromoCodeFactory.Core.Domain.Administration; @@ -8,14 +9,17 @@ namespace PromoCodeFactory.Core.Domain.PromoCodeManagement public class PromoCode : BaseEntity { + [StringLength(20)] public string Code { get; set; } + [StringLength(150)] public string ServiceInfo { get; set; } public DateTime BeginDate { get; set; } public DateTime EndDate { get; set; } + [StringLength(150)] public string PartnerName { get; set; } public Employee PartnerManager { get; set; } diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs index aa665794..0b1dd78e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs @@ -51,7 +51,7 @@ public static class FakeDataFactory new Preference() { Id = Guid.Parse("ef7f299f-92d7-459f-896e-078ed53ef99c"), - Name = "Театр", + Name = "Театр" }, new Preference() { @@ -79,6 +79,7 @@ public static IEnumerable Customers FirstName = "Иван", LastName = "Петров", //TODO: Добавить предзаполненный список предпочтений + Preferences = Preferences.Where(x => x.Value == "Театр" || x.Value == "Семья") } }; diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs index 9ef6ee23..57dd0baa 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs @@ -1,33 +1,65 @@ -using System.Collections; +using System.Linq; using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.DataAccess.Data; -using Microsoft.EntityFrameworkCore.Sqlite; namespace PromoCodeFactory.DataAccess; -public class EfContext : DbContext +public class EfContext(DbContextOptions options) : DbContext(options) { - //Employee, Roles, Customer,Preference и PromoCode - public DbSet Employees { get; set; } - public DbSet Roles { get; set; } - public DbSet Customers { get; set; } - public DbSet Preferences { get; set; } - public DbSet PromoCodes { get; set; } - - // public EfContext(DbContextOptions options) + /// + // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) // { + // base.OnConfiguring(optionsBuilder); + // + // Database.EnsureDeleted(); + // Database.EnsureCreated(); // // } - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + public static void SeedData(DbContext context) { - optionsBuilder.UseSqlite("Data Source=PromoCodeFactory.db"); - base.OnConfiguring(optionsBuilder); + context.Set().AddRange(FakeDataFactory.Roles); + context.Set().AddRange(FakeDataFactory.Preferences); + context.SaveChanges(); + + var customers = FakeDataFactory.Customers.ToList(); + + // foreach (var customer in customers) + // { + // var prefs = customer.Preferences.ToList(); + // customer.Preferences = context.Set().Where(x => prefs.Any(z => z.Id == x.Id)).ToList(); + // } + context.Set().AddRange(customers); + + var employees = FakeDataFactory.Employees.ToList(); + //var roles = context.Set().ToList(); + foreach (var employee in employees) + { + employee.Role = context.Set().Single(r => r.Id == employee.Role.Id); + } + context.Set().AddRange(employees); + + // var rolesToAdd = FakeDataFactory.Roles + // .Where(x => context.Set().All(z => z.Id != x.Id)) + // .ToList(); + + + context.SaveChanges(); } + + //Database.EnsureDeleted(); + //Database.EnsureCreated(); + + // + // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + // { + // optionsBuilder.UseSqlite("Data Source=PromoCodeFactory.db"); + // base.OnConfiguring(optionsBuilder); + // } + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -40,20 +72,27 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) //Связь Customer и Promocode реализовать через One-To-Many, //будем считать, что в данном примере //промокод может быть выдан только одному клиенту из базы. - - //modelBuilder - // .Entity() - //.HasData(FakeDataFactory.Employees); modelBuilder .Entity() .HasOne(x => x.Preference); - + modelBuilder .Entity() .HasOne(x => x.Role) - .WithMany(); - + .WithMany(x => x.Employees) + ;//.HasForeignKey(x=> x.Role); + + // modelBuilder + // .Entity() + // .Property("RoleId"); + // + // modelBuilder + // .Entity() + // .HasOne(x => x.Role) + // .WithMany() + // .HasForeignKey("RoleId"); + modelBuilder .Entity() .HasMany(x => x.Preferences) @@ -61,20 +100,38 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .UsingEntity( "CustomerPreference", l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("CustomersId").IsRequired(), - r => r.HasOne(typeof(Preference)).WithMany().HasForeignKey("PreferencesId").IsRequired(), + r => r.HasOne(typeof(Preference)).WithMany().HasForeignKey("PreferencesId").IsRequired(), j => j.HasKey("CustomersId", "PreferencesId") - ) + ) ; - + modelBuilder .Entity() .HasMany(x => x.PromoCodes) .WithOne(x => x.Customer); - + + // modelBuilder // .Entity() // .HasMany(x => x.Id); - + + + // modelBuilder + // .Entity() + // .HasData(FakeDataFactory.Roles); + // + // modelBuilder + // .Entity() + // .HasData(FakeDataFactory.Preferences); + // + // modelBuilder + // .Entity() + // .HasData(FakeDataFactory.Customers); + // + // modelBuilder + // .Entity() + // .HasData(FakeDataFactory.Employees); + base.OnModelCreating(modelBuilder); } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj index 6608f16b..b2f22684 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs index b5cfbd97..1eb5261e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs @@ -4,16 +4,18 @@ using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.DataAccess.Repositories; -public class EfRepository(IDbContextFactory contextFactory) : IRepository +public class EfRepository(EfContext context) : IRepository where T : BaseEntity { + public DbSet Items { get; set; } /// public async Task> GetAllAsync() { - await using var context = await contextFactory.CreateDbContextAsync(); + //await using var context = await contextFactory.CreateDbContextAsync(); var result = await context.Set().ToListAsync(); return result; } @@ -21,15 +23,26 @@ public async Task> GetAllAsync() /// public async Task GetByIdAsync(Guid id) { - await using var context = await contextFactory.CreateDbContextAsync(); + //await using var context = await contextFactory.CreateDbContextAsync(); var result = await context.Set().SingleAsync(x => x.Id == id); return result; } + /// + public async Task AddAsync(Customer customer) + { + var presentCustomer = await context.Set().FirstOrDefaultAsync(x => x.Id == customer.Id); + if(presentCustomer != null) + throw new Exception("Customer already exists"); + + await context.AddAsync(customer); + await context.SaveChangesAsync(); + } + /// public async Task AddAsync(T entity) { - await using var context = await contextFactory.CreateDbContextAsync(); + //await using var context = await contextFactory.CreateDbContextAsync(); var result = await context.Set().AddAsync(entity); await context.SaveChangesAsync(); return result.Entity; @@ -38,7 +51,7 @@ public async Task AddAsync(T entity) /// public async Task UpdateAsync(T entity) { - await using var context = await contextFactory.CreateDbContextAsync(); + //await using var context = await contextFactory.CreateDbContextAsync(); var result = context.Set().Update(entity); await context.SaveChangesAsync(); return result.Entity; @@ -47,7 +60,7 @@ public async Task UpdateAsync(T entity) /// public async Task DeleteAsync(T entity) { - await using var context = await contextFactory.CreateDbContextAsync(); + //await using var context = await contextFactory.CreateDbContextAsync(); context.Set().Remove(entity); await context.SaveChangesAsync(); } diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs index dd099f5e..a042965f 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.DataAccess.Repositories { @@ -27,5 +28,11 @@ public Task GetByIdAsync(Guid id) { return Task.FromResult(Data.FirstOrDefault(x => x.Id == id)); } + + /// + public Task AddAsync(Customer customer) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs index cf069a09..f1e04509 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs @@ -1,7 +1,13 @@ using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.WebHost.Models; using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.WebHost.Controllers { @@ -10,28 +16,72 @@ namespace PromoCodeFactory.WebHost.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public class CustomersController + public class CustomersController( + IRepository customersRepository + ) : ControllerBase { + [HttpGet] - public Task> GetCustomersAsync() + public async Task>> GetCustomersAsync() { - //TODO: Добавить получение списка клиентов - throw new NotImplementedException(); + var customers = await customersRepository.GetAllAsync(); + + return Ok(customers.Select(x => new CustomerShortResponse() + { + Id = x.Id, + Email = x.Email, + FirstName = x.FirstName, + LastName = x.LastName + })); } [HttpGet("{id}")] - public Task> GetCustomerAsync(Guid id) + public async Task> GetCustomerAsync(Guid id) { - //TODO: Добавить получение клиента вместе с выданными ему промомкодами - throw new NotImplementedException(); + //TODO: Добавить получение клиента вместе с выданными ему промокодами + var customer = await customersRepository.GetByIdAsync(id); + if (customer == null) + return NotFound(); + + return new CustomerResponse() + { + Id = customer.Id, + Email = customer.Email, + FirstName = customer.FirstName, + LastName = customer.LastName, + PromoCodes = customer.PromoCodes.Select(x => new PromoCodeShortResponse() + { + Id = x.Id, + BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), + EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), + Code = x.Code, + PartnerName = x.PartnerName, + ServiceInfo = x.ServiceInfo + }).ToList() //await promoCodesRepository.GetByIdAsync(customer.Id). + + //customer. + }; } [HttpPost] - public Task CreateCustomerAsync(CreateOrEditCustomerRequest request) + public async Task CreateCustomerAsync(CreateOrEditCustomerRequest request) { //TODO: Добавить создание нового клиента вместе с его предпочтениями - throw new NotImplementedException(); + var customer = new Customer + { + Id = Guid.NewGuid(), + Email = request.Email, + FirstName = request.FirstName, + LastName = request.LastName, + Preferences = [], + PromoCodes = [] + //PromoCodes = new List() + }; + + await customersRepository.AddAsync(customer); + return Created(); + } [HttpPut("{id}")] diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj b/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj index 4bb08b6e..387bd67e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj @@ -12,6 +12,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs index 15164b84..f1af7224 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -19,34 +20,53 @@ public class Startup public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - services.AddScoped(typeof(IRepository), (x) => - new InMemoryRepository(FakeDataFactory.Employees)); - services.AddScoped(typeof(IRepository), (x) => - new InMemoryRepository(FakeDataFactory.Roles)); - services.AddScoped(typeof(IRepository), (x) => - new InMemoryRepository(FakeDataFactory.Preferences)); - services.AddScoped(typeof(IRepository), (x) => - new InMemoryRepository(FakeDataFactory.Customers)); + + // services.AddScoped(typeof(IRepository), (x) => + // new InMemoryRepository(FakeDataFactory.Employees)); + // services.AddScoped(typeof(IRepository), (x) => + // new InMemoryRepository(FakeDataFactory.Roles)); + // services.AddScoped(typeof(IRepository), (x) => + // new InMemoryRepository(FakeDataFactory.Preferences)); + // services.AddScoped(typeof(IRepository), (x) => + // new InMemoryRepository(FakeDataFactory.Customers)); + + + services.AddScoped, EfRepository>(); + services.AddScoped, EfRepository>(); + services.AddScoped, EfRepository>(); + services.AddScoped, EfRepository>(); + services.AddScoped, EfRepository>(); + services.AddDbContext(options => + { + options + .UseSqlite("Data Source=PromoCodeFactory.db") + .UseSeeding((c,b) => EfContext.SeedData(c)) + .UseAsyncSeeding((c,b, t) => + { + EfContext.SeedData(c); + return Task.CompletedTask; + }); + }); + services.AddOpenApiDocument(options => { options.Title = "PromoCode Factory API Doc"; options.Version = "1.0"; }); - services.AddDbContextFactory(options => - { - options.UseSqlite("Data Source=PromoCodeFactory.db"); - }); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, EfContext efContext) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + efContext.Database.EnsureDeleted(); + efContext.Database.EnsureCreated(); } else { From adc2549469d8f07a54f5abf771ed0d4e8f13f55a Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Tue, 30 Sep 2025 22:11:46 +0300 Subject: [PATCH 6/9] preferences controller implemented --- .../Abstractions/Repositories/IRepository.cs | 7 +- .../Domain/PromoCodeManagement/Customer.cs | 10 +- .../Domain/PromoCodeManagement/Preference.cs | 7 +- .../PromoCodeFactory.Core.csproj | 1 + .../Data/FakeDataFactory.cs | 40 +++- .../PromoCodeFactory.DataAccess/EfContext.cs | 120 ++++------- .../PromoCodeFactory.DataAccess.csproj | 1 + .../Repositories/EfRepository.cs | 36 +--- .../Repositories/InMemoryRepository.cs | 16 +- .../Controllers/CustomersController.cs | 201 +++++++++++++----- .../Controllers/PreferencesController.cs | 36 ++++ .../Controllers/PromocodesController.cs | 58 ++++- .../Models/CreateOrEditCustomerRequest.cs | 16 +- .../Models/CustomerResponse.cs | 9 +- .../Models/PreferenceResponse.cs | 12 ++ .../PromoCodeFactory.WebHost.csproj | 1 + .../src/PromoCodeFactory.WebHost/Startup.cs | 17 +- Homeworks/EF/src/PromoCodeFactory.sln | 6 + .../CustomersControllerTests.cs | 146 +++++++++++++ .../PreferenceControllerTests.cs | 55 +++++ .../PromocodesControllerTests.cs | 70 ++++++ .../WebHostFixture.cs | 9 + 22 files changed, 675 insertions(+), 199 deletions(-) create mode 100644 Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.WebHost/Models/PreferenceResponse.cs create mode 100644 Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs create mode 100644 Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs create mode 100644 Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs create mode 100644 Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs index 15941fc3..e18840d4 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using PromoCodeFactory.Core.Domain; -using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.Core.Abstractions.Repositories { @@ -11,7 +10,9 @@ public interface IRepository { Task> GetAllAsync(); - Task GetByIdAsync(Guid id); - Task AddAsync(Customer customer); + Task GetByIdAsync(Guid id); + Task AddAsync(T customer); + Task UpdateAsync(T customer); + Task RemoveAsync(T customer); } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index c205bb3d..46353b7a 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -6,6 +6,12 @@ namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { + public class CustomerPreference : BaseEntity + { + public Guid CustomersId { get; set; } + public Guid PreferencesId { get; set; } + } + public class Customer : BaseEntity { @@ -21,7 +27,7 @@ public class Customer [StringLength(50)] public string Email { get; set; } - public IEnumerable Preferences { get; set; } = []; - public IEnumerable PromoCodes { get; set; } = []; + public ICollection Preferences { get; set; } = new List(); + public ICollection PromoCodes { get; set; } = new List(); } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs index 3ad1cc0e..d8a22d06 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs @@ -1,5 +1,4 @@ -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace PromoCodeFactory.Core.Domain.PromoCodeManagement @@ -12,7 +11,7 @@ public class Preference [StringLength(150)] public string Value { get; set; } - - public ICollection Customers { get; set; } + + public ICollection Customers { get; set; } = new List(); } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/PromoCodeFactory.Core.csproj b/Homeworks/EF/src/PromoCodeFactory.Core/PromoCodeFactory.Core.csproj index 4c096661..b8bbd025 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/PromoCodeFactory.Core.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.Core/PromoCodeFactory.Core.csproj @@ -2,6 +2,7 @@ net8.0 + enable diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs index 0b1dd78e..ad6398c1 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs @@ -51,17 +51,20 @@ public static class FakeDataFactory new Preference() { Id = Guid.Parse("ef7f299f-92d7-459f-896e-078ed53ef99c"), - Name = "Театр" + Name = "Театр", + Value = "theatre" }, new Preference() { Id = Guid.Parse("c4bda62e-fc74-4256-a956-4760b3858cbd"), Name = "Семья", + Value = "family" }, new Preference() { Id = Guid.Parse("76324c47-68d2-472d-abb8-33cfa8cc0c84"), Name = "Дети", + Value = "children" } }; @@ -79,12 +82,45 @@ public static IEnumerable Customers FirstName = "Иван", LastName = "Петров", //TODO: Добавить предзаполненный список предпочтений - Preferences = Preferences.Where(x => x.Value == "Театр" || x.Value == "Семья") + Preferences = Preferences.Where(x => x.Name == "Театр" || x.Name == "Семья").ToList() } }; return customers; } } + + // public static IEnumerable PromoCodes => new List([ + // new () + // { + // Id = Guid.NewGuid(), + // Code = "Code-1", + // BeginDate = DateTime.UtcNow, + // EndDate = DateTime.UtcNow.AddDays(10), + // PartnerName = "Partner-1", + // ServiceInfo = "SvcInfo-1" + // }, + // new () + // { + // Id = Guid.NewGuid(), + // Code = "Code-2", + // BeginDate = DateTime.UtcNow, + // EndDate = DateTime.UtcNow.AddDays(10), + // PartnerName = "Partner-2", + // ServiceInfo = "SvcInfo-2" + // }, + // new () + // { + // Id = Guid.NewGuid(), + // Code = "Code-3", + // BeginDate = DateTime.UtcNow, + // EndDate = DateTime.UtcNow.AddDays(10), + // PartnerName = "Partner-3", + // ServiceInfo = "SvcInfo-3" + // } + // + // ]); + + } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs index 57dd0baa..343deade 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs @@ -8,15 +8,6 @@ namespace PromoCodeFactory.DataAccess; public class EfContext(DbContextOptions options) : DbContext(options) { - /// - // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - // { - // base.OnConfiguring(optionsBuilder); - // - // Database.EnsureDeleted(); - // Database.EnsureCreated(); - // - // } public static void SeedData(DbContext context) { @@ -25,112 +16,79 @@ public static void SeedData(DbContext context) context.SaveChanges(); var customers = FakeDataFactory.Customers.ToList(); - - // foreach (var customer in customers) - // { - // var prefs = customer.Preferences.ToList(); - // customer.Preferences = context.Set().Where(x => prefs.Any(z => z.Id == x.Id)).ToList(); - // } + foreach (var customer in customers) + { + customer.Preferences = + context + .Set() + .AsEnumerable() + .Where(r => customer.Preferences.Any(c => c.Id == r.Id)) + .ToList(); + } context.Set().AddRange(customers); var employees = FakeDataFactory.Employees.ToList(); - //var roles = context.Set().ToList(); foreach (var employee in employees) { - employee.Role = context.Set().Single(r => r.Id == employee.Role.Id); + employee.Role = context.Set().AsEnumerable().Single(r => r.Id == employee.Role.Id); } context.Set().AddRange(employees); - - // var rolesToAdd = FakeDataFactory.Roles - // .Where(x => context.Set().All(z => z.Id != x.Id)) - // .ToList(); - context.SaveChanges(); } - - //Database.EnsureDeleted(); - //Database.EnsureCreated(); - - // - // protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - // { - // optionsBuilder.UseSqlite("Data Source=PromoCodeFactory.db"); - // base.OnConfiguring(optionsBuilder); - // } - /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - //PromoCode имеет ссылку на Preference - //Employee имеет ссылку на Role. - //Customer имеет набор Preference, - //но так как Preference - это общий справочник и сущности связаны через Many-to-many, - //то нужно сделать маппинг через сущность CustomerPreference. - //Строковые поля должны иметь ограничения на MaxLength. - //Связь Customer и Promocode реализовать через One-To-Many, - //будем считать, что в данном примере - //промокод может быть выдан только одному клиенту из базы. + //NOTE: MaxLength для строк указана через атрибуты в сущностях + modelBuilder + .Entity() + .HasKey(x => x.Id); + modelBuilder + .Entity() + .HasKey(x => x.Id); + modelBuilder .Entity() .HasOne(x => x.Preference); - + modelBuilder .Entity() .HasOne(x => x.Role) - .WithMany(x => x.Employees) - ;//.HasForeignKey(x=> x.Role); - - // modelBuilder - // .Entity() - // .Property("RoleId"); - // - // modelBuilder - // .Entity() - // .HasOne(x => x.Role) - // .WithMany() - // .HasForeignKey("RoleId"); + .WithMany(x => x.Employees); + + modelBuilder + .Entity() + .Navigation(x => x.Role) + .AutoInclude(); modelBuilder .Entity() .HasMany(x => x.Preferences) .WithMany(x => x.Customers) .UsingEntity( - "CustomerPreference", + nameof(CustomerPreference), l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("CustomersId").IsRequired(), r => r.HasOne(typeof(Preference)).WithMany().HasForeignKey("PreferencesId").IsRequired(), j => j.HasKey("CustomersId", "PreferencesId") - ) - ; + ); modelBuilder .Entity() .HasMany(x => x.PromoCodes) - .WithOne(x => x.Customer); - - - // modelBuilder - // .Entity() - // .HasMany(x => x.Id); - - - // modelBuilder - // .Entity() - // .HasData(FakeDataFactory.Roles); - // - // modelBuilder - // .Entity() - // .HasData(FakeDataFactory.Preferences); - // - // modelBuilder - // .Entity() - // .HasData(FakeDataFactory.Customers); - // - // modelBuilder - // .Entity() - // .HasData(FakeDataFactory.Employees); + .WithOne(x => x.Customer) + .OnDelete(DeleteBehavior.Cascade); + + modelBuilder + .Entity() + .Navigation(x => x.PromoCodes) + .AutoInclude(); + + modelBuilder + .Entity() + .Navigation(x => x.Preferences) + .AutoInclude(); base.OnModelCreating(modelBuilder); } diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj index b2f22684..4254b718 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj @@ -2,6 +2,7 @@ net8.0 + enable diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs index 1eb5261e..8deae478 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EfRepository.cs @@ -4,63 +4,49 @@ using Microsoft.EntityFrameworkCore; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain; -using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.DataAccess.Repositories; public class EfRepository(EfContext context) : IRepository where T : BaseEntity { - public DbSet Items { get; set; } - /// + /// public async Task> GetAllAsync() { - //await using var context = await contextFactory.CreateDbContextAsync(); var result = await context.Set().ToListAsync(); return result; } - /// - public async Task GetByIdAsync(Guid id) + /// + public async Task GetByIdAsync(Guid id) { - //await using var context = await contextFactory.CreateDbContextAsync(); - var result = await context.Set().SingleAsync(x => x.Id == id); + var result = await context.Set().SingleOrDefaultAsync(x => x.Id == id); return result; } - /// - public async Task AddAsync(Customer customer) + /// + public async Task AddAsync(T customer) { var presentCustomer = await context.Set().FirstOrDefaultAsync(x => x.Id == customer.Id); if(presentCustomer != null) throw new Exception("Customer already exists"); - await context.AddAsync(customer); - await context.SaveChangesAsync(); - } - - /// - public async Task AddAsync(T entity) - { - //await using var context = await contextFactory.CreateDbContextAsync(); - var result = await context.Set().AddAsync(entity); + var result = await context.AddAsync(customer); await context.SaveChangesAsync(); return result.Entity; } - - /// + + /// public async Task UpdateAsync(T entity) { - //await using var context = await contextFactory.CreateDbContextAsync(); var result = context.Set().Update(entity); await context.SaveChangesAsync(); return result.Entity; } - /// - public async Task DeleteAsync(T entity) + /// + public async Task RemoveAsync(T entity) { - //await using var context = await contextFactory.CreateDbContextAsync(); context.Set().Remove(entity); await context.SaveChangesAsync(); } diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs index a042965f..d99eac24 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -24,13 +24,25 @@ public Task> GetAllAsync() return Task.FromResult(Data); } - public Task GetByIdAsync(Guid id) + public Task GetByIdAsync(Guid id) { return Task.FromResult(Data.FirstOrDefault(x => x.Id == id)); } /// - public Task AddAsync(Customer customer) + public Task AddAsync(T customer) + { + throw new NotImplementedException(); + } + + /// + public Task UpdateAsync(T customer) + { + throw new NotImplementedException(); + } + + /// + public Task RemoveAsync(T customer) { throw new NotImplementedException(); } diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs index f1e04509..e910accc 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs @@ -5,7 +5,8 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.PromoCodeManagement; @@ -17,85 +18,185 @@ namespace PromoCodeFactory.WebHost.Controllers [ApiController] [Route("api/v1/[controller]")] public class CustomersController( - IRepository customersRepository - ) - : ControllerBase + ILogger logger, + IRepository customersRepository, + IRepository preferenceRepository + ) : ControllerBase { - + /// + /// Получение списка клиентов + /// + /// + /// Возвращает полный список клиентов, зарегистрированных в БД + /// + /// Список из [HttpGet] public async Task>> GetCustomersAsync() { var customers = await customersRepository.GetAllAsync(); - - return Ok(customers.Select(x => new CustomerShortResponse() + return Ok(customers.Select(x => new CustomerShortResponse { Id = x.Id, Email = x.Email, FirstName = x.FirstName, LastName = x.LastName - })); + }).ToList()); } - [HttpGet("{id}")] + /// + /// Данные о клиенте + /// + /// + /// Возвращает полные данные + /// + /// Идентификатор клиента + /// Полные данные пользователя + [HttpGet("{id:guid}")] public async Task> GetCustomerAsync(Guid id) { - //TODO: Добавить получение клиента вместе с выданными ему промокодами - var customer = await customersRepository.GetByIdAsync(id); - if (customer == null) - return NotFound(); + try + { + var customer = await customersRepository.GetByIdAsync(id); + if (customer == null) + return NotFound(); - return new CustomerResponse() + return Ok(CreateCustomerResponse(customer)); + } + catch (Exception e) { - Id = customer.Id, - Email = customer.Email, - FirstName = customer.FirstName, - LastName = customer.LastName, - PromoCodes = customer.PromoCodes.Select(x => new PromoCodeShortResponse() - { - Id = x.Id, - BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), - EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), - Code = x.Code, - PartnerName = x.PartnerName, - ServiceInfo = x.ServiceInfo - }).ToList() //await promoCodesRepository.GetByIdAsync(customer.Id). - - //customer. - }; + logger.LogError(e, "Error get customer with id [{id:D}]: {msg}", id, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + /// + /// Создание нового клиента + /// + /// Данные создаваемого клиента + /// Код 201 при успешном создании клиента, 500 в случае ошибки [HttpPost] public async Task CreateCustomerAsync(CreateOrEditCustomerRequest request) { - //TODO: Добавить создание нового клиента вместе с его предпочтениями - var customer = new Customer + try { - Id = Guid.NewGuid(), - Email = request.Email, - FirstName = request.FirstName, - LastName = request.LastName, - Preferences = [], - PromoCodes = [] - //PromoCodes = new List() - }; - - await customersRepository.AddAsync(customer); - return Created(); + var customer = new Customer + { + Id = Guid.NewGuid(), + Email = request.Email, + FirstName = request.FirstName, + LastName = request.LastName, + Preferences = await GetPreferences(request.PreferenceIds), + PromoCodes = [] + }; + await customersRepository.AddAsync(customer); + return Created( + HttpContext.Request.Path.Add(new PathString($"/{customer.Id:D}")), + CreateCustomerResponse(customer) + ); + } + catch (KeyNotFoundException e) + { + return BadRequest(e.Message); + } + catch (Exception e) + { + return StatusCode(500, e.Message); + } } - [HttpPut("{id}")] - public Task EditCustomersAsync(Guid id, CreateOrEditCustomerRequest request) + /// + /// Обновление данных клиента + /// + /// Идентификатор клиента + /// Новые данные пользователя + /// + [HttpPut("{id:guid}")] + public async Task EditCustomersAsync(Guid id, CreateOrEditCustomerRequest request) { - //TODO: Обновить данные клиента вместе с его предпочтениями - throw new NotImplementedException(); + try + { + var customer = await customersRepository.GetByIdAsync(id); + if (customer == null) + return NotFound(id); + + customer.Email = request.Email; + customer.FirstName = request.FirstName; + customer.LastName = request.LastName; + + customer.Preferences = await GetPreferences(request.PreferenceIds); + + await customersRepository.UpdateAsync(customer); + return Ok(CreateCustomerResponse(customer)); + } + catch (KeyNotFoundException e) + { + return BadRequest(e.Message); + } + catch (Exception e) + { + return StatusCode(500, e.Message); + } } + /// + /// Удаление клиента по его идентификатору + /// + /// Идентификатор клиента + /// 200 при успешном удалении, 500 при ошибке, 404 если клиент не найден [HttpDelete] - public Task DeleteCustomer(Guid id) + public async Task DeleteCustomer(Guid id) { - //TODO: Удаление клиента вместе с выданными ему промокодами - throw new NotImplementedException(); + try + { + var customer = await customersRepository.GetByIdAsync(id); + if (customer == null) + return NotFound(id); + + await customersRepository.RemoveAsync(customer); + return Ok(); + } + catch (Exception e) + { + return StatusCode(500, e.Message); + } + } + + private async Task> GetPreferences(IEnumerable preferenceIds) + { + var preferences = new List(); + foreach (var id in preferenceIds) + { + var preference = await preferenceRepository.GetByIdAsync(id); + if (preference == null) + throw new KeyNotFoundException($"Preference with id [{id}] was not found"); + preferences.Add(preference); + } + + return preferences; + } + + private static CustomerResponse CreateCustomerResponse(Customer customerEntity) + { + return new CustomerResponse + { + Id = customerEntity.Id, + Email = customerEntity.Email, + FirstName = customerEntity.FirstName, + LastName = customerEntity.LastName, + PromoCodes = customerEntity.PromoCodes.Select(x => new PromoCodeShortResponse() + { + Id = x.Id, + BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), + EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), + Code = x.Code, + PartnerName = x.PartnerName, + ServiceInfo = x.ServiceInfo + }).ToList(), + + Preferences = customerEntity.Preferences.Select(x => x.Id).ToList() + }; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs new file mode 100644 index 00000000..c50e51c9 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodeFactory.WebHost.Controllers +{ + /// + /// Предпочтения + /// + [ApiController] + [Route("api/v1/[controller]")] + public class PreferencesController( + IRepository preferencesRepository + ) : ControllerBase + { + /// + /// Получить все предпочтения + /// + /// + [HttpGet] + public async Task>> GetPreferencesAsync() + { + var codes = await preferencesRepository.GetAllAsync(); + + return Ok(codes.Select(x => new PreferenceResponse() + { + Name = x.Name, + Value = x.Value + }).ToList()); + } + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs index 5b96c327..e593e699 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.WebHost.Models; namespace PromoCodeFactory.WebHost.Controllers @@ -11,18 +15,29 @@ namespace PromoCodeFactory.WebHost.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public class PromocodesController - : ControllerBase + public class PromocodesController( + IRepository codesRepository, + IRepository preferencesRepository + ) : ControllerBase { /// /// Получить все промокоды /// /// [HttpGet] - public Task>> GetPromocodesAsync() + public async Task>> GetPromocodesAsync() { - //TODO: Получить все промокоды - throw new NotImplementedException(); + var codes = await codesRepository.GetAllAsync(); + + return Ok(codes.Select(x => new PromoCodeShortResponse + { + Id = x.Id, + Code = x.Code, + BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), + EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), + PartnerName = x.PartnerName, + ServiceInfo = x.ServiceInfo + }).ToList()); } /// @@ -30,10 +45,37 @@ public Task>> GetPromocodesAsync() /// /// [HttpPost] - public Task GivePromoCodesToCustomersWithPreferenceAsync(GivePromoCodeRequest request) + public async Task GivePromoCodesToCustomersWithPreferenceAsync(GivePromoCodeRequest request) { - //TODO: Создать промокод и выдать его клиентам с указанным предпочтением - throw new NotImplementedException(); + var preference = (await preferencesRepository.GetAllAsync()) + .FirstOrDefault(x => x.Name == request.Preference); + + if (preference == null) + return BadRequest("Preference not found"); + + var code = new PromoCode + { + Id = Guid.NewGuid(), + PartnerName = request.PartnerName, + ServiceInfo = request.ServiceInfo, + Code = request.PromoCode, + Preference = preference, + + //TODO: fix this + BeginDate = DateTime.UtcNow, + Customer = null, + PartnerManager = null + }; + + code = await codesRepository.AddAsync(code); + + foreach (var customer in preference.Customers) + { + customer.PromoCodes.Add(code); + } + + await preferencesRepository.UpdateAsync(preference); + return Ok(code); } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CreateOrEditCustomerRequest.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CreateOrEditCustomerRequest.cs index 59f313fb..2717ae69 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CreateOrEditCustomerRequest.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CreateOrEditCustomerRequest.cs @@ -1,13 +1,21 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace PromoCodeFactory.WebHost.Models { public class CreateOrEditCustomerRequest { - public string FirstName { get; set; } - public string LastName { get; set; } - public string Email { get; set; } - public List PreferenceIds { get; set; } + [JsonInclude] + public string FirstName { get; set; } = string.Empty; + + [JsonInclude] + public string LastName { get; set; } = string.Empty; + + [JsonInclude] + public string Email { get; set; } = string.Empty; + + [JsonInclude] + public List PreferenceIds { get; set; } = []; } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs index 97ea3e6b..83899a74 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs @@ -6,10 +6,11 @@ namespace PromoCodeFactory.WebHost.Models public class CustomerResponse { public Guid Id { get; set; } - public string FirstName { get; set; } - public string LastName { get; set; } - public string Email { get; set; } + public string FirstName { get; set; } = string.Empty; + public string LastName { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; //TODO: Добавить список предпочтений - public List PromoCodes { get; set; } + public List PromoCodes { get; set; } = []; + public List Preferences { get; set; } = []; } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/PreferenceResponse.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/PreferenceResponse.cs new file mode 100644 index 00000000..d2777aa3 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/PreferenceResponse.cs @@ -0,0 +1,12 @@ +using System; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.WebHost.Models +{ + public class PreferenceResponse + { + public string Name { get; set; } + + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj b/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj index 387bd67e..0c6859e5 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj @@ -4,6 +4,7 @@ net8.0 true $(NoWarn);1591 + enable diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs index f1af7224..57f0905f 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -8,7 +9,6 @@ using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.DataAccess; -using PromoCodeFactory.DataAccess.Data; using PromoCodeFactory.DataAccess.Repositories; namespace PromoCodeFactory.WebHost @@ -21,16 +21,6 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); - // services.AddScoped(typeof(IRepository), (x) => - // new InMemoryRepository(FakeDataFactory.Employees)); - // services.AddScoped(typeof(IRepository), (x) => - // new InMemoryRepository(FakeDataFactory.Roles)); - // services.AddScoped(typeof(IRepository), (x) => - // new InMemoryRepository(FakeDataFactory.Preferences)); - // services.AddScoped(typeof(IRepository), (x) => - // new InMemoryRepository(FakeDataFactory.Customers)); - - services.AddScoped, EfRepository>(); services.AddScoped, EfRepository>(); services.AddScoped, EfRepository>(); @@ -54,9 +44,6 @@ public void ConfigureServices(IServiceCollection services) options.Title = "PromoCode Factory API Doc"; options.Version = "1.0"; }); - - - } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -65,6 +52,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, EfContex if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); + + Console.WriteLine("EnsureCreated/EnsureDeleted"); efContext.Database.EnsureDeleted(); efContext.Database.EnsureCreated(); } diff --git a/Homeworks/EF/src/PromoCodeFactory.sln b/Homeworks/EF/src/PromoCodeFactory.sln index 39e21828..5deaad31 100644 --- a/Homeworks/EF/src/PromoCodeFactory.sln +++ b/Homeworks/EF/src/PromoCodeFactory.sln @@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoCodeFactory.DataAccess EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PromoCodeFactory.Core", "PromoCodeFactory.Core\PromoCodeFactory.Core.csproj", "{FA64A24E-D404-4E7D-89A8-B87E134F697F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PromoCodesFactory.WebHost.Tests", "PromoCodesFactory.WebHost.Tests\PromoCodesFactory.WebHost.Tests.csproj", "{442B07B0-2981-43CB-80D6-4EEEA90A93F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA64A24E-D404-4E7D-89A8-B87E134F697F}.Release|Any CPU.Build.0 = Release|Any CPU + {442B07B0-2981-43CB-80D6-4EEEA90A93F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B07B0-2981-43CB-80D6-4EEEA90A93F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B07B0-2981-43CB-80D6-4EEEA90A93F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B07B0-2981-43CB-80D6-4EEEA90A93F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs new file mode 100644 index 00000000..a54ce240 --- /dev/null +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs @@ -0,0 +1,146 @@ +using System.Net.Http.Json; +using FluentAssertions; +using PromoCodeFactory.DataAccess.Data; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodesFactory.WebHost.Tests; + +public class CustomerControllerTests(WebHostFixture fixture) : IClassFixture +{ + [Fact] + public async Task Get_Should_Return_Status_200() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Customers"); + response.Should().Be200Ok(); + } + + [Fact] + public async Task Get_Should_Return_List_With_One_Customer() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Customers"); + response.Should().Be200Ok(); + + var content = (await response.Content.ReadFromJsonAsync>())?.ToArray(); + content.Should() + .NotBeNull() + .And.HaveCount(1) + .And.AllSatisfy(x => + { + x.Id.Should().Be(FakeDataFactory.Customers.First().Id); + x.Email.Should().Be(FakeDataFactory.Customers.First().Email); + x.FirstName.Should().Be(FakeDataFactory.Customers.First().FirstName); + x.LastName.Should().Be(FakeDataFactory.Customers.First().LastName); + } + ); + } + + [Fact] + public async Task Post_Should_Return_Status_201_On_Valid_Request() + { + var fakeCustomer = FakeDataFactory.Customers.First(); + var fakePreference = FakeDataFactory.Preferences.First(); + var client = new WebHostFixture().CreateClient(); + var response = await client.PostAsJsonAsync("api/v1/Customers", + new CreateOrEditCustomerRequest + { + FirstName = fakeCustomer.FirstName, + LastName = fakeCustomer.LastName, + Email = fakeCustomer.Email, + PreferenceIds = [fakePreference.Id] + }); + response.Should().Be201Created(); + + var content = await response.Content.ReadFromJsonAsync(); + content.Should() + .NotBeNull() + .And.Satisfy(x => + { + x.Email.Should().Be(fakeCustomer.Email); + x.FirstName.Should().Be(fakeCustomer.FirstName); + x.LastName.Should().Be(fakeCustomer.LastName); + x.Preferences.Contains(fakePreference.Id).Should().BeTrue(); + } + ); + } + + [Fact] + public async Task Post_Should_Return_Status_400_On_Preference_Missing() + { + var fakeCustomer = FakeDataFactory.Customers.First(); + var client = fixture.CreateClient(); + var response = await client.PostAsJsonAsync("api/v1/Customers", + new CreateOrEditCustomerRequest + { + FirstName = fakeCustomer.FirstName, + LastName = fakeCustomer.LastName, + Email = fakeCustomer.Email, + PreferenceIds = [Guid.NewGuid()] + }); + response.Should().Be400BadRequest(); + } + + [Fact] + public async Task Put_Should_Return_Status_200_On_Valid_Request() + { + var fakeCustomer = FakeDataFactory.Customers.First(); + var fakePreference1 = FakeDataFactory.Preferences.First(); + var fakePreference2 = FakeDataFactory.Preferences.First(); + var client = new WebHostFixture().CreateClient(); + var response = await client.PutAsJsonAsync($"api/v1/Customers/{fakeCustomer.Id:D}", + new CreateOrEditCustomerRequest + { + FirstName = fakeCustomer.FirstName, + LastName = fakeCustomer.LastName, + Email = fakeCustomer.Email, + PreferenceIds = [fakePreference1.Id, fakePreference2.Id] + }); + response.Should().Be200Ok(); + + var content = await response.Content.ReadFromJsonAsync(); + content.Should() + .NotBeNull() + .And.Satisfy(x => + { + x.Email.Should().Be(fakeCustomer.Email); + x.FirstName.Should().Be(fakeCustomer.FirstName); + x.LastName.Should().Be(fakeCustomer.LastName); + x.Preferences.Contains(fakePreference1.Id).Should().BeTrue(); + x.Preferences.Contains(fakePreference2.Id).Should().BeTrue(); + } + ); + } + + [Fact] + public async Task Put_Should_Return_Status_400_If_Preference_Not_Exists() + { + var fakeCustomer = FakeDataFactory.Customers.First(); + var client = fixture.CreateClient(); + var response = await client.PutAsJsonAsync($"api/v1/Customers/{fakeCustomer.Id:D}", + new CreateOrEditCustomerRequest + { + FirstName = fakeCustomer.FirstName, + LastName = fakeCustomer.LastName, + Email = fakeCustomer.Email, + PreferenceIds = [Guid.NewGuid()] + }); + response.Should().Be400BadRequest(); + } + + [Fact] + public async Task Put_Should_Return_Status_404_If_Customer_Not_Exists() + { + var fakeCustomer = FakeDataFactory.Customers.First(); + var client = fixture.CreateClient(); + var response = await client.PutAsJsonAsync($"api/v1/Customers/{Guid.NewGuid():D}", + new CreateOrEditCustomerRequest + { + FirstName = fakeCustomer.FirstName, + LastName = fakeCustomer.LastName, + Email = fakeCustomer.Email, + PreferenceIds = [Guid.NewGuid()] + }); + response.Should().Be404NotFound(); + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs new file mode 100644 index 00000000..92e5baeb --- /dev/null +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs @@ -0,0 +1,55 @@ +using System.Net.Http.Json; +using FluentAssertions; +using PromoCodeFactory.DataAccess.Data; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodesFactory.WebHost.Tests; + +public class PreferenceControllerTests(WebHostFixture fixture) : IClassFixture +{ + [Fact] + public async Task Get_Should_Return_Status_200() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Preferences"); + response.Should().Be200Ok(); + } + + [Fact] + public async Task Get_Should_Return_List_With_One_Customer() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Preferences"); + response.Should().Be200Ok(); + + var content = (await response.Content.ReadFromJsonAsync>())?.ToArray(); + content.Should() + .NotBeNull() + .And.HaveCount(FakeDataFactory.Preferences.Count()); + } + + [Fact] + public async Task Post_Should_Return_Status_405() + { + var client = fixture.CreateClient(); + var response = await client.PostAsJsonAsync("api/v1/Preferences", new { Test = "dummy" }); + response.Should().Be405MethodNotAllowed(); + } + + [Fact] + public async Task Put_Should_Return_Status_404() + { + var client = fixture.CreateClient(); + var response = await client.PutAsJsonAsync($"api/v1/Preferences/{Guid.NewGuid():D}", new { Test = "dummy" }); + response.Should().Be404NotFound(); + } + + + [Fact] + public async Task Delete_Should_Return_Status_404() + { + var client = fixture.CreateClient(); + var response = await client.DeleteAsync($"api/v1/Preferences/{Guid.NewGuid():D}"); + response.Should().Be404NotFound(); + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs new file mode 100644 index 00000000..e2b0eb34 --- /dev/null +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs @@ -0,0 +1,70 @@ +using System.Net.Http.Json; +using FluentAssertions; +using PromoCodeFactory.DataAccess.Data; +using PromoCodeFactory.WebHost.Models; + +namespace PromoCodesFactory.WebHost.Tests; + +public class PromocodesControllerTests(WebHostFixture fixture) : IClassFixture +{ + [Fact] + public async Task Get_Should_Return_Status_200() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Promocodes"); + response.Should().Be200Ok(); + } + + [Fact] + public async Task Get_Should_Return_List_With_Customer() + { + var client = fixture.CreateClient(); + var response = await client.GetAsync("api/v1/Promocodes"); + response.Should().Be200Ok(); + + var content = (await response.Content.ReadFromJsonAsync>())?.ToArray(); + content.Should() + .NotBeNull() + .And.BeEmpty(); + } + + [Fact] + public async Task Post_Should_Return_Status_405() + { + var client = fixture.CreateClient(); + var response = await client.PostAsJsonAsync("api/v1/Promocodes", new GivePromoCodeRequest + { + PartnerName = FakeDataFactory.Employees.First().Id.ToString("D"), + ServiceInfo = "SvcInfo New", + Preference = "Театр", + PromoCode = "CODE_TEST" + + }); + response.Should().Be200Ok(); + } + + // [Fact] + // public async Task Post_Should_Return_Status_405() + // { + // var client = fixture.CreateClient(); + // var response = await client.PostAsJsonAsync("api/v1/Preferences", new { Test = "dummy" }); + // response.Should().Be405MethodNotAllowed(); + // } + // + // [Fact] + // public async Task Put_Should_Return_Status_404() + // { + // var client = fixture.CreateClient(); + // var response = await client.PutAsJsonAsync($"api/v1/Preferences/{Guid.NewGuid():D}", new { Test = "dummy" }); + // response.Should().Be404NotFound(); + // } + // + // + // [Fact] + // public async Task Delete_Should_Return_Status_404() + // { + // var client = fixture.CreateClient(); + // var response = await client.DeleteAsync($"api/v1/Preferences/{Guid.NewGuid():D}"); + // response.Should().Be404NotFound(); + // } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs new file mode 100644 index 00000000..3cf4c756 --- /dev/null +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Mvc.Testing; +using PromoCodeFactory.WebHost; + +namespace PromoCodesFactory.WebHost.Tests; + +public class WebHostFixture : WebApplicationFactory +{ + +} \ No newline at end of file From 5a3dbad174ae54b6e68e65218706f150d14af432 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Thu, 2 Oct 2025 00:08:54 +0300 Subject: [PATCH 7/9] tests migrated to InMemoryDatabase --- .../Domain/PromoCodeManagement/Customer.cs | 6 +++ .../Domain/PromoCodeManagement/PromoCode.cs | 3 +- .../PromoCodeFactory.DataAccess/EfContext.cs | 15 +++++- .../Controllers/PromocodesController.cs | 27 +++++----- .../CustomersControllerTests.cs | 16 +++--- .../PreferenceControllerTests.cs | 10 ++-- .../PromocodesControllerTests.cs | 40 +++----------- .../WebHostFixture.cs | 52 ++++++++++++++++++- 8 files changed, 107 insertions(+), 62 deletions(-) diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index 46353b7a..681e8e0b 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -12,6 +12,12 @@ public class CustomerPreference : BaseEntity public Guid PreferencesId { get; set; } } + public class CustomerPromoCode : BaseEntity + { + public Guid CustomersId { get; set; } + public Guid PromoCodesId { get; set; } + } + public class Customer : BaseEntity { diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs index 0170812d..52415e45 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime; using PromoCodeFactory.Core.Domain; @@ -26,6 +27,6 @@ public class PromoCode public Preference Preference { get; set; } - public Customer Customer { get; set; } + public ICollection Customers { get; set; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs index 343deade..25973feb 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/EfContext.cs @@ -77,8 +77,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder .Entity() .HasMany(x => x.PromoCodes) - .WithOne(x => x.Customer) - .OnDelete(DeleteBehavior.Cascade); + .WithMany(x => x.Customers) + .UsingEntity( + nameof(CustomerPromoCode), + l => l.HasOne(typeof(Customer)).WithMany().HasForeignKey("CustomersId").IsRequired(), + r => r.HasOne(typeof(PromoCode)).WithMany().HasForeignKey("PromoCodesId").IsRequired(), + j => j.HasKey("CustomersId", "PromoCodesId") + ); + + // modelBuilder + // .Entity() + // .HasMany(x => x.PromoCodes) + // .WithOne(x => x.Customer) + // .OnDelete(DeleteBehavior.Cascade); modelBuilder .Entity() diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs index e593e699..5afa578a 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.WebHost.Models; @@ -17,7 +18,10 @@ namespace PromoCodeFactory.WebHost.Controllers [Route("api/v1/[controller]")] public class PromocodesController( IRepository codesRepository, - IRepository preferencesRepository + IRepository preferencesRepository, + IRepository customersRepository, + IRepository employeeRepository + ) : ControllerBase { /// @@ -52,7 +56,10 @@ public async Task GivePromoCodesToCustomersWithPreferenceAsync(Gi if (preference == null) return BadRequest("Preference not found"); - + + // extend IRepo with IPromocodesRepo: FindPrefByName, Findcust + + var employees = await employeeRepository.GetAllAsync(); var code = new PromoCode { Id = Guid.NewGuid(), @@ -61,21 +68,13 @@ public async Task GivePromoCodesToCustomersWithPreferenceAsync(Gi Code = request.PromoCode, Preference = preference, - //TODO: fix this BeginDate = DateTime.UtcNow, - Customer = null, - PartnerManager = null + Customers = preference.Customers.ToList(), + PartnerManager = employees.Single(x => x.FullName == request.PartnerName) }; - code = await codesRepository.AddAsync(code); - - foreach (var customer in preference.Customers) - { - customer.PromoCodes.Add(code); - } - - await preferencesRepository.UpdateAsync(preference); - return Ok(code); + await codesRepository.AddAsync(code); + return Ok(); } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs index a54ce240..fde9e805 100644 --- a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/CustomersControllerTests.cs @@ -7,10 +7,12 @@ namespace PromoCodesFactory.WebHost.Tests; public class CustomerControllerTests(WebHostFixture fixture) : IClassFixture { + + [Fact] public async Task Get_Should_Return_Status_200() { - var client = fixture.CreateClient(); + var client = fixture.GetClient(); var response = await client.GetAsync("api/v1/Customers"); response.Should().Be200Ok(); } @@ -18,7 +20,7 @@ public async Task Get_Should_Return_Status_200() [Fact] public async Task Get_Should_Return_List_With_One_Customer() { - var client = fixture.CreateClient(); + var client = fixture.GetClient(); var response = await client.GetAsync("api/v1/Customers"); response.Should().Be200Ok(); @@ -41,7 +43,7 @@ public async Task Post_Should_Return_Status_201_On_Valid_Request() { var fakeCustomer = FakeDataFactory.Customers.First(); var fakePreference = FakeDataFactory.Preferences.First(); - var client = new WebHostFixture().CreateClient(); + var client = fixture.GetClient(true); var response = await client.PostAsJsonAsync("api/v1/Customers", new CreateOrEditCustomerRequest { @@ -69,7 +71,7 @@ public async Task Post_Should_Return_Status_201_On_Valid_Request() public async Task Post_Should_Return_Status_400_On_Preference_Missing() { var fakeCustomer = FakeDataFactory.Customers.First(); - var client = fixture.CreateClient(); + var client = fixture.GetClient(true); var response = await client.PostAsJsonAsync("api/v1/Customers", new CreateOrEditCustomerRequest { @@ -87,7 +89,7 @@ public async Task Put_Should_Return_Status_200_On_Valid_Request() var fakeCustomer = FakeDataFactory.Customers.First(); var fakePreference1 = FakeDataFactory.Preferences.First(); var fakePreference2 = FakeDataFactory.Preferences.First(); - var client = new WebHostFixture().CreateClient(); + var client = fixture.GetClient(true); var response = await client.PutAsJsonAsync($"api/v1/Customers/{fakeCustomer.Id:D}", new CreateOrEditCustomerRequest { @@ -116,7 +118,7 @@ public async Task Put_Should_Return_Status_200_On_Valid_Request() public async Task Put_Should_Return_Status_400_If_Preference_Not_Exists() { var fakeCustomer = FakeDataFactory.Customers.First(); - var client = fixture.CreateClient(); + var client = fixture.GetClient(true); var response = await client.PutAsJsonAsync($"api/v1/Customers/{fakeCustomer.Id:D}", new CreateOrEditCustomerRequest { @@ -132,7 +134,7 @@ public async Task Put_Should_Return_Status_400_If_Preference_Not_Exists() public async Task Put_Should_Return_Status_404_If_Customer_Not_Exists() { var fakeCustomer = FakeDataFactory.Customers.First(); - var client = fixture.CreateClient(); + var client = fixture.GetClient(true); var response = await client.PutAsJsonAsync($"api/v1/Customers/{Guid.NewGuid():D}", new CreateOrEditCustomerRequest { diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs index 92e5baeb..dc36bc5b 100644 --- a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PreferenceControllerTests.cs @@ -10,7 +10,7 @@ public class PreferenceControllerTests(WebHostFixture fixture) : IClassFixture { + /// + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.UseEnvironment("Test"); + builder.ConfigureTestServices(services => + { + var descriptor = + services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextOptionsConfiguration)); + if (descriptor != null) + services.Remove(descriptor); + + var id = Guid.NewGuid().ToString("D"); + + services.AddDbContext(options => + options.UseInMemoryDatabase(id) + .UseSeeding((c,_) => EfContext.SeedData(c)) + .UseAsyncSeeding((c,_, t) => + { + EfContext.SeedData(c); + return Task.CompletedTask; + })); + }); + base.ConfigureWebHost(builder); + } + + + internal HttpClient GetClient(bool standalone = false) + { + using var scope = Services.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + return CreateClient(); + } + + /// + protected override void Dispose(bool disposing) + { + Console.WriteLine("DISPOSE"); + base.Dispose(disposing); + } } \ No newline at end of file From b8d0a2c3b963f61679eff6ba5fd4ed2aa84931f3 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Thu, 2 Oct 2025 21:10:30 +0300 Subject: [PATCH 8/9] =?UTF-8?q?=D0=94=D0=97=20=D0=BA=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D1=83=D0=BB=D1=8E=20=E2=84=96=205=20=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/PromoCodeManagement/Customer.cs | 16 +--- .../PromoCodeManagement/CustomerPreference.cs | 9 +++ .../PromoCodeManagement/CustomerPromoCode.cs | 9 +++ .../Data/FakeDataFactory.cs | 34 -------- .../PromoCodeFactory.DataAccess.csproj | 4 - .../Controllers/CustomersController.cs | 28 +++++-- .../Controllers/EmployeesController.cs | 76 +++++++++-------- .../Controllers/PreferencesController.cs | 25 ++++-- .../Controllers/PromocodesController.cs | 81 +++++++++++-------- .../Controllers/RolesController.cs | 44 +++++----- .../src/PromoCodeFactory.WebHost/Startup.cs | 4 +- .../PromoCodesFactory.WebHost.Tests.csproj | 32 ++++++++ .../PromocodesControllerTests.cs | 63 ++++++++++++++- .../WebHostFixture.cs | 17 ++-- 14 files changed, 278 insertions(+), 164 deletions(-) create mode 100644 Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPromoCode.cs create mode 100644 Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromoCodesFactory.WebHost.Tests.csproj diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index 681e8e0b..eafc0087 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -1,23 +1,10 @@ using PromoCodeFactory.Core.Domain; -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { - public class CustomerPreference : BaseEntity - { - public Guid CustomersId { get; set; } - public Guid PreferencesId { get; set; } - } - - public class CustomerPromoCode : BaseEntity - { - public Guid CustomersId { get; set; } - public Guid PromoCodesId { get; set; } - } - public class Customer : BaseEntity { @@ -32,6 +19,9 @@ public class Customer [StringLength(50)] public string Email { get; set; } + + [StringLength(512)] + public string? Address { get; set; } public ICollection Preferences { get; set; } = new List(); public ICollection PromoCodes { get; set; } = new List(); diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs new file mode 100644 index 00000000..b29ddb82 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs @@ -0,0 +1,9 @@ +using System; + +namespace PromoCodeFactory.Core.Domain.PromoCodeManagement; + +public class CustomerPreference : BaseEntity +{ + public Guid CustomersId { get; set; } + public Guid PreferencesId { get; set; } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPromoCode.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPromoCode.cs new file mode 100644 index 00000000..6ef7475f --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPromoCode.cs @@ -0,0 +1,9 @@ +using System; + +namespace PromoCodeFactory.Core.Domain.PromoCodeManagement; + +public class CustomerPromoCode : BaseEntity +{ + public Guid CustomersId { get; set; } + public Guid PromoCodesId { get; set; } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs index ad6398c1..4901e49e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs @@ -81,7 +81,6 @@ public static IEnumerable Customers Email = "ivan_sergeev@mail.ru", FirstName = "Иван", LastName = "Петров", - //TODO: Добавить предзаполненный список предпочтений Preferences = Preferences.Where(x => x.Name == "Театр" || x.Name == "Семья").ToList() } }; @@ -89,38 +88,5 @@ public static IEnumerable Customers return customers; } } - - // public static IEnumerable PromoCodes => new List([ - // new () - // { - // Id = Guid.NewGuid(), - // Code = "Code-1", - // BeginDate = DateTime.UtcNow, - // EndDate = DateTime.UtcNow.AddDays(10), - // PartnerName = "Partner-1", - // ServiceInfo = "SvcInfo-1" - // }, - // new () - // { - // Id = Guid.NewGuid(), - // Code = "Code-2", - // BeginDate = DateTime.UtcNow, - // EndDate = DateTime.UtcNow.AddDays(10), - // PartnerName = "Partner-2", - // ServiceInfo = "SvcInfo-2" - // }, - // new () - // { - // Id = Guid.NewGuid(), - // Code = "Code-3", - // BeginDate = DateTime.UtcNow, - // EndDate = DateTime.UtcNow.AddDays(10), - // PartnerName = "Partner-3", - // ServiceInfo = "SvcInfo-3" - // } - // - // ]); - - } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj index 4254b718..e4f6d03b 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj @@ -18,8 +18,4 @@ - - - - diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs index e910accc..3b6e9c5d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs @@ -33,14 +33,22 @@ IRepository preferenceRepository [HttpGet] public async Task>> GetCustomersAsync() { - var customers = await customersRepository.GetAllAsync(); - return Ok(customers.Select(x => new CustomerShortResponse + try + { + var customers = await customersRepository.GetAllAsync(); + return Ok(customers.Select(x => new CustomerShortResponse + { + Id = x.Id, + Email = x.Email, + FirstName = x.FirstName, + LastName = x.LastName + }).ToList()); + } + catch (Exception e) { - Id = x.Id, - Email = x.Email, - FirstName = x.FirstName, - LastName = x.LastName - }).ToList()); + logger.LogError(e, "Failed to get list of customers: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } /// @@ -67,7 +75,6 @@ public async Task> GetCustomerAsync(Guid id) logger.LogError(e, "Error get customer with id [{id:D}]: {msg}", id, e.Message); return StatusCode(StatusCodes.Status500InternalServerError, e.Message); } - } /// @@ -98,10 +105,12 @@ public async Task CreateCustomerAsync(CreateOrEditCustomerRequest } catch (KeyNotFoundException e) { + logger.LogError(e, "Bad request on customer creation: {msg}", e.Message); return BadRequest(e.Message); } catch (Exception e) { + logger.LogError(e, "Error on customer creation: {msg}", e.Message); return StatusCode(500, e.Message); } } @@ -132,10 +141,12 @@ public async Task EditCustomersAsync(Guid id, CreateOrEditCustome } catch (KeyNotFoundException e) { + logger.LogError(e, "Bad request on customer update: {msg}", e.Message); return BadRequest(e.Message); } catch (Exception e) { + logger.LogError(e, "Error on customer update: {msg}", e.Message); return StatusCode(500, e.Message); } } @@ -159,6 +170,7 @@ public async Task DeleteCustomer(Guid id) } catch (Exception e) { + logger.LogError(e, "Error on customer deletion: {msg}", e.Message); return StatusCode(500, e.Message); } } diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs index 2dd31dd2..7b5ac04f 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.WebHost.Models; @@ -14,34 +15,35 @@ namespace PromoCodeFactory.WebHost.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public class EmployeesController + public class EmployeesController(ILogger logger, IRepository employeeRepository) : ControllerBase { - private readonly IRepository _employeeRepository; - - public EmployeesController(IRepository employeeRepository) - { - _employeeRepository = employeeRepository; - } - /// /// Получить данные всех сотрудников /// /// [HttpGet] - public async Task> GetEmployeesAsync() + public async Task>> GetEmployeesAsync() { - var employees = await _employeeRepository.GetAllAsync(); + try + { + var employees = await employeeRepository.GetAllAsync(); - var employeesModelList = employees.Select(x => - new EmployeeShortResponse() - { - Id = x.Id, - Email = x.Email, - FullName = x.FullName, - }).ToList(); + var employeesModelList = employees.Select(x => + new EmployeeShortResponse() + { + Id = x.Id, + Email = x.Email, + FullName = x.FullName, + }).ToList(); - return employeesModelList; + return Ok(employeesModelList); + } + catch (Exception e) + { + logger.LogError(e, "Error get employees list: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } /// @@ -51,25 +53,33 @@ public async Task> GetEmployeesAsync() [HttpGet("{id:guid}")] public async Task> GetEmployeeByIdAsync(Guid id) { - var employee = await _employeeRepository.GetByIdAsync(id); + try + { + var employee = await employeeRepository.GetByIdAsync(id); - if (employee == null) - return NotFound(); + if (employee == null) + return NotFound(); - var employeeModel = new EmployeeResponse() - { - Id = employee.Id, - Email = employee.Email, - Role = new RoleItemResponse() + var employeeModel = new EmployeeResponse() { - Name = employee.Role.Name, - Description = employee.Role.Description - }, - FullName = employee.FullName, - AppliedPromocodesCount = employee.AppliedPromocodesCount - }; + Id = employee.Id, + Email = employee.Email, + Role = new RoleItemResponse() + { + Name = employee.Role.Name, + Description = employee.Role.Description + }, + FullName = employee.FullName, + AppliedPromocodesCount = employee.AppliedPromocodesCount + }; - return employeeModel; + return employeeModel; + } + catch (Exception e) + { + logger.LogError(e, "Error get employee with id [{id:D}] list: [{msg}]", id, e.Message); + return StatusCode(500, e.Message); + } } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs index c50e51c9..29d6ccc2 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.WebHost.Models; @@ -14,8 +16,9 @@ namespace PromoCodeFactory.WebHost.Controllers [ApiController] [Route("api/v1/[controller]")] public class PreferencesController( + ILogger logger, IRepository preferencesRepository - ) : ControllerBase + ) : ControllerBase { /// /// Получить все предпочтения @@ -24,13 +27,21 @@ IRepository preferencesRepository [HttpGet] public async Task>> GetPreferencesAsync() { - var codes = await preferencesRepository.GetAllAsync(); + try + { + var codes = await preferencesRepository.GetAllAsync(); - return Ok(codes.Select(x => new PreferenceResponse() + return Ok(codes.Select(x => new PreferenceResponse() + { + Name = x.Name, + Value = x.Value + }).ToList()); + } + catch (Exception e) { - Name = x.Name, - Value = x.Value - }).ToList()); + logger.LogError(e, "Error get preferences list: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs index 5afa578a..2cea8a19 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; @@ -17,12 +18,12 @@ namespace PromoCodeFactory.WebHost.Controllers [ApiController] [Route("api/v1/[controller]")] public class PromocodesController( + ILogger logger, IRepository codesRepository, IRepository preferencesRepository, IRepository customersRepository, IRepository employeeRepository - - ) : ControllerBase + ) : ControllerBase { /// /// Получить все промокоды @@ -31,17 +32,25 @@ IRepository employeeRepository [HttpGet] public async Task>> GetPromocodesAsync() { - var codes = await codesRepository.GetAllAsync(); + try + { + var codes = await codesRepository.GetAllAsync(); - return Ok(codes.Select(x => new PromoCodeShortResponse + return Ok(codes.Select(x => new PromoCodeShortResponse + { + Id = x.Id, + Code = x.Code, + BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), + EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), + PartnerName = x.PartnerName, + ServiceInfo = x.ServiceInfo + }).ToList()); + } + catch (Exception e) { - Id = x.Id, - Code = x.Code, - BeginDate = x.BeginDate.ToString(CultureInfo.InvariantCulture), - EndDate = x.EndDate.ToString(CultureInfo.InvariantCulture), - PartnerName = x.PartnerName, - ServiceInfo = x.ServiceInfo - }).ToList()); + logger.LogError(e, "Error get promocodes list: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } /// @@ -51,30 +60,36 @@ public async Task>> GetPromocodesAsync [HttpPost] public async Task GivePromoCodesToCustomersWithPreferenceAsync(GivePromoCodeRequest request) { - var preference = (await preferencesRepository.GetAllAsync()) - .FirstOrDefault(x => x.Name == request.Preference); - - if (preference == null) - return BadRequest("Preference not found"); - - // extend IRepo with IPromocodesRepo: FindPrefByName, Findcust - - var employees = await employeeRepository.GetAllAsync(); - var code = new PromoCode + try { - Id = Guid.NewGuid(), - PartnerName = request.PartnerName, - ServiceInfo = request.ServiceInfo, - Code = request.PromoCode, - Preference = preference, - - BeginDate = DateTime.UtcNow, - Customers = preference.Customers.ToList(), - PartnerManager = employees.Single(x => x.FullName == request.PartnerName) - }; + var preference = (await preferencesRepository.GetAllAsync()) + .FirstOrDefault(x => x.Name == request.Preference); + + if (preference == null) + return BadRequest("Preference not found"); - await codesRepository.AddAsync(code); - return Ok(); + var employees = await employeeRepository.GetAllAsync(); + var code = new PromoCode + { + Id = Guid.NewGuid(), + PartnerName = request.PartnerName, + ServiceInfo = request.ServiceInfo, + Code = request.PromoCode, + Preference = preference, + + BeginDate = DateTime.UtcNow, + Customers = preference.Customers.ToList(), + PartnerManager = employees.Single(x => x.FullName == request.PartnerName) + }; + + await codesRepository.AddAsync(code); + return Ok(); + } + catch (Exception e) + { + logger.LogError(e, "Error apply promocodes: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/RolesController.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/RolesController.cs index 4de2687f..f432cc0b 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/RolesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/RolesController.cs @@ -1,7 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.WebHost.Models; @@ -13,33 +15,37 @@ namespace PromoCodeFactory.WebHost.Controllers /// [ApiController] [Route("api/v1/[controller]")] - public class RolesController + public class RolesController( + ILogger logger, + IRepository rolesRepository + ) : ControllerBase { - private readonly IRepository _rolesRepository; - - public RolesController(IRepository rolesRepository) - { - _rolesRepository = rolesRepository; - } - /// /// Получить все доступные роли сотрудников /// /// [HttpGet] - public async Task> GetRolesAsync() + public async Task>> GetRolesAsync() { - var roles = await _rolesRepository.GetAllAsync(); + try + { + var roles = await rolesRepository.GetAllAsync(); - var rolesModelList = roles.Select(x => - new RoleItemResponse() - { - Id = x.Id, - Name = x.Name, - Description = x.Description - }).ToList(); + var rolesModelList = roles.Select(x => + new RoleItemResponse() + { + Id = x.Id, + Name = x.Name, + Description = x.Description + }).ToList(); - return rolesModelList; + return rolesModelList; + } + catch (Exception e) + { + logger.LogError(e, "Error get roles list: [{msg}]", e.Message); + return StatusCode(500, e.Message); + } } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs index 57f0905f..4b371790 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; @@ -31,6 +32,7 @@ public void ConfigureServices(IServiceCollection services) { options .UseSqlite("Data Source=PromoCodeFactory.db") + .LogTo(Console.WriteLine, LogLevel.Trace) .UseSeeding((c,b) => EfContext.SeedData(c)) .UseAsyncSeeding((c,b, t) => { @@ -52,8 +54,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, EfContex if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - - Console.WriteLine("EnsureCreated/EnsureDeleted"); efContext.Database.EnsureDeleted(); efContext.Database.EnsureCreated(); } diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromoCodesFactory.WebHost.Tests.csproj b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromoCodesFactory.WebHost.Tests.csproj new file mode 100644 index 00000000..1d2a09e4 --- /dev/null +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromoCodesFactory.WebHost.Tests.csproj @@ -0,0 +1,32 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs index d690a1b3..e4808a39 100644 --- a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/PromocodesControllerTests.cs @@ -29,7 +29,7 @@ public async Task Get_Should_Return_Empty_List() } [Fact] - public async Task Post_Should_Return_Status_200() + public async Task Post_Should_Add_Promocode_To_Customer() { var client = fixture.GetClient(true); var response = await client.PostAsJsonAsync("api/v1/Promocodes", new GivePromoCodeRequest @@ -41,6 +41,65 @@ public async Task Post_Should_Return_Status_200() }); response.Should().Be200Ok(); - //TODO: add extended testing + var customerResponse = await client.GetAsync($"api/v1/Customers/{FakeDataFactory.Customers.First().Id}"); + customerResponse.Should().Be200Ok(); + + var customer = await customerResponse.Content.ReadFromJsonAsync(); + + customer.Should() + .NotBeNull() + .And.Satisfy(x => x.Preferences.Should().Contain(FakeDataFactory.Preferences.First().Id)); + + + } + + [Fact] + public async Task Post_Should_Not_Add_Promocode_To_Customer() + { + var client = fixture.GetClient(true); + var response = await client.PostAsJsonAsync("api/v1/Promocodes", new GivePromoCodeRequest + { + PartnerName = FakeDataFactory.Employees.First().FullName, + ServiceInfo = "SvcInfo New", + Preference = "Дети", + PromoCode = "CODE_TEST" + }); + response.Should().Be200Ok(); + + var customerResponse = await client.GetAsync($"api/v1/Customers/{FakeDataFactory.Customers.First().Id}"); + customerResponse.Should().Be200Ok(); + + var customer = await customerResponse.Content.ReadFromJsonAsync(); + + customer.Should() + .NotBeNull() + .And.Satisfy(x => x.Preferences.Should().NotContain(FakeDataFactory.Preferences.Last().Id)); + + + } + + [Fact] + public async Task Post_Should_Add_Customer_Promocode() + { + var client = fixture.GetClient(true); + var response = await client.PostAsJsonAsync("api/v1/Promocodes", new GivePromoCodeRequest + { + PartnerName = FakeDataFactory.Employees.First().FullName, + ServiceInfo = "SvcInfo New", + Preference = "Театр", + PromoCode = "CODE_TEST" + }); + response.Should().Be200Ok(); + + var customerResponse = await client.GetAsync($"api/v1/Customers/{FakeDataFactory.Customers.First().Id}"); + customerResponse.Should().Be200Ok(); + + var customer = await customerResponse.Content.ReadFromJsonAsync(); + + customer.Should() + .NotBeNull() + .And.Satisfy(x => x.Preferences.Should().Contain(FakeDataFactory.Preferences.First().Id)); + + } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs index 6d8f68ca..e5f6b74f 100644 --- a/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs +++ b/Homeworks/EF/src/PromoCodesFactory.WebHost.Tests/WebHostFixture.cs @@ -20,16 +20,16 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureTestServices(services => { var descriptor = - services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextOptionsConfiguration)); - if (descriptor != null) + services.SingleOrDefault(d => d.ServiceType == typeof(IDbContextOptionsConfiguration)); + if (descriptor != null) services.Remove(descriptor); var id = Guid.NewGuid().ToString("D"); - - services.AddDbContext(options => + + services.AddDbContext(options => options.UseInMemoryDatabase(id) - .UseSeeding((c,_) => EfContext.SeedData(c)) - .UseAsyncSeeding((c,_, t) => + .UseSeeding((c, _) => EfContext.SeedData(c)) + .UseAsyncSeeding((c, _, t) => { EfContext.SeedData(c); return Task.CompletedTask; @@ -38,15 +38,14 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) base.ConfigureWebHost(builder); } - - + internal HttpClient GetClient(bool standalone = false) { using var scope = Services.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); context.Database.EnsureDeleted(); context.Database.EnsureCreated(); - + return CreateClient(); } From 61b0581cd3a44cb7a4c05c4ab1257bafac682169 Mon Sep 17 00:00:00 2001 From: Paroxizm Date: Fri, 3 Oct 2025 21:12:38 +0300 Subject: [PATCH 9/9] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20(=D0=BD=D0=B5=20=D0=B2=D0=BE=D1=88=D0=BB=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D0=B4=D1=8B=D0=B4=D1=83=D1=89=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20251002171936_Initial.Designer.cs | 264 +++++++++++++++++ .../Migrations/20251002171936_Initial.cs | 205 ++++++++++++++ ...20251002172008_CustomerAddress.Designer.cs | 268 ++++++++++++++++++ .../20251002172008_CustomerAddress.cs | 29 ++ .../Migrations/EfContextModelSnapshot.cs | 265 +++++++++++++++++ 5 files changed, 1031 insertions(+) create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.Designer.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.Designer.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.cs create mode 100644 Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/EfContextModelSnapshot.cs diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.Designer.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.Designer.cs new file mode 100644 index 00000000..1d71cacc --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.Designer.cs @@ -0,0 +1,264 @@ +// +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(EfContext))] + [Migration("20251002171936_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + 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("CustomerPromoCode", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PromoCodesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PromoCodesId"); + + b.HasIndex("PromoCodesId"); + + b.ToTable("CustomerPromoCode"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedPromocodesCount") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .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") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .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(50) + .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.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(150) + .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(20) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .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("CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany() + .HasForeignKey("PromoCodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany("Employees") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + 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.Administration.Role", b => + { + b.Navigation("Employees"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.cs new file mode 100644 index 00000000..fcc14c6d --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002171936_Initial.cs @@ -0,0 +1,205 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + /// + public partial class Initial : 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: 50, 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: 50, nullable: false), + Value = table.Column(type: "TEXT", maxLength: 150, 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: 50, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 150, nullable: false) + }, + 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: 50, nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false), + AppliedPromocodesCount = table.Column(type: "INTEGER", 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: 20, nullable: false), + ServiceInfo = table.Column(type: "TEXT", maxLength: 150, nullable: false), + BeginDate = table.Column(type: "TEXT", nullable: false), + EndDate = table.Column(type: "TEXT", nullable: false), + PartnerName = table.Column(type: "TEXT", maxLength: 150, 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 + { + CustomersId = table.Column(type: "TEXT", nullable: false), + PromoCodesId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomerPromoCode", x => new { x.CustomersId, x.PromoCodesId }); + table.ForeignKey( + name: "FK_CustomerPromoCode_Customer_CustomersId", + column: x => x.CustomersId, + principalTable: "Customer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_CustomerPromoCode_PromoCode_PromoCodesId", + column: x => x.PromoCodesId, + principalTable: "PromoCode", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPreference_PreferencesId", + table: "CustomerPreference", + column: "PreferencesId"); + + migrationBuilder.CreateIndex( + name: "IX_CustomerPromoCode_PromoCodesId", + table: "CustomerPromoCode", + column: "PromoCodesId"); + + 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/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.Designer.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.Designer.cs new file mode 100644 index 00000000..8548e91b --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.Designer.cs @@ -0,0 +1,268 @@ +// +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(EfContext))] + [Migration("20251002172008_CustomerAddress")] + partial class CustomerAddress + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + 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("CustomerPromoCode", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PromoCodesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PromoCodesId"); + + b.HasIndex("PromoCodesId"); + + b.ToTable("CustomerPromoCode"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedPromocodesCount") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .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") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .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.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(150) + .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(20) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .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("CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany() + .HasForeignKey("PromoCodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany("Employees") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + 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.Administration.Role", b => + { + b.Navigation("Employees"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.cs new file mode 100644 index 00000000..64bf4527 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/20251002172008_CustomerAddress.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace PromoCodeFactory.DataAccess.Migrations +{ + /// + public partial class CustomerAddress : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Address", + table: "Customer", + type: "TEXT", + maxLength: 512, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Address", + table: "Customer"); + } + } +} diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/EfContextModelSnapshot.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/EfContextModelSnapshot.cs new file mode 100644 index 00000000..b27d54fa --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Migrations/EfContextModelSnapshot.cs @@ -0,0 +1,265 @@ +// +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(EfContext))] + partial class EfContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + 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("CustomerPromoCode", b => + { + b.Property("CustomersId") + .HasColumnType("TEXT"); + + b.Property("PromoCodesId") + .HasColumnType("TEXT"); + + b.HasKey("CustomersId", "PromoCodesId"); + + b.HasIndex("PromoCodesId"); + + b.ToTable("CustomerPromoCode"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppliedPromocodesCount") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .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") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Role"); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Address") + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(50) + .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.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(150) + .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(20) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("PartnerManagerId") + .HasColumnType("TEXT"); + + b.Property("PartnerName") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.Property("PreferenceId") + .HasColumnType("TEXT"); + + b.Property("ServiceInfo") + .IsRequired() + .HasMaxLength(150) + .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("CustomerPromoCode", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("PromoCodeFactory.Core.Domain.PromoCodeManagement.PromoCode", null) + .WithMany() + .HasForeignKey("PromoCodesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PromoCodeFactory.Core.Domain.Administration.Employee", b => + { + b.HasOne("PromoCodeFactory.Core.Domain.Administration.Role", "Role") + .WithMany("Employees") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + 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.Administration.Role", b => + { + b.Navigation("Employees"); + }); +#pragma warning restore 612, 618 + } + } +}