diff --git a/.vs/ASP.NET.slnx/FileContentIndex/4a2fc05e-6d63-4b93-8c07-b74c784de692.vsidx b/.vs/ASP.NET.slnx/FileContentIndex/4a2fc05e-6d63-4b93-8c07-b74c784de692.vsidx new file mode 100644 index 000000000..53523937d Binary files /dev/null and b/.vs/ASP.NET.slnx/FileContentIndex/4a2fc05e-6d63-4b93-8c07-b74c784de692.vsidx differ diff --git a/.vs/ASP.NET.slnx/FileContentIndex/9cb8cf5c-c815-4302-8055-1335997cd1ec.vsidx b/.vs/ASP.NET.slnx/FileContentIndex/9cb8cf5c-c815-4302-8055-1335997cd1ec.vsidx new file mode 100644 index 000000000..1fba789c0 Binary files /dev/null and b/.vs/ASP.NET.slnx/FileContentIndex/9cb8cf5c-c815-4302-8055-1335997cd1ec.vsidx differ diff --git a/.vs/ASP.NET.slnx/v18/.wsuo b/.vs/ASP.NET.slnx/v18/.wsuo new file mode 100644 index 000000000..bbee44420 Binary files /dev/null and b/.vs/ASP.NET.slnx/v18/.wsuo differ diff --git a/.vs/ASP.NET.slnx/v18/DocumentLayout.backup.json b/.vs/ASP.NET.slnx/v18/DocumentLayout.backup.json new file mode 100644 index 000000000..4c616d731 --- /dev/null +++ b/.vs/ASP.NET.slnx/v18/DocumentLayout.backup.json @@ -0,0 +1,69 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Work_directory\\Git_repository\\ASP.NET\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 4\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 2. Publishing\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 1\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 4\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md", + "RelativeToolTip": "Lessons\\Module 4\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:36.011Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 2. Publishing\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md", + "RelativeToolTip": "Lessons\\Module 2. Publishing\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:29.053Z" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 1\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md", + "RelativeToolTip": "Lessons\\Module 1\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:22.877Z" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/ASP.NET.slnx/v18/DocumentLayout.json b/.vs/ASP.NET.slnx/v18/DocumentLayout.json new file mode 100644 index 000000000..4c616d731 --- /dev/null +++ b/.vs/ASP.NET.slnx/v18/DocumentLayout.json @@ -0,0 +1,69 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Work_directory\\Git_repository\\ASP.NET\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 4\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 2. Publishing\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:Lessons\\Module 1\\README.md||{EFC0BB08-EA7D-40C6-A696-C870411A895B}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 4\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 4\\README.md", + "RelativeToolTip": "Lessons\\Module 4\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:36.011Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 2. Publishing\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 2. Publishing\\README.md", + "RelativeToolTip": "Lessons\\Module 2. Publishing\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:29.053Z" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "README.md", + "DocumentMoniker": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md", + "RelativeDocumentMoniker": "Lessons\\Module 1\\README.md", + "ToolTip": "D:\\Work_directory\\Git_repository\\ASP.NET\\Lessons\\Module 1\\README.md", + "RelativeToolTip": "Lessons\\Module 1\\README.md", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001818|", + "WhenOpened": "2026-03-20T14:12:22.877Z" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.vs/ASP.NET/v18/workspaceFileList.bin b/.vs/ASP.NET/v18/workspaceFileList.bin new file mode 100644 index 000000000..04e0b5b13 Binary files /dev/null and b/.vs/ASP.NET/v18/workspaceFileList.bin differ diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 000000000..f8b488856 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 000000000..f78da413e --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,13 @@ +{ + "ExpandedNodes": [ + "", + "\\Homeworks", + "\\Homeworks\\Base", + "\\Homeworks\\Base\\src", + "\\Homeworks\\Base\\src\\PromoCodeFactory.Core", + "\\Homeworks\\Base\\src\\PromoCodeFactory.DataAccess", + "\\Homeworks\\Base\\src\\PromoCodeFactory.WebHost" + ], + "SelectedNode": "\\Homeworks\\Base\\src\\PromoCodeFactory.WebHost", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 000000000..1dd4e3fe9 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs b/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs index 3fe683e51..3e4b4926b 100644 --- a/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs @@ -9,6 +9,12 @@ public interface IRepository where T: BaseEntity { Task> GetAllAsync(); - Task GetByIdAsync(Guid id); + Task GetByIdAsync(Guid id); + + Task CreateAsync(T entity); + + Task UpdateAsync(T entity); + + Task DeleteAsync(Guid id); } } \ 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 0f1e04d7c..498e83768 100644 --- a/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ b/Homeworks/Base/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs @@ -20,9 +20,36 @@ 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 CreateAsync(T entity) + { + if (entity.Id == Guid.Empty) + entity.Id = Guid.NewGuid(); + + Data = Data.Append(entity); + return Task.FromResult(entity); + } + + public Task UpdateAsync(T entity) + { + var existingEntity = Data.FirstOrDefault(x => x.Id == entity.Id); + if (existingEntity == null) + return Task.FromResult(false); + Data = Data.Select(x => x.Id == entity.Id ? entity : x); + return Task.FromResult(true); + } + + public Task DeleteAsync(Guid id) + { + var existingEntity = Data.FirstOrDefault(x => x.Id == id); + if (existingEntity is null) + return Task.FromResult(false); + Data = Data.Where(x => x.Id != id); + return Task.FromResult(true); + } } } \ 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 df1c41dd3..6561cf4f0 100644 --- a/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs +++ b/Homeworks/Base/src/PromoCodeFactory.WebHost/Controllers/EmployeesController.cs @@ -1,11 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using PromoCodeFactory.Core.Abstractions.Repositories; using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.WebHost.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; namespace PromoCodeFactory.WebHost.Controllers { @@ -28,19 +29,28 @@ public EmployeesController(IRepository 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) + { + return Problem( + title: "Ошибка при получении списка сотрудников", + statusCode: StatusCodes.Status500InternalServerError); + } } /// @@ -50,25 +60,125 @@ 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, + Roles = employee.Roles.Select(x => new RoleItemResponse() + { + Name = x.Name, + Description = x.Description + }).ToList(), + FullName = employee.FullName, + AppliedPromocodesCount = employee.AppliedPromocodesCount + }; - var employeeModel = new EmployeeResponse() + return Ok(employeeModel); + } + catch (Exception) { - Id = employee.Id, - Email = employee.Email, - Roles = employee.Roles.Select(x => new RoleItemResponse() + return Problem( + title: "Ошибка при получении данных сотрудника", + statusCode: StatusCodes.Status500InternalServerError); + } + } + + /// + /// Создать сотрудника + /// + /// + /// + [HttpPost] + public async Task> CreateEmployeeAsync([FromBody] Employee employee) + { + try + { + var createdEmployee = await _employeeRepository.CreateAsync(employee); + + var employeeModel = new EmployeeResponse() { - Name = x.Name, - Description = x.Description - }).ToList(), - FullName = employee.FullName, - AppliedPromocodesCount = employee.AppliedPromocodesCount - }; - - return employeeModel; + Id = createdEmployee.Id, + FullName = createdEmployee.FullName, + Email = createdEmployee.Email, + Roles = createdEmployee.Roles.Select(x => new RoleItemResponse() + { + Id = x.Id, + Name = x.Name, + Description = x.Description + }).ToList(), + AppliedPromocodesCount = createdEmployee.AppliedPromocodesCount + }; + + return CreatedAtAction(nameof(GetEmployeeByIdAsync), new { id = createdEmployee.Id }, employeeModel); + } + catch(Exception) + { + return Problem( + title: "Ошибка при создании сотрудника", + statusCode: StatusCodes.Status500InternalServerError); + } } + + /// + /// Обновить данные сотрудника + /// + /// + /// + /// + [HttpPut("{id:guid}")] + public async Task UpdateEmployeeAsync(Guid id, [FromBody] Employee employee) + { + try + { + employee.Id = id; + + var updated = await _employeeRepository.UpdateAsync(employee); + + if (!updated) + return NotFound(); + + return NoContent(); + } + catch(Exception) + { + return Problem( + title: "Ошибка при обновлении сотрудника", + statusCode: StatusCodes.Status500InternalServerError); + } + } + + /// + /// Удалить сотрудника + /// + /// + /// + [HttpDelete("{id:guid}")] + public async Task DeleteEmployeeAsync(Guid id) + { + try + { + var deleted = await _employeeRepository.DeleteAsync(id); + + if (!deleted) + return NotFound(); + + return NoContent(); + } + catch (Exception) + { + return Problem( + title: "Ошибка при удалении сотрудника", + statusCode: StatusCodes.Status500InternalServerError); + } + } + + } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs index a1eb21fe2..c054ebf43 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Abstractions/Repositories/IRepository.cs @@ -10,6 +10,12 @@ public interface IRepository { Task> GetAllAsync(); - Task GetByIdAsync(Guid id); + Task GetByIdAsync(Guid id); + + Task CreateAsync(T entity); + + Task UpdateAsync(T entity); + + Task DeleteAsync(T entity); } } \ 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 28099d61d..d7a1791de 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/Administration/Employee.cs @@ -14,6 +14,8 @@ public class Employee public string Email { get; set; } + public Guid RoleId { get; set; } + public Role Role { get; set; } public int AppliedPromocodesCount { get; set; } diff --git a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs index 6f5c35931..d38d8e3cb 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Customer.cs @@ -14,6 +14,8 @@ public class Customer public string Email { get; set; } - //TODO: Списки Preferences и Promocodes + public ICollection CustomerPreferences { 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/CustomerPreference.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs new file mode 100644 index 000000000..f5fe8ca91 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/CustomerPreference.cs @@ -0,0 +1,15 @@ +using System; + +namespace PromoCodeFactory.Core.Domain.PromoCodeManagement +{ + public class CustomerPreference + { + public Guid CustomerId { get; set; } + + public Customer Customer { get; set; } + + public Guid PreferenceId { get; set; } + + public Preference Preference { 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 28e172a4f..8c57af96e 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/Preference.cs @@ -1,8 +1,15 @@ -namespace PromoCodeFactory.Core.Domain.PromoCodeManagement +using System.Collections.Generic; +using PromoCodeFactory.Core.Domain; + +namespace PromoCodeFactory.Core.Domain.PromoCodeManagement { public class Preference : BaseEntity { public string Name { get; set; } + + public ICollection CustomerPreferences { 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/PromoCode.cs b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs index 3305a41a2..b881b1f08 100644 --- a/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs +++ b/Homeworks/EF/src/PromoCodeFactory.Core/Domain/PromoCodeManagement/PromoCode.cs @@ -18,8 +18,16 @@ public class PromoCode public string PartnerName { get; set; } + public Guid PartnerManagerId { get; set; } + public Employee PartnerManager { get; set; } + public Guid PreferenceId { get; set; } + public Preference Preference { get; set; } + + public Guid? CustomerId { get; set; } + + public Customer Customer { get; set; } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/DbInitializer.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/DbInitializer.cs new file mode 100644 index 000000000..895fa948c --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/DbInitializer.cs @@ -0,0 +1,20 @@ +namespace PromoCodeFactory.DataAccess.Data +{ + public static class DbInitializer + { + public static void Initialize(PromoCodeFactoryContext context) + { + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + + context.Roles.AddRange(FakeDataFactory.Roles); + context.Preferences.AddRange(FakeDataFactory.Preferences); + context.Customers.AddRange(FakeDataFactory.Customers); + context.Employees.AddRange(FakeDataFactory.Employees); + context.CustomerPreferences.AddRange(FakeDataFactory.CustomerPreferences); + context.PromoCodes.AddRange(FakeDataFactory.PromoCodes); + + context.SaveChanges(); + } + } +} \ 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 aa665794d..64944f28c 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/FakeDataFactory.cs @@ -8,82 +8,129 @@ namespace PromoCodeFactory.DataAccess.Data { public static class FakeDataFactory { - public static IEnumerable Employees => new List() + public static readonly Guid AdminRoleId = Guid.Parse("53729686-a368-4eeb-8bfa-cc69b6050d02"); + public static readonly Guid PartnerManagerRoleId = Guid.Parse("b0ae7aac-5493-45cd-ad16-87426a5e7665"); + + public static readonly Guid AdminEmployeeId = Guid.Parse("451533d5-d8d5-4a11-9c7b-eb9f14e1a32f"); + public static readonly Guid PartnerManagerEmployeeId = Guid.Parse("f766e2bf-340a-46ea-bff3-f1700b435895"); + + public static readonly Guid TheatrePreferenceId = Guid.Parse("ef7f299f-92d7-459f-896e-078ed53ef99c"); + public static readonly Guid FamilyPreferenceId = Guid.Parse("c4bda62e-fc74-4256-a956-4760b3858cbd"); + public static readonly Guid ChildrenPreferenceId = Guid.Parse("76324c47-68d2-472d-abb8-33cfa8cc0c84"); + + public static readonly Guid FirstCustomerId = Guid.Parse("a6c8c6b1-4349-45b0-ab31-244740aaf0f0"); + public static readonly Guid SecondCustomerId = Guid.Parse("b2b72d40-9c7b-4c66-b8a3-1f6d0e3a1111"); + + public static readonly Guid FirstPromoCodeId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + + public static IEnumerable Roles => new List + { + new Role + { + Id = AdminRoleId, + Name = "Admin", + Description = "Администратор" + }, + new Role + { + Id = PartnerManagerRoleId, + Name = "PartnerManager", + Description = "Партнерский менеджер" + } + }; + + public static IEnumerable Employees => new List { - new Employee() + new Employee { - Id = Guid.Parse("451533d5-d8d5-4a11-9c7b-eb9f14e1a32f"), + Id = AdminEmployeeId, Email = "owner@somemail.ru", FirstName = "Иван", LastName = "Сергеев", - Role = Roles.FirstOrDefault(x => x.Name == "Admin"), + RoleId = AdminRoleId, AppliedPromocodesCount = 5 }, - new Employee() + new Employee { - Id = Guid.Parse("f766e2bf-340a-46ea-bff3-f1700b435895"), + Id = PartnerManagerEmployeeId, Email = "andreev@somemail.ru", FirstName = "Петр", LastName = "Андреев", - Role = Roles.FirstOrDefault(x => x.Name == "PartnerManager"), + RoleId = PartnerManagerRoleId, AppliedPromocodesCount = 10 + } + }; + + public static IEnumerable Preferences => new List + { + new Preference + { + Id = TheatrePreferenceId, + Name = "Театр" + }, + new Preference + { + Id = FamilyPreferenceId, + Name = "Семья" }, + new Preference + { + Id = ChildrenPreferenceId, + Name = "Дети" + } }; - public static IEnumerable Roles => new List() + public static IEnumerable Customers => new List { - new Role() + new Customer { - Id = Guid.Parse("53729686-a368-4eeb-8bfa-cc69b6050d02"), - Name = "Admin", - Description = "Администратор", + Id = FirstCustomerId, + Email = "ivan_sergeev@mail.ru", + FirstName = "Иван", + LastName = "Петров" }, - new Role() + new Customer { - Id = Guid.Parse("b0ae7aac-5493-45cd-ad16-87426a5e7665"), - Name = "PartnerManager", - Description = "Партнерский менеджер" + Id = SecondCustomerId, + Email = "maria@mail.ru", + FirstName = "Мария", + LastName = "Иванова" } }; - public static IEnumerable Preferences => new List() + public static IEnumerable CustomerPreferences => new List { - new Preference() + new CustomerPreference { - Id = Guid.Parse("ef7f299f-92d7-459f-896e-078ed53ef99c"), - Name = "Театр", + CustomerId = FirstCustomerId, + PreferenceId = TheatrePreferenceId }, - new Preference() + new CustomerPreference { - Id = Guid.Parse("c4bda62e-fc74-4256-a956-4760b3858cbd"), - Name = "Семья", + CustomerId = FirstCustomerId, + PreferenceId = FamilyPreferenceId }, - new Preference() + new CustomerPreference { - Id = Guid.Parse("76324c47-68d2-472d-abb8-33cfa8cc0c84"), - Name = "Дети", + CustomerId = SecondCustomerId, + PreferenceId = ChildrenPreferenceId } }; - public static IEnumerable Customers + public static IEnumerable PromoCodes => new List { - get + new PromoCode { - var customerId = Guid.Parse("a6c8c6b1-4349-45b0-ab31-244740aaf0f0"); - var customers = new List() - { - new Customer() - { - Id = customerId, - Email = "ivan_sergeev@mail.ru", - FirstName = "Иван", - LastName = "Петров", - //TODO: Добавить предзаполненный список предпочтений - } - }; - - return customers; + Id = FirstPromoCodeId, + Code = "THEATRE-10", + ServiceInfo = "Скидка 10% на билеты", + BeginDate = DateTime.UtcNow.Date, + EndDate = DateTime.UtcNow.Date.AddMonths(1), + PartnerName = "Big Theatre Partner", + PartnerManagerId = PartnerManagerEmployeeId, + PreferenceId = TheatrePreferenceId, + CustomerId = FirstCustomerId } - } + }; } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/PromoCodeFactoryContext.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/PromoCodeFactoryContext.cs new file mode 100644 index 000000000..fe9c48305 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Data/PromoCodeFactoryContext.cs @@ -0,0 +1,140 @@ +using System; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; + +namespace PromoCodeFactory.DataAccess.Data +{ + public class PromoCodeFactoryContext : DbContext + { + public PromoCodeFactoryContext(DbContextOptions options) + : base(options) + { + } + + 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 DbSet CustomerPreferences { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Name) + .HasMaxLength(100) + .IsRequired(); + + entity.Property(x => x.Description) + .HasMaxLength(300) + .IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Ignore(x => x.FullName); + + entity.Property(x => x.FirstName) + .HasMaxLength(100) + .IsRequired(); + + entity.Property(x => x.LastName) + .HasMaxLength(100) + .IsRequired(); + + entity.Property(x => x.Email) + .HasMaxLength(200) + .IsRequired(); + + entity.HasOne(x => x.Role) + .WithMany() + .HasForeignKey(x => x.RoleId) + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Name) + .HasMaxLength(100) + .IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Ignore(x => x.FullName); + + entity.Property(x => x.FirstName) + .HasMaxLength(100) + .IsRequired(); + + entity.Property(x => x.LastName) + .HasMaxLength(100) + .IsRequired(); + + entity.Property(x => x.Email) + .HasMaxLength(200) + .IsRequired(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => x.Id); + + entity.Property(x => x.Code) + .HasMaxLength(50) + .IsRequired(); + + entity.Property(x => x.ServiceInfo) + .HasMaxLength(300) + .IsRequired(); + + entity.Property(x => x.PartnerName) + .HasMaxLength(200) + .IsRequired(); + + entity.HasOne(x => x.Preference) + .WithMany(x => x.PromoCodes) + .HasForeignKey(x => x.PreferenceId) + .OnDelete(DeleteBehavior.Restrict); + + entity.HasOne(x => x.PartnerManager) + .WithMany() + .HasForeignKey(x => x.PartnerManagerId) + .OnDelete(DeleteBehavior.Restrict); + + entity.HasOne(x => x.Customer) + .WithMany(x => x.PromoCodes) + .HasForeignKey(x => x.CustomerId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(x => new { x.CustomerId, x.PreferenceId }); + + entity.HasOne(x => x.Customer) + .WithMany(x => x.CustomerPreferences) + .HasForeignKey(x => x.CustomerId); + + entity.HasOne(x => x.Preference) + .WithMany(x => x.CustomerPreferences) + .HasForeignKey(x => x.PreferenceId); + }); + } + } +} \ 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 4b290698a..b764cbf11 100644 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/PromoCodeFactory.DataAccess.csproj @@ -8,4 +8,13 @@ + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + 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 000000000..65be590c0 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/EFRepository.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.DataAccess.Data; + +namespace PromoCodeFactory.DataAccess.Repositories +{ + public class EfRepository : IRepository + where T : BaseEntity + { + + private readonly PromoCodeFactoryContext _context; + private readonly DbSet _dbSet; + + public EfRepository(PromoCodeFactoryContext context) + { + _context = context; + _dbSet = context.Set(); + } + + public async Task> GetAllAsync() + { + return await BuildQuery().ToListAsync(); + } + + public async Task GetByIdAsync(Guid id) + { + return await BuildQuery().FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task CreateAsync(T entity) + { + await _dbSet.AddAsync(entity); + await _context.SaveChangesAsync(); + + return entity; + } + + public async Task UpdateAsync(T entity) + { + _dbSet.Update(entity); + await _context.SaveChangesAsync(); + } + + public async Task DeleteAsync(T entity) + { + _dbSet.Remove(entity); + await _context.SaveChangesAsync(); + } + + private IQueryable BuildQuery() + { + if (typeof(T) == typeof(Employee)) + { + return (IQueryable)_context.Employees + .Include(x => x.Role); + } + + if (typeof(T) == typeof(Customer)) + { + return (IQueryable)_context.Customers + .Include(x => x.CustomerPreferences) + .ThenInclude(x => x.Preference) + .Include(x => x.PromoCodes); + } + + if (typeof(T) == typeof(PromoCode)) + { + return (IQueryable)_context.PromoCodes + .Include(x => x.Preference) + .Include(x => x.PartnerManager) + .Include(x => x.Customer); + } + + return _dbSet.AsQueryable(); + } + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs b/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs deleted file mode 100644 index dd099f5e6..000000000 --- a/Homeworks/EF/src/PromoCodeFactory.DataAccess/Repositories/InMemoryRepository.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using PromoCodeFactory.Core.Abstractions.Repositories; -using PromoCodeFactory.Core.Domain; - -namespace PromoCodeFactory.DataAccess.Repositories -{ - public class InMemoryRepository - : IRepository - where T : BaseEntity - { - protected IEnumerable Data { get; set; } - - public InMemoryRepository(IEnumerable data) - { - Data = data; - } - - public Task> GetAllAsync() - { - return Task.FromResult(Data); - } - - public Task GetByIdAsync(Guid id) - { - return Task.FromResult(Data.FirstOrDefault(x => x.Id == id)); - } - } -} \ 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 cf069a09f..de72f26e7 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/CustomersController.cs @@ -2,6 +2,10 @@ using PromoCodeFactory.WebHost.Models; using System; using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; namespace PromoCodeFactory.WebHost.Controllers { @@ -13,39 +17,175 @@ namespace PromoCodeFactory.WebHost.Controllers public class CustomersController : ControllerBase { + private readonly IRepository _customerRepository; + private readonly IRepository _preferenceRepository; + + public CustomersController( + IRepository customerRepository, + IRepository preferenceRepository) + { + _customerRepository = customerRepository; + _preferenceRepository = preferenceRepository; + } + + /// + /// Получить список всех клиентов + /// [HttpGet] - public Task> GetCustomersAsync() + public async Task> GetCustomersAsync() { - //TODO: Добавить получение списка клиентов - throw new NotImplementedException(); + var customers = await _customerRepository.GetAllAsync(); + + return customers.Select(x => new CustomerShortResponse + { + Id = x.Id, + FirstName = x.FirstName, + LastName = x.LastName, + Email = x.Email + }).ToList(); } - [HttpGet("{id}")] - public Task> GetCustomerAsync(Guid id) + /// + /// Получить клиента по Id + /// + [HttpGet("{id:guid}")] + public async Task> GetCustomerAsync(Guid id) { - //TODO: Добавить получение клиента вместе с выданными ему промомкодами - throw new NotImplementedException(); + var customer = await _customerRepository.GetByIdAsync(id); + + if (customer == null) + return NotFound(); + + return Ok(new CustomerResponse + { + Id = customer.Id, + FirstName = customer.FirstName, + LastName = customer.LastName, + Email = customer.Email, + Preferences = customer.CustomerPreferences + .Select(x => new PreferenceResponse + { + Id = x.Preference.Id, + Name = x.Preference.Name + }).ToList(), + PromoCodes = customer.PromoCodes + .Select(x => new PromoCodeShortResponse + { + Id = x.Id, + Code = x.Code, + ServiceInfo = x.ServiceInfo, + BeginDate = x.BeginDate.ToString("yyyy-MM-dd"), + EndDate = x.EndDate.ToString("yyyy-MM-dd"), + PartnerName = x.PartnerName + }).ToList() + }); } + /// + /// Создать клиента + /// [HttpPost] - public Task CreateCustomerAsync(CreateOrEditCustomerRequest request) + public async Task> CreateCustomerAsync([FromBody] CreateOrEditCustomerRequest request) { - //TODO: Добавить создание нового клиента вместе с его предпочтениями - throw new NotImplementedException(); + var preferences = await _preferenceRepository.GetAllAsync(); + var selectedPreferences = preferences + .Where(x => request.PreferenceIds.Contains(x.Id)) + .ToList(); + + var customerId = Guid.NewGuid(); + + var customer = new Customer + { + Id = customerId, + FirstName = request.FirstName, + LastName = request.LastName, + Email = request.Email, + CustomerPreferences = selectedPreferences + .Select(x => new CustomerPreference + { + CustomerId = customerId, + PreferenceId = x.Id + }).ToList() + }; + + var createdCustomer = await _customerRepository.CreateAsync(customer); + var customerFromDb = await _customerRepository.GetByIdAsync(createdCustomer.Id); + + return CreatedAtAction(nameof(GetCustomerAsync), new { id = createdCustomer.Id }, new CustomerResponse + { + Id = customerFromDb.Id, + FirstName = customerFromDb.FirstName, + LastName = customerFromDb.LastName, + Email = customerFromDb.Email, + Preferences = customerFromDb.CustomerPreferences + .Select(x => new PreferenceResponse + { + Id = x.Preference.Id, + Name = x.Preference.Name + }).ToList(), + PromoCodes = customerFromDb.PromoCodes + .Select(x => new PromoCodeShortResponse + { + Id = x.Id, + Code = x.Code, + ServiceInfo = x.ServiceInfo, + BeginDate = x.BeginDate.ToString("yyyy-MM-dd"), + EndDate = x.EndDate.ToString("yyyy-MM-dd"), + PartnerName = x.PartnerName + }).ToList() + }); } - [HttpPut("{id}")] - public Task EditCustomersAsync(Guid id, CreateOrEditCustomerRequest request) + /// + /// Обновить клиента + /// + [HttpPut("{id:guid}")] + public async Task EditCustomersAsync(Guid id, [FromBody] CreateOrEditCustomerRequest request) { - //TODO: Обновить данные клиента вместе с его предпочтениями - throw new NotImplementedException(); + var customer = await _customerRepository.GetByIdAsync(id); + + if (customer == null) + return NotFound(); + + var preferences = await _preferenceRepository.GetAllAsync(); + var selectedPreferences = preferences + .Where(x => request.PreferenceIds.Contains(x.Id)) + .ToList(); + + customer.FirstName = request.FirstName; + customer.LastName = request.LastName; + customer.Email = request.Email; + + customer.CustomerPreferences.Clear(); + + foreach (var preference in selectedPreferences) + { + customer.CustomerPreferences.Add(new CustomerPreference + { + CustomerId = customer.Id, + PreferenceId = preference.Id + }); + } + + await _customerRepository.UpdateAsync(customer); + + return NoContent(); } - [HttpDelete] - public Task DeleteCustomer(Guid id) + /// + /// Удалить клиента + /// + [HttpDelete("{id:guid}")] + public async Task DeleteCustomerAsync(Guid id) { - //TODO: Удаление клиента вместе с выданными ему промокодами - throw new NotImplementedException(); + var customer = await _customerRepository.GetByIdAsync(id); + + if (customer == null) + return NotFound(); + + await _customerRepository.DeleteAsync(customer); + + return NoContent(); } } } \ 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 000000000..be981cc56 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PreferencesController.cs @@ -0,0 +1,40 @@ +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 : ControllerBase + { + private readonly IRepository _preferenceRepository; + + public PreferencesController(IRepository preferenceRepository) + { + _preferenceRepository = preferenceRepository; + } + + /// + /// Получить список предпочтений + /// + [HttpGet] + public async Task> GetPreferencesAsync() + { + var preferences = await _preferenceRepository.GetAllAsync(); + + return preferences.Select(x => new PreferenceResponse + { + Id = x.Id, + Name = x.Name + }).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 5b96c327d..4b62b8232 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Controllers/PromocodesController.cs @@ -1,9 +1,15 @@ 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.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.WebHost.Models; + namespace PromoCodeFactory.WebHost.Controllers { /// @@ -14,15 +20,76 @@ namespace PromoCodeFactory.WebHost.Controllers public class PromocodesController : ControllerBase { + private readonly IRepository _promoCodeRepository; + private readonly IRepository _customerRepository; + private readonly IRepository _preferenceRepository; + private readonly IRepository _employeeRepository; + + public PromocodesController( + IRepository promoCodeRepository, + IRepository customerRepository, + IRepository preferenceRepository, + IRepository employeeRepository) + { + _promoCodeRepository = promoCodeRepository; + _customerRepository = customerRepository; + _preferenceRepository = preferenceRepository; + _employeeRepository = employeeRepository; + } + /// /// Получить все промокоды + /// beginDate и endDate передаются в формате yyyy-MM-dd строкой. /// /// [HttpGet] - public Task>> GetPromocodesAsync() + public async Task>> GetPromocodesAsync( + [FromQuery] string? beginDate, + [FromQuery] string? endDate) { - //TODO: Получить все промокоды - throw new NotImplementedException(); + var promoCodes = await _promoCodeRepository.GetAllAsync(); + + if (!string.IsNullOrWhiteSpace(beginDate)) + { + if (!DateTime.TryParseExact( + beginDate, + "yyyy-MM-dd", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var parsedBeginDate)) + { + return BadRequest("beginDate must be in format yyyy-MM-dd"); + } + + promoCodes = promoCodes.Where(x => x.BeginDate.Date >= parsedBeginDate.Date); + } + + if (!string.IsNullOrWhiteSpace(endDate)) + { + if (!DateTime.TryParseExact( + endDate, + "yyyy-MM-dd", + CultureInfo.InvariantCulture, + DateTimeStyles.None, + out var parsedEndDate)) + { + return BadRequest("endDate must be in format yyyy-MM-dd"); + } + + promoCodes = promoCodes.Where(x => x.EndDate.Date <= parsedEndDate.Date); + } + + var response = promoCodes.Select(x => new PromoCodeShortResponse + { + Id = x.Id, + Code = x.Code, + ServiceInfo = x.ServiceInfo, + BeginDate = x.BeginDate.ToString("yyyy-MM-dd"), + EndDate = x.EndDate.ToString("yyyy-MM-dd"), + PartnerName = x.PartnerName + }).ToList(); + + return Ok(response); } /// @@ -30,10 +97,47 @@ public Task>> GetPromocodesAsync() /// /// [HttpPost] - public Task GivePromoCodesToCustomersWithPreferenceAsync(GivePromoCodeRequest request) + public async Task GivePromoCodesToCustomersWithPreferenceAsync([FromBody] GivePromoCodeRequest request) { - //TODO: Создать промокод и выдать его клиентам с указанным предпочтением - throw new NotImplementedException(); + var preference = (await _preferenceRepository.GetAllAsync()) + .FirstOrDefault(x => x.Name == request.Preference); + + if (preference == null) + { + return BadRequest("Preference not found"); + } + + var partnerManager = (await _employeeRepository.GetAllAsync()) + .FirstOrDefault(x => x.Role.Name == "PartnerManager"); + + if (partnerManager == null) + { + return BadRequest("Partner manager not found"); + } + + var customers = (await _customerRepository.GetAllAsync()) + .Where(x => x.CustomerPreferences.Any(cp => cp.PreferenceId == preference.Id)) + .ToList(); + + foreach (var customer in customers) + { + var promoCode = new PromoCode + { + Id = Guid.NewGuid(), + Code = request.PromoCode, + ServiceInfo = request.ServiceInfo, + BeginDate = DateTime.UtcNow.Date, + EndDate = DateTime.UtcNow.Date.AddMonths(1), + PartnerName = request.PartnerName, + PartnerManagerId = partnerManager.Id, + PreferenceId = preference.Id, + CustomerId = customer.Id + }; + + await _promoCodeRepository.CreateAsync(promoCode); + } + + return NoContent(); } } } \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/GraphQL/CustomersQuery.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/GraphQL/CustomersQuery.cs new file mode 100644 index 000000000..74d17cc77 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/GraphQL/CustomersQuery.cs @@ -0,0 +1,65 @@ +using HotChocolate; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; + +namespace PromoCodeFactory.WebHost.GraphQL; + +public class CustomersQuery +{ + public async Task> GetCustomers( + [Service] IRepository customerRepository) + { + var customers = await customerRepository.GetAllAsync(); + + return customers.Select(customer => new CustomerShortResponse + { + Id = customer.Id, + FirstName = customer.FirstName, + LastName = customer.LastName, + Email = customer.Email + }).ToList(); + } + + public async Task GetCustomer( + Guid id, + [Service] IRepository customerRepository) + { + var customer = await customerRepository.GetByIdAsync(id); + + if (customer == null) + { + return null; + } + + return new CustomerResponse + { + Id = customer.Id, + FirstName = customer.FirstName, + LastName = customer.LastName, + Email = customer.Email, + Preferences = customer.CustomerPreferences + .Select(customerPreference => new PreferenceResponse + { + Id = customerPreference.Preference.Id, + Name = customerPreference.Preference.Name + }) + .ToList(), + PromoCodes = customer.PromoCodes + .Select(promoCode => new PromoCodeShortResponse + { + Id = promoCode.Id, + Code = promoCode.Code, + ServiceInfo = promoCode.ServiceInfo, + BeginDate = promoCode.BeginDate.ToString("yyyy-MM-dd"), + EndDate = promoCode.EndDate.ToString("yyyy-MM-dd"), + PartnerName = promoCode.PartnerName + }) + .ToList() + }; + } +} \ No newline at end of file diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/GrpcServices/CustomersGrpcService.cs b/Homeworks/EF/src/PromoCodeFactory.WebHost/GrpcServices/CustomersGrpcService.cs new file mode 100644 index 000000000..ca0914c82 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/GrpcServices/CustomersGrpcService.cs @@ -0,0 +1,83 @@ +using System.Linq; +using Grpc.Core; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Grpc; +using System; +using System.Threading.Tasks; + +namespace PromoCodeFactory.WebHost.GrpcServices; + +public class CustomersGrpcService : CustomersGrpc.CustomersGrpcBase +{ + private readonly IRepository _customerRepository; + + public CustomersGrpcService(IRepository customerRepository) + { + _customerRepository = customerRepository; + } + + public override async Task GetCustomers( + GetCustomersRequest request, + ServerCallContext context) + { + var customers = await _customerRepository.GetAllAsync(); + + var response = new GetCustomersResponse(); + + response.Customers.AddRange(customers.Select(customer => new CustomerShortGrpcResponse + { + Id = customer.Id.ToString(), + FirstName = customer.FirstName, + LastName = customer.LastName, + Email = customer.Email + })); + + return response; + } + + public override async Task GetCustomerById( + GetCustomerByIdRequest request, + ServerCallContext context) + { + if (!Guid.TryParse(request.Id, out var customerId)) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid customer id")); + } + + var customer = await _customerRepository.GetByIdAsync(customerId); + + if (customer == null) + { + throw new RpcException(new Status(StatusCode.NotFound, "Customer not found")); + } + + var response = new CustomerGrpcResponse + { + Id = customer.Id.ToString(), + FirstName = customer.FirstName, + LastName = customer.LastName, + Email = customer.Email + }; + + response.Preferences.AddRange(customer.CustomerPreferences.Select(customerPreference => + new PreferenceGrpcResponse + { + Id = customerPreference.Preference.Id.ToString(), + Name = customerPreference.Preference.Name + })); + + response.PromoCodes.AddRange(customer.PromoCodes.Select(promoCode => + new PromoCodeGrpcResponse + { + Id = promoCode.Id.ToString(), + Code = promoCode.Code, + ServiceInfo = promoCode.ServiceInfo, + BeginDate = promoCode.BeginDate.ToString("yyyy-MM-dd"), + EndDate = promoCode.EndDate.ToString("yyyy-MM-dd"), + PartnerName = promoCode.PartnerName + })); + + return response; + } +} \ 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 97ea3e6bc..cd2b8a578 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/CustomerResponse.cs @@ -9,7 +9,7 @@ public class CustomerResponse public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } - //TODO: Добавить список предпочтений + public List Preferences { get; set; } public List PromoCodes { 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 000000000..c2e7fc360 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Models/PreferenceResponse.cs @@ -0,0 +1,11 @@ +using System; + +namespace PromoCodeFactory.WebHost.Models +{ + public class PreferenceResponse + { + public Guid Id { get; set; } + + public string Name { 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 4bb08b6ef..6d32b65f3 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/PromoCodeFactory.WebHost.csproj @@ -12,7 +12,14 @@ + + + + + + + diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/Protos/customers.proto b/Homeworks/EF/src/PromoCodeFactory.WebHost/Protos/customers.proto new file mode 100644 index 000000000..c3d6dc5a4 --- /dev/null +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Protos/customers.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +option csharp_namespace = "PromoCodeFactory.WebHost.Grpc"; + +package customers; + +service CustomersGrpc { + rpc GetCustomers (GetCustomersRequest) returns (GetCustomersResponse); + rpc GetCustomerById (GetCustomerByIdRequest) returns (CustomerGrpcResponse); +} + +message GetCustomersRequest { +} + +message GetCustomerByIdRequest { + string id = 1; +} + +message GetCustomersResponse { + repeated CustomerShortGrpcResponse customers = 1; +} + +message CustomerShortGrpcResponse { + string id = 1; + string firstName = 2; + string lastName = 3; + string email = 4; +} + +message CustomerGrpcResponse { + string id = 1; + string firstName = 2; + string lastName = 3; + string email = 4; + repeated PreferenceGrpcResponse preferences = 5; + repeated PromoCodeGrpcResponse promoCodes = 6; +} + +message PreferenceGrpcResponse { + string id = 1; + string name = 2; +} + +message PromoCodeGrpcResponse { + string id = 1; + string code = 2; + string serviceInfo = 3; + string beginDate = 4; + string endDate = 5; + string partnerName = 6; +} \ 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 73a898fcd..6b24b8f5d 100644 --- a/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs +++ b/Homeworks/EF/src/PromoCodeFactory.WebHost/Startup.cs @@ -7,6 +7,9 @@ using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.DataAccess.Data; using PromoCodeFactory.DataAccess.Repositories; +using Microsoft.EntityFrameworkCore; +using PromoCodeFactory.WebHost.GrpcServices; +using PromoCodeFactory.WebHost.GraphQL; namespace PromoCodeFactory.WebHost { @@ -17,14 +20,19 @@ 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.AddDbContext(options => + options.UseSqlite("Data Source=promocodefactory.db")); + + services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); + + services.AddGrpc(); + + services.AddGrpcReflection(); + + services + .AddGraphQLServer() + .AddQueryType(); services.AddOpenApiDocument(options => { @@ -36,6 +44,12 @@ public void ConfigureServices(IServiceCollection services) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + using (var scope = app.ApplicationServices.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + DbInitializer.Initialize(dbContext); + } + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -58,6 +72,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseEndpoints(endpoints => { endpoints.MapControllers(); + endpoints.MapGrpcService(); + endpoints.MapGrpcReflectionService(); + endpoints.MapGraphQL(); }); } } diff --git a/Homeworks/EF/src/PromoCodeFactory.WebHost/promocodefactory.db b/Homeworks/EF/src/PromoCodeFactory.WebHost/promocodefactory.db new file mode 100644 index 000000000..d3c1787f9 Binary files /dev/null and b/Homeworks/EF/src/PromoCodeFactory.WebHost/promocodefactory.db differ