From 3f50c4152d8aa923b966ebf3c0bdbed643f332d9 Mon Sep 17 00:00:00 2001 From: Stefan Draganov Date: Sun, 10 Nov 2024 21:04:34 +0100 Subject: [PATCH 1/2] Refactored To N-Tier And Added IOptions Pattern --- ToDoList.sln | 51 ++++++- ToDoList/CurrentUser.cs | 7 +- ToDoList/Endpoints.cs | 4 +- ToDoList/Extensions/ServiceExtensions.cs | 109 ++++++++++++++ ToDoList/Program.cs | 68 +-------- ToDoList/Services/TodoService.cs | 94 ------------ ToDoList/Services/UserService.cs | 124 ---------------- ToDoList/TodoList.csproj | 8 +- ToDoList/appsettings.Development.json | 8 +- ToDoList/appsettings.json | 8 +- .../Contracts/ITodoService.cs | 6 +- .../Contracts/IUserService.cs | 6 +- .../Extensions/ServiceExtensions.cs | 31 ++++ .../Services/TodoService.cs | 101 +++++++++++++ .../Services/TokenService.cs | 18 ++- .../Services/UserService.cs | 137 ++++++++++++++++++ .../TodoList.BusinessLogic.csproj | 15 ++ .../Common/Contracts/ICurrentUser.cs | 13 ++ TodoList.Core/Common/IOptions/JwtSettings.cs | 10 ++ .../Constants}/Constants.cs | 4 +- .../Helpers}/MethodHelper.cs | 2 +- {ToDoList => TodoList.Core}/Models/Todo.cs | 2 +- {ToDoList => TodoList.Core}/Models/User.cs | 11 +- TodoList.Core/TodoList.Core.csproj | 9 ++ .../Todo/CreateTodoDTO.cs | 0 .../Todo/UpdateTodoDTO.cs | 0 TodoList.DTOs/TodoList.DTOs.csproj | 9 ++ .../User/ChangePasswordDTO.cs | 0 .../User/CreateUserDTO.cs | 0 .../DTOs => TodoList.DTOs}/User/LoginDTO.cs | 0 .../User/UpdateUserDTO.cs | 0 .../DTOs => TodoList.DTOs}/User/UserDTO.cs | 0 .../20240824142029_Init.Designer.cs | 2 +- .../Migrations/20240824142029_Init.cs | 0 .../TodoListDBContextModelSnapshot.cs | 2 +- .../Repositories/Contracts/ITodoRepository.cs | 22 +++ .../Repositories/Contracts/IUserRepository.cs | 22 +++ .../Repositories/TodoRepository.cs | 51 +++++++ .../Repositories/UserRepository.cs | 51 +++++++ .../ServiceExtensions/ServiceExtensions.cs | 44 ++++++ .../TodoList.DataAccess.csproj | 24 +++ .../TodoListDBContext.cs | 4 +- 42 files changed, 756 insertions(+), 321 deletions(-) create mode 100644 ToDoList/Extensions/ServiceExtensions.cs delete mode 100644 ToDoList/Services/TodoService.cs delete mode 100644 ToDoList/Services/UserService.cs rename {ToDoList/Services => TodoList.BusinessLogic}/Contracts/ITodoService.cs (66%) rename {ToDoList/Services => TodoList.BusinessLogic}/Contracts/IUserService.cs (80%) create mode 100644 TodoList.BusinessLogic/Extensions/ServiceExtensions.cs create mode 100644 TodoList.BusinessLogic/Services/TodoService.cs rename {ToDoList => TodoList.BusinessLogic}/Services/TokenService.cs (66%) create mode 100644 TodoList.BusinessLogic/Services/UserService.cs create mode 100644 TodoList.BusinessLogic/TodoList.BusinessLogic.csproj create mode 100644 TodoList.Core/Common/Contracts/ICurrentUser.cs create mode 100644 TodoList.Core/Common/IOptions/JwtSettings.cs rename {ToDoList => TodoList.Core/Constants}/Constants.cs (54%) rename {ToDoList => TodoList.Core/Helpers}/MethodHelper.cs (93%) rename {ToDoList => TodoList.Core}/Models/Todo.cs (94%) rename {ToDoList => TodoList.Core}/Models/User.cs (75%) create mode 100644 TodoList.Core/TodoList.Core.csproj rename {ToDoList/DTOs => TodoList.DTOs}/Todo/CreateTodoDTO.cs (100%) rename {ToDoList/DTOs => TodoList.DTOs}/Todo/UpdateTodoDTO.cs (100%) create mode 100644 TodoList.DTOs/TodoList.DTOs.csproj rename {ToDoList/DTOs => TodoList.DTOs}/User/ChangePasswordDTO.cs (100%) rename {ToDoList/DTOs => TodoList.DTOs}/User/CreateUserDTO.cs (100%) rename {ToDoList/DTOs => TodoList.DTOs}/User/LoginDTO.cs (100%) rename {ToDoList/DTOs => TodoList.DTOs}/User/UpdateUserDTO.cs (100%) rename {ToDoList/DTOs => TodoList.DTOs}/User/UserDTO.cs (100%) rename {ToDoList => TodoList.DataAccess}/Migrations/20240824142029_Init.Designer.cs (99%) rename {ToDoList => TodoList.DataAccess}/Migrations/20240824142029_Init.cs (100%) rename {ToDoList => TodoList.DataAccess}/Migrations/TodoListDBContextModelSnapshot.cs (99%) create mode 100644 TodoList.DataAccess/Repositories/Contracts/ITodoRepository.cs create mode 100644 TodoList.DataAccess/Repositories/Contracts/IUserRepository.cs create mode 100644 TodoList.DataAccess/Repositories/TodoRepository.cs create mode 100644 TodoList.DataAccess/Repositories/UserRepository.cs create mode 100644 TodoList.DataAccess/ServiceExtensions/ServiceExtensions.cs create mode 100644 TodoList.DataAccess/TodoList.DataAccess.csproj rename {ToDoList => TodoList.DataAccess}/TodoListDBContext.cs (86%) diff --git a/ToDoList.sln b/ToDoList.sln index 433cd46..79c0917 100644 --- a/ToDoList.sln +++ b/ToDoList.sln @@ -3,7 +3,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.9.34728.123 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList", "ToDoList\TodoList.csproj", "{D58E669D-A851-4711-BDC8-9F6402B6A8A3}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{7EFD5263-C6E2-40F1-874F-70269A72D0AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoList", "ToDoList\TodoList.csproj", "{22EA097C-F848-4FAE-9F5A-508584F88C7A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BLL", "BLL", "{D1C32702-34EF-4606-BBA4-524C0182DC2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DAL", "DAL", "{5E77DB42-17FE-4461-BD36-9440429871E6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.DataAccess", "TodoList.DataAccess\TodoList.DataAccess.csproj", "{738A90B6-1482-4E22-B6C8-FC41CE5EF7F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.BusinessLogic", "TodoList.BusinessLogic\TodoList.BusinessLogic.csproj", "{7F6D7D39-18FC-4E40-A184-371348773D54}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DTOs", "DTOs", "{06FCC980-F331-4ACF-8DB0-D34E6FBF33EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.DTOs", "TodoList.DTOs\TodoList.DTOs.csproj", "{8EFA0D3D-5568-49A8-B432-88389598A191}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{B065DE16-FDE9-4C1E-AA14-70EEECECD660}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoList.Core", "TodoList.Core\TodoList.Core.csproj", "{DC68F972-5FD5-4468-A5C0-00E50E30DB26}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,14 +29,37 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D58E669D-A851-4711-BDC8-9F6402B6A8A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D58E669D-A851-4711-BDC8-9F6402B6A8A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D58E669D-A851-4711-BDC8-9F6402B6A8A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D58E669D-A851-4711-BDC8-9F6402B6A8A3}.Release|Any CPU.Build.0 = Release|Any CPU + {22EA097C-F848-4FAE-9F5A-508584F88C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22EA097C-F848-4FAE-9F5A-508584F88C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22EA097C-F848-4FAE-9F5A-508584F88C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22EA097C-F848-4FAE-9F5A-508584F88C7A}.Release|Any CPU.Build.0 = Release|Any CPU + {738A90B6-1482-4E22-B6C8-FC41CE5EF7F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {738A90B6-1482-4E22-B6C8-FC41CE5EF7F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {738A90B6-1482-4E22-B6C8-FC41CE5EF7F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {738A90B6-1482-4E22-B6C8-FC41CE5EF7F1}.Release|Any CPU.Build.0 = Release|Any CPU + {7F6D7D39-18FC-4E40-A184-371348773D54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F6D7D39-18FC-4E40-A184-371348773D54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F6D7D39-18FC-4E40-A184-371348773D54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F6D7D39-18FC-4E40-A184-371348773D54}.Release|Any CPU.Build.0 = Release|Any CPU + {8EFA0D3D-5568-49A8-B432-88389598A191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8EFA0D3D-5568-49A8-B432-88389598A191}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8EFA0D3D-5568-49A8-B432-88389598A191}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8EFA0D3D-5568-49A8-B432-88389598A191}.Release|Any CPU.Build.0 = Release|Any CPU + {DC68F972-5FD5-4468-A5C0-00E50E30DB26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC68F972-5FD5-4468-A5C0-00E50E30DB26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC68F972-5FD5-4468-A5C0-00E50E30DB26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC68F972-5FD5-4468-A5C0-00E50E30DB26}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {22EA097C-F848-4FAE-9F5A-508584F88C7A} = {7EFD5263-C6E2-40F1-874F-70269A72D0AD} + {738A90B6-1482-4E22-B6C8-FC41CE5EF7F1} = {5E77DB42-17FE-4461-BD36-9440429871E6} + {7F6D7D39-18FC-4E40-A184-371348773D54} = {D1C32702-34EF-4606-BBA4-524C0182DC2A} + {8EFA0D3D-5568-49A8-B432-88389598A191} = {06FCC980-F331-4ACF-8DB0-D34E6FBF33EE} + {DC68F972-5FD5-4468-A5C0-00E50E30DB26} = {B065DE16-FDE9-4C1E-AA14-70EEECECD660} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1649D7CC-B602-4854-AF45-4A4E73B3E09D} EndGlobalSection diff --git a/ToDoList/CurrentUser.cs b/ToDoList/CurrentUser.cs index d6621b7..ae787c8 100644 --- a/ToDoList/CurrentUser.cs +++ b/ToDoList/CurrentUser.cs @@ -1,7 +1,10 @@ using System.Security.Claims; +using TodoList.Core.Common.Contracts; namespace TodoList; -public class CurrentUser(IHttpContextAccessor httpContextAccessor) +public class CurrentUser(IHttpContextAccessor httpContextAccessor) : ICurrentUser { - public string Id => httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor; + + public string Id => _httpContextAccessor.HttpContext?.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? string.Empty; } diff --git a/ToDoList/Endpoints.cs b/ToDoList/Endpoints.cs index 77bc15e..f991452 100644 --- a/ToDoList/Endpoints.cs +++ b/ToDoList/Endpoints.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using TodoList.BusinessLogic.Contracts; +using TodoList.Core.Constants; using TodoList.DTOs.Todo; using TodoList.DTOs.User; -using TodoList.Services.Contracts; -using ToDoList.Services.Contracts; namespace TodoList; diff --git a/ToDoList/Extensions/ServiceExtensions.cs b/ToDoList/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..66e03ec --- /dev/null +++ b/ToDoList/Extensions/ServiceExtensions.cs @@ -0,0 +1,109 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using System.Text; +using TodoList.BusinessLogic.Services; +using TodoList.Core.Common.Contracts; +using TodoList.Core.Common.IOptions; + +namespace TodoList.Extensions +{ + public static class ServiceExtensions + { + public static IServiceCollection AddPresentation(this IServiceCollection services, IConfiguration configuration) + { + services.ConfigureIOptions(configuration); + + services.ConfigureJwtAuthentication( + services.BuildServiceProvider().GetRequiredService>() + ); + + services.ConfigureSwaggerGen(); + + services.ConfigureDI(); + + return services; + } + + private static IServiceCollection ConfigureIOptions(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection("JwtSettings")); + + return services; + } + + private static IServiceCollection ConfigureJwtAuthentication(this IServiceCollection services, IOptions jwtSettings) + { + JwtSettings _jwtSettings = jwtSettings.Value; + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = _jwtSettings.Issuer, + ValidAudience = _jwtSettings.Audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey)) + }; + }); + + return services; + } + + private static IServiceCollection ConfigureSwaggerGen(this IServiceCollection services) + { + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Swagger", + Contact = new OpenApiContact + { + Name = "Behzad Dara", + Email = "Behzad.Dara.99@gmail.com", + Url = new Uri("https://www.linkedin.com/in/behzaddara/") + } + }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please insert JWT into field", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + }); + }); + + return services; + } + + private static IServiceCollection ConfigureDI(this IServiceCollection services) + { + services.AddSingleton(); + + return services; + } + } +} diff --git a/ToDoList/Program.cs b/ToDoList/Program.cs index f6be091..f850ea7 100644 --- a/ToDoList/Program.cs +++ b/ToDoList/Program.cs @@ -4,78 +4,24 @@ using Microsoft.OpenApi.Models; using System.Text; using TodoList; -using TodoList.Services; -using TodoList.Services.Contracts; -using ToDoList.Services.Contracts; +using TodoList.BusinessLogic.Extensions; +using TodoList.Core.Common.Contracts; +using TodoList.DataAccess.ServiceExtensions; +using TodoList.Extensions; var builder = WebApplication.CreateBuilder(args); -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidateAudience = true, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - ValidIssuer = "test-issuer", - ValidAudience = "test-audience", - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Constants.TOKEN_KEY)) - }; - }); +builder.Services.AddPresentation(builder.Configuration); builder.Services.AddAuthorization(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(c => -{ - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Swagger", - Contact = new OpenApiContact - { - Name = "Behzad Dara", - Email = "Behzad.Dara.99@gmail.com", - Url = new Uri("https://www.linkedin.com/in/behzaddara/") - } - }); - - c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Please insert JWT into field", - Name = "Authorization", - Type = SecuritySchemeType.Http, - BearerFormat = "JWT", - Scheme = "bearer" - }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } - }); -}); +builder.Services.AddBusiness(); -var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); -builder.Services.AddDbContext(options => options.UseSqlServer(connectionString)); +builder.Services.AddDataAccess(builder.Configuration); builder.Services.AddHttpContextAccessor(); -builder.Services.AddSingleton(typeof(CurrentUser)); - -builder.Services.AddScoped(typeof(TokenService)); -builder.Services.AddScoped(); -builder.Services.AddScoped(); var app = builder.Build(); diff --git a/ToDoList/Services/TodoService.cs b/ToDoList/Services/TodoService.cs deleted file mode 100644 index d86dc82..0000000 --- a/ToDoList/Services/TodoService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using TodoList.DTOs.Todo; -using TodoList.Models; -using TodoList.Services.Contracts; - -namespace TodoList.Services; - -public class TodoService( - TodoListDBContext context, - CurrentUser currentUser) : ITodoService -{ - public async Task Create(CreateTodoDTO input) - { - var todo = Todo.Create(int.Parse(currentUser.Id), input.Title, input.Description); - - await context.Todos.AddAsync(todo); - await context.SaveChangesAsync(); - - return todo; - } - - public async Task Check(int id) - { - var todo = await context.Todos.FirstOrDefaultAsync(x => - x.Id == id && - x.UserId == int.Parse(currentUser.Id)); - - if (todo is null) - { - return null; - } - - todo.IsDone = true; - await context.SaveChangesAsync(); - - return todo; - } - - public async Task Get(int id) - { - var todo = await context.Todos.FirstOrDefaultAsync(x => - x.Id == id && - x.UserId == int.Parse(currentUser.Id)); - - if (todo is null) - { - return null; - } - - return todo; - } - - public async Task> Get() - { - var todos = await context.Todos.Where(x => - x.UserId == int.Parse(currentUser.Id)) - .ToListAsync(); - - return todos; - } - - public async Task Update(UpdateTodoDTO input) - { - var todo = await context.Todos.FirstOrDefaultAsync(x => - x.Id == input.Id); - - if (todo is null) - { - return null; - } - - todo.Description = input.Description; - todo.Id = input.Id; - todo.Title = input.Title; - todo.IsDone = input.IsDone; - await context.SaveChangesAsync(); - return todo; - } - - public async Task Delete(int id) - { - var todo = await context.Todos.FirstOrDefaultAsync(x => - x.Id == id); - - if (todo is null) - { - return false; - } - - context.Todos.Remove(todo); - await context.SaveChangesAsync(); - return true; - } -} diff --git a/ToDoList/Services/UserService.cs b/ToDoList/Services/UserService.cs deleted file mode 100644 index 8071d76..0000000 --- a/ToDoList/Services/UserService.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using TodoList.DTOs.User; -using TodoList.Migrations; -using TodoList.Models; -using ToDoList.Services.Contracts; - -namespace TodoList.Services; - -public class UserService( - TodoListDBContext context, - TokenService tokenService, - CurrentUser currentUser) : IUserService -{ - public async Task Login(LoginDTO input) - { - var user = await context.Users.FirstOrDefaultAsync(x => - x.Username == input.Username && - x.HashedPassword == MethodHelper.ComputeSHA512Hash(input.Password)); - - if (user is null) - { - return null; - } - - var token = tokenService.Generate(user); - return token; - } - - public async Task Refresh() - { - var user = await context.Users.FirstAsync(x => - x.Id == int.Parse(currentUser.Id)); - - var token = tokenService.Generate(user); - return token; - } - - public async Task ChangePassword(ChangePasswordDTO input) - { - var user = await context.Users.FirstOrDefaultAsync(x => - x.Id == int.Parse(currentUser.Id) && - x.HashedPassword == MethodHelper.ComputeSHA512Hash(input.CurrentPassword) - ); - - if (user is null) - { - return false; - } - - user.HashedPassword = MethodHelper.ComputeSHA512Hash(input.NewPassword); - await context.SaveChangesAsync(); - - return true; - } - - public async Task Create(CreateUserDTO input) - { - var exists = await context.Users.AnyAsync(x => - x.Username == input.Username); - - if (exists) - { - return null; - } - - var user = User.CreateCustomer(input.Username); - - await context.Users.AddAsync(user); - await context.SaveChangesAsync(); - - return user; - } - - public async Task Update(UpdateUserDTO input) - { - var user = await context.Users.FirstOrDefaultAsync(x => - x.Id == input.Id); - - if (user is null) - { - return null; - } - - var exists = await context.Users.AnyAsync(x => - x.Username == input.Username); - - if (exists) - { - return null; - } - - user.Username = input.Username; - user.Role = input.Role; - await context.SaveChangesAsync(); - - return user; - } - - public async Task Delete(int id) - { - var user = await context.Users.FirstOrDefaultAsync(x => - x.Id == id); - - if (user is null) - { - return false; - } - - context.Users.Remove(user); - await context.SaveChangesAsync(); - return true; - } - - public async Task> GetAll() - { - return await context.Users.Select(x => new UserDTO() - { - Username = x.Username, - Id = x.Id, - Role = x.Role - }).ToListAsync(); - - } -} diff --git a/ToDoList/TodoList.csproj b/ToDoList/TodoList.csproj index e1e0100..ba49674 100644 --- a/ToDoList/TodoList.csproj +++ b/ToDoList/TodoList.csproj @@ -9,11 +9,11 @@ - - - - + + + + diff --git a/ToDoList/appsettings.Development.json b/ToDoList/appsettings.Development.json index 4d9ac9f..9e7d2e6 100644 --- a/ToDoList/appsettings.Development.json +++ b/ToDoList/appsettings.Development.json @@ -1,11 +1,17 @@ { "ConnectionStrings": { - "DefaultConnection": "Data Source=BEHZAD-DARA\\LOCALDB;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;", + "DefaultConnection": "Data Source=BEHZAD-DARA\\LOCALDB;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" + }, + "JwtSettings": { + "Issuer": "test-issuer", + "Audience": "test-audience", + "SecretKey": "BEHZAD_DARA_@!_KEY_!@_BEHZAD_DARA", + "ExpirationInMinutes": "15" } } } diff --git a/ToDoList/appsettings.json b/ToDoList/appsettings.json index b40bb67..11aeae9 100644 --- a/ToDoList/appsettings.json +++ b/ToDoList/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Data Source=BEHZAD-DARA\\LOCALDB;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;" + "DefaultConnection": "Data Source=localhost;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;" }, "Logging": { "LogLevel": { @@ -8,5 +8,11 @@ "Microsoft.AspNetCore": "Warning" } }, + "JwtSettings": { + "Issuer": "test-issuer", + "Audience": "test-audience", + "SecretKey": "BEHZAD_DARA_@!_KEY_!@_BEHZAD_DARA", + "ExpirationInMinutes": "15" + }, "AllowedHosts": "*" } diff --git a/ToDoList/Services/Contracts/ITodoService.cs b/TodoList.BusinessLogic/Contracts/ITodoService.cs similarity index 66% rename from ToDoList/Services/Contracts/ITodoService.cs rename to TodoList.BusinessLogic/Contracts/ITodoService.cs index 8044eb5..0b53b69 100644 --- a/ToDoList/Services/Contracts/ITodoService.cs +++ b/TodoList.BusinessLogic/Contracts/ITodoService.cs @@ -1,7 +1,7 @@ using TodoList.DTOs.Todo; -using TodoList.Models; +using TodoList.Core.Models; -namespace TodoList.Services.Contracts; +namespace TodoList.BusinessLogic.Contracts; public interface ITodoService { @@ -9,7 +9,7 @@ public interface ITodoService Task Check(int id); Task Get(int id); Task> Get(); - Task Update(UpdateTodoDTO input); + Task Update(UpdateTodoDTO input); Task Delete(int id); } diff --git a/ToDoList/Services/Contracts/IUserService.cs b/TodoList.BusinessLogic/Contracts/IUserService.cs similarity index 80% rename from ToDoList/Services/Contracts/IUserService.cs rename to TodoList.BusinessLogic/Contracts/IUserService.cs index f7216ee..3c0aab9 100644 --- a/ToDoList/Services/Contracts/IUserService.cs +++ b/TodoList.BusinessLogic/Contracts/IUserService.cs @@ -3,9 +3,9 @@ using System.Linq; using System.Threading.Tasks; using TodoList.DTOs.User; -using TodoList.Models; +using TodoList.Core.Models; -namespace ToDoList.Services.Contracts +namespace TodoList.BusinessLogic.Contracts { public interface IUserService { @@ -15,6 +15,6 @@ public interface IUserService Task Create(CreateUserDTO input); Task Update(UpdateUserDTO input); Task Delete(int id); - Task> GetAll(); + Task> GetAll(); } } \ No newline at end of file diff --git a/TodoList.BusinessLogic/Extensions/ServiceExtensions.cs b/TodoList.BusinessLogic/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..4f09ea7 --- /dev/null +++ b/TodoList.BusinessLogic/Extensions/ServiceExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TodoList.BusinessLogic.Contracts; +using TodoList.BusinessLogic.Services; + +namespace TodoList.BusinessLogic.Extensions +{ + public static class ServiceExtensions + { + public static IServiceCollection AddBusiness(this IServiceCollection services) + { + services.ConfigureDI(); + + return services; + } + + private static IServiceCollection ConfigureDI(this IServiceCollection services) + { + services + .AddScoped(typeof(TokenService)) + .AddScoped() + .AddScoped(); + + return services; + } + } +} diff --git a/TodoList.BusinessLogic/Services/TodoService.cs b/TodoList.BusinessLogic/Services/TodoService.cs new file mode 100644 index 0000000..21343ee --- /dev/null +++ b/TodoList.BusinessLogic/Services/TodoService.cs @@ -0,0 +1,101 @@ +using Microsoft.EntityFrameworkCore; +using TodoList.DTOs.Todo; +using TodoList.BusinessLogic.Contracts; +using TodoList.Core.Models; +using TodoList.DataAccess.Repositories.Contracts; +using TodoList.Core.Common.Contracts; + +namespace TodoList.BusinessLogic.Services; + +public class TodoService( + ITodoRepository todoRepository, + ICurrentUser currentUser) : ITodoService +{ + private readonly ITodoRepository _todoRepository = todoRepository; + private readonly ICurrentUser _currentUser = currentUser; + + public async Task Create(CreateTodoDTO input) + { + var todo = Todo.Create(int.Parse(_currentUser.Id), input.Title, input.Description); + + await _todoRepository.Create(todo); + await _todoRepository.SaveChangesAsync(); + + return todo; + } + + public async Task Check(int id) + { + Todo? todo = await _todoRepository.GetFirstOrDefault(x => + x.Id == id && + x.UserId == int.Parse(_currentUser.Id) + ); + + if (todo is null) + { + return null; + } + + todo.IsDone = true; + + _todoRepository.Update(todo); + await _todoRepository.SaveChangesAsync(); + + return todo; + } + + public async Task Get(int id) + { + Todo? todo = await _todoRepository.GetFirstOrDefault(x => + x.Id == id && + x.UserId == int.Parse(_currentUser.Id)); + + if (todo is null) + { + return null; + } + + return todo; + } + + public async Task> Get() + { + List todos = await _todoRepository.GetAll(x => x.UserId == int.Parse(_currentUser.Id)); + + return todos; + } + + public async Task Update(UpdateTodoDTO input) + { + var todo = await _todoRepository.GetById(input.Id); + + if (todo is null) + { + return null; + } + + todo.Description = input.Description; + todo.Id = input.Id; + todo.Title = input.Title; + todo.IsDone = input.IsDone; + + await _todoRepository.SaveChangesAsync(); + + return todo; + } + + public async Task Delete(int id) + { + var todo = await _todoRepository.GetById(id); + + if (todo is null) + { + return false; + } + + _todoRepository.Delete(todo); + await _todoRepository.SaveChangesAsync(); + + return true; + } +} diff --git a/ToDoList/Services/TokenService.cs b/TodoList.BusinessLogic/Services/TokenService.cs similarity index 66% rename from ToDoList/Services/TokenService.cs rename to TodoList.BusinessLogic/Services/TokenService.cs index ca590cb..4988492 100644 --- a/ToDoList/Services/TokenService.cs +++ b/TodoList.BusinessLogic/Services/TokenService.cs @@ -1,14 +1,18 @@ using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; -using TodoList.Models; +using TodoList.Core.Common.IOptions; +using TodoList.Core.Models; -namespace TodoList.Services; +namespace TodoList.BusinessLogic.Services; -public class TokenService +public class TokenService(IOptions jwtSettings) { + private JwtSettings _jwtSettings = jwtSettings.Value; + public string Generate(User user) { var claims = new List @@ -18,16 +22,16 @@ public string Generate(User user) }; var tokenHandler = new JwtSecurityTokenHandler(); - var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Constants.TOKEN_KEY)); + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var now = DateTime.Now; var tokenDescriptor = new JwtSecurityToken( - "test-issuer", - "test-audience", + _jwtSettings.Issuer, + _jwtSettings.Audience, claims, now, - now.AddMinutes(Constants.TOKEN_EXPIRATION_IN_MINUTES), + now.AddMinutes(_jwtSettings.ExpirationInMinutes), credentials); var token = tokenHandler.WriteToken(tokenDescriptor); diff --git a/TodoList.BusinessLogic/Services/UserService.cs b/TodoList.BusinessLogic/Services/UserService.cs new file mode 100644 index 0000000..b81ca2b --- /dev/null +++ b/TodoList.BusinessLogic/Services/UserService.cs @@ -0,0 +1,137 @@ +using Microsoft.EntityFrameworkCore; +using TodoList.BusinessLogic.Contracts; +using TodoList.Core.Common.Contracts; +using TodoList.Core.Helpers; +using TodoList.Core.Models; +using TodoList.DataAccess.Repositories.Contracts; +using TodoList.DTOs.User; +using TodoList.Migrations; + +namespace TodoList.BusinessLogic.Services; + +//TODO: Tightly coupled to TokenService +public class UserService( + IUserRepository userRepository, + TokenService tokenService, + ICurrentUser currentUser) : IUserService +{ + private readonly IUserRepository _userRepository = userRepository; + private readonly ICurrentUser _currentUser = currentUser; + + public async Task Login(LoginDTO input) + { + var user = await _userRepository.GetFirstOrDefault(x => + x.Username == input.Username && + x.HashedPassword == MethodHelper.ComputeSHA512Hash(input.Password) + ); + + if (user is null) + { + return null; + } + + var token = tokenService.Generate(user); + return token; + } + + public async Task Refresh() + { + var user = await _userRepository.GetById(int.Parse(_currentUser.Id)); + + var token = tokenService.Generate(user!); + return token; + } + + public async Task ChangePassword(ChangePasswordDTO input) + { + var user = await _userRepository.GetFirstOrDefault(x => + x.Id == int.Parse(_currentUser.Id) && + x.HashedPassword == MethodHelper.ComputeSHA512Hash(input.CurrentPassword) + ); + + if (user is null) + { + return false; + } + + user.HashedPassword = MethodHelper.ComputeSHA512Hash(input.NewPassword); + + _userRepository.Update(user); + await _userRepository.SaveChangesAsync(); + + return true; + } + + public async Task Create(CreateUserDTO input) + { + User? userExists = await _userRepository.GetFirstOrDefault(x => + x.Username == input.Username + ); + + if (userExists is not null) + { + return null; + } + + var user = User.CreateCustomer(input.Username); + + await _userRepository.Create(user); + await _userRepository.SaveChangesAsync(); + + return user; + } + + public async Task Update(UpdateUserDTO input) + { + User? user = await _userRepository.GetById(input.Id); + + if (user is null) + { + return null; + } + + User? userExists = await _userRepository.GetFirstOrDefault(x => + x.Username == input.Username + ); + + if (userExists is not null) + { + return null; + } + + user.Username = input.Username; + user.Role = input.Role; + + _userRepository.Update(user); + await _userRepository.SaveChangesAsync(); + + return user; + } + + public async Task Delete(int id) + { + User? user = await _userRepository.GetById(id); + + if (user is null) + { + return false; + } + + _userRepository.Delete(user); + await _userRepository.SaveChangesAsync(); + return true; + } + + public async Task> GetAll() + { + List users = await _userRepository.GetAll(); + + return users.Select(x => new UserDTO() + { + Username = x.Username, + Id = x.Id, + Role = x.Role + }).ToList(); + + } +} diff --git a/TodoList.BusinessLogic/TodoList.BusinessLogic.csproj b/TodoList.BusinessLogic/TodoList.BusinessLogic.csproj new file mode 100644 index 0000000..ed59426 --- /dev/null +++ b/TodoList.BusinessLogic/TodoList.BusinessLogic.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/TodoList.Core/Common/Contracts/ICurrentUser.cs b/TodoList.Core/Common/Contracts/ICurrentUser.cs new file mode 100644 index 0000000..1000d44 --- /dev/null +++ b/TodoList.Core/Common/Contracts/ICurrentUser.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TodoList.Core.Common.Contracts +{ + public interface ICurrentUser + { + string Id { get; } + } +} diff --git a/TodoList.Core/Common/IOptions/JwtSettings.cs b/TodoList.Core/Common/IOptions/JwtSettings.cs new file mode 100644 index 0000000..302e743 --- /dev/null +++ b/TodoList.Core/Common/IOptions/JwtSettings.cs @@ -0,0 +1,10 @@ +namespace TodoList.Core.Common.IOptions +{ + public class JwtSettings + { + public required string Issuer { get; set; } + public required string Audience { get; set; } + public required string SecretKey { get; set; } + public required int ExpirationInMinutes { get; set; } + } +} diff --git a/ToDoList/Constants.cs b/TodoList.Core/Constants/Constants.cs similarity index 54% rename from ToDoList/Constants.cs rename to TodoList.Core/Constants/Constants.cs index 5836e55..6af8eae 100644 --- a/ToDoList/Constants.cs +++ b/TodoList.Core/Constants/Constants.cs @@ -1,10 +1,8 @@ -namespace TodoList; +namespace TodoList.Core.Constants; public static class Constants { public const string PASSWORD_DEFAULT = "123"; public const string ROLE_ADMIN = "Admin"; public const string ROLE_CUSTOMER = "Customer"; - public const string TOKEN_KEY = "BEHZAD_DARA_@!_KEY_!@_BEHZAD_DARA"; - public const int TOKEN_EXPIRATION_IN_MINUTES = 15; } diff --git a/ToDoList/MethodHelper.cs b/TodoList.Core/Helpers/MethodHelper.cs similarity index 93% rename from ToDoList/MethodHelper.cs rename to TodoList.Core/Helpers/MethodHelper.cs index 890a828..1209a53 100644 --- a/ToDoList/MethodHelper.cs +++ b/TodoList.Core/Helpers/MethodHelper.cs @@ -1,7 +1,7 @@ using System.Security.Cryptography; using System.Text; -namespace TodoList; +namespace TodoList.Core.Helpers; public static class MethodHelper { diff --git a/ToDoList/Models/Todo.cs b/TodoList.Core/Models/Todo.cs similarity index 94% rename from ToDoList/Models/Todo.cs rename to TodoList.Core/Models/Todo.cs index 1dfb376..0fb8a96 100644 --- a/ToDoList/Models/Todo.cs +++ b/TodoList.Core/Models/Todo.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace TodoList.Models; +namespace TodoList.Core.Models; public class Todo { diff --git a/ToDoList/Models/User.cs b/TodoList.Core/Models/User.cs similarity index 75% rename from ToDoList/Models/User.cs rename to TodoList.Core/Models/User.cs index 1c75901..2aba33e 100644 --- a/ToDoList/Models/User.cs +++ b/TodoList.Core/Models/User.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; +using TodoList.Core.Helpers; -namespace TodoList.Models; +namespace TodoList.Core.Models; public class User { @@ -15,8 +16,8 @@ public static User CreateCustomer(string username) return new User { Username = username, - HashedPassword = MethodHelper.ComputeSHA512Hash(Constants.PASSWORD_DEFAULT), - Role = Constants.ROLE_CUSTOMER + HashedPassword = MethodHelper.ComputeSHA512Hash(Constants.Constants.PASSWORD_DEFAULT), + Role = Constants.Constants.ROLE_CUSTOMER }; } @@ -26,8 +27,8 @@ public static User CreateAdmin() { Id = 1, Username = "BehzadDara", - HashedPassword = MethodHelper.ComputeSHA512Hash(Constants.PASSWORD_DEFAULT), - Role = Constants.ROLE_ADMIN + HashedPassword = MethodHelper.ComputeSHA512Hash(Constants.Constants.PASSWORD_DEFAULT), + Role = Constants.Constants.ROLE_ADMIN }; } } diff --git a/TodoList.Core/TodoList.Core.csproj b/TodoList.Core/TodoList.Core.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/TodoList.Core/TodoList.Core.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/ToDoList/DTOs/Todo/CreateTodoDTO.cs b/TodoList.DTOs/Todo/CreateTodoDTO.cs similarity index 100% rename from ToDoList/DTOs/Todo/CreateTodoDTO.cs rename to TodoList.DTOs/Todo/CreateTodoDTO.cs diff --git a/ToDoList/DTOs/Todo/UpdateTodoDTO.cs b/TodoList.DTOs/Todo/UpdateTodoDTO.cs similarity index 100% rename from ToDoList/DTOs/Todo/UpdateTodoDTO.cs rename to TodoList.DTOs/Todo/UpdateTodoDTO.cs diff --git a/TodoList.DTOs/TodoList.DTOs.csproj b/TodoList.DTOs/TodoList.DTOs.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/TodoList.DTOs/TodoList.DTOs.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/ToDoList/DTOs/User/ChangePasswordDTO.cs b/TodoList.DTOs/User/ChangePasswordDTO.cs similarity index 100% rename from ToDoList/DTOs/User/ChangePasswordDTO.cs rename to TodoList.DTOs/User/ChangePasswordDTO.cs diff --git a/ToDoList/DTOs/User/CreateUserDTO.cs b/TodoList.DTOs/User/CreateUserDTO.cs similarity index 100% rename from ToDoList/DTOs/User/CreateUserDTO.cs rename to TodoList.DTOs/User/CreateUserDTO.cs diff --git a/ToDoList/DTOs/User/LoginDTO.cs b/TodoList.DTOs/User/LoginDTO.cs similarity index 100% rename from ToDoList/DTOs/User/LoginDTO.cs rename to TodoList.DTOs/User/LoginDTO.cs diff --git a/ToDoList/DTOs/User/UpdateUserDTO.cs b/TodoList.DTOs/User/UpdateUserDTO.cs similarity index 100% rename from ToDoList/DTOs/User/UpdateUserDTO.cs rename to TodoList.DTOs/User/UpdateUserDTO.cs diff --git a/ToDoList/DTOs/User/UserDTO.cs b/TodoList.DTOs/User/UserDTO.cs similarity index 100% rename from ToDoList/DTOs/User/UserDTO.cs rename to TodoList.DTOs/User/UserDTO.cs diff --git a/ToDoList/Migrations/20240824142029_Init.Designer.cs b/TodoList.DataAccess/Migrations/20240824142029_Init.Designer.cs similarity index 99% rename from ToDoList/Migrations/20240824142029_Init.Designer.cs rename to TodoList.DataAccess/Migrations/20240824142029_Init.Designer.cs index 2981e0f..ff2f9bd 100644 --- a/ToDoList/Migrations/20240824142029_Init.Designer.cs +++ b/TodoList.DataAccess/Migrations/20240824142029_Init.Designer.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using TodoList; +using TodoList.DataAccess; #nullable disable diff --git a/ToDoList/Migrations/20240824142029_Init.cs b/TodoList.DataAccess/Migrations/20240824142029_Init.cs similarity index 100% rename from ToDoList/Migrations/20240824142029_Init.cs rename to TodoList.DataAccess/Migrations/20240824142029_Init.cs diff --git a/ToDoList/Migrations/TodoListDBContextModelSnapshot.cs b/TodoList.DataAccess/Migrations/TodoListDBContextModelSnapshot.cs similarity index 99% rename from ToDoList/Migrations/TodoListDBContextModelSnapshot.cs rename to TodoList.DataAccess/Migrations/TodoListDBContextModelSnapshot.cs index 1a7945d..b078f82 100644 --- a/ToDoList/Migrations/TodoListDBContextModelSnapshot.cs +++ b/TodoList.DataAccess/Migrations/TodoListDBContextModelSnapshot.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using TodoList; +using TodoList.DataAccess; #nullable disable diff --git a/TodoList.DataAccess/Repositories/Contracts/ITodoRepository.cs b/TodoList.DataAccess/Repositories/Contracts/ITodoRepository.cs new file mode 100644 index 0000000..3ca7cb3 --- /dev/null +++ b/TodoList.DataAccess/Repositories/Contracts/ITodoRepository.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using TodoList.Core.Models; + +namespace TodoList.DataAccess.Repositories.Contracts +{ + public interface ITodoRepository + { + Task Create(Todo newTodo); + void Update(Todo updateTodo); + void Delete(Todo deleteTodo); + Task> GetAll(); + Task> GetAll(Expression> predicate); + Task GetById(int id); + Task GetFirstOrDefault(Expression> predicate); + Task SaveChangesAsync(); + } +} diff --git a/TodoList.DataAccess/Repositories/Contracts/IUserRepository.cs b/TodoList.DataAccess/Repositories/Contracts/IUserRepository.cs new file mode 100644 index 0000000..391a73d --- /dev/null +++ b/TodoList.DataAccess/Repositories/Contracts/IUserRepository.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using TodoList.Core.Models; + +namespace TodoList.DataAccess.Repositories.Contracts +{ + public interface IUserRepository + { + Task Create(User newUser); + void Update(User updateUser); + void Delete(User deleteUser); + Task> GetAll(); + Task> GetAll(Expression> predicate); + Task GetById(int id); + Task GetFirstOrDefault(Expression> predicate); + Task SaveChangesAsync(); + } +} diff --git a/TodoList.DataAccess/Repositories/TodoRepository.cs b/TodoList.DataAccess/Repositories/TodoRepository.cs new file mode 100644 index 0000000..c4f07d1 --- /dev/null +++ b/TodoList.DataAccess/Repositories/TodoRepository.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using TodoList.Core.Models; +using TodoList.DataAccess.Repositories.Contracts; + +namespace TodoList.DataAccess.Repositories +{ + public class TodoRepository(TodoListDBContext dBContext) : ITodoRepository + { + private readonly TodoListDBContext _dbContext = dBContext; + + public async Task Create(Todo newTodo) + { + await _dbContext.Todos.AddAsync(newTodo); + } + + public void Delete(Todo deleteTodo) + { + _dbContext.Remove(deleteTodo); + } + + public async Task> GetAll() + => await _dbContext.Todos.ToListAsync(); + + public async Task> GetAll(Expression> predicate) + => await _dbContext.Todos.Where(predicate).ToListAsync(); + + public async Task GetById(int id) + { + Todo? foundTodo = await _dbContext.Todos.FindAsync(id); + + return foundTodo is null ? null : foundTodo; + } + + public async Task GetFirstOrDefault(Expression> predicate) + => await _dbContext.Todos.Where(predicate).FirstOrDefaultAsync(); + + public async Task SaveChangesAsync() + => await _dbContext.SaveChangesAsync(); + + public void Update(Todo updateTodo) + { + _dbContext.Todos.Update(updateTodo); + } + } +} diff --git a/TodoList.DataAccess/Repositories/UserRepository.cs b/TodoList.DataAccess/Repositories/UserRepository.cs new file mode 100644 index 0000000..f690ac4 --- /dev/null +++ b/TodoList.DataAccess/Repositories/UserRepository.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; +using TodoList.Core.Models; +using TodoList.DataAccess.Repositories.Contracts; + +namespace TodoList.DataAccess.Repositories +{ + public class UserRepository(TodoListDBContext todoListDBContext) : IUserRepository + { + private readonly TodoListDBContext _dbContext = todoListDBContext; + + public async Task Create(User newUser) + { + await _dbContext.Users.AddAsync(newUser); + } + + public void Delete(User deleteUser) + { + _dbContext.Remove(deleteUser); + } + + public async Task> GetAll() + => await _dbContext.Users.ToListAsync(); + + public async Task> GetAll(Expression> predicate) + => await _dbContext.Users.Where(predicate).ToListAsync(); + + public async Task GetById(int id) + { + User? foundUser = await _dbContext.Users.FindAsync(id); + + return foundUser is null ? null : foundUser; + } + + public async Task GetFirstOrDefault(Expression> predicate) + => await _dbContext.Users.Where(predicate).FirstOrDefaultAsync(); + + public async Task SaveChangesAsync() + => await _dbContext.SaveChangesAsync(); + + public void Update(User updateUser) + { + _dbContext.Users.Update(updateUser); + } + } +} diff --git a/TodoList.DataAccess/ServiceExtensions/ServiceExtensions.cs b/TodoList.DataAccess/ServiceExtensions/ServiceExtensions.cs new file mode 100644 index 0000000..b272fde --- /dev/null +++ b/TodoList.DataAccess/ServiceExtensions/ServiceExtensions.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TodoList.DataAccess.Repositories; +using TodoList.DataAccess.Repositories.Contracts; + +namespace TodoList.DataAccess.ServiceExtensions +{ + public static class ServiceExtensions + { + public static IServiceCollection AddDataAccess(this IServiceCollection services, IConfiguration configuration) + { + services.ConfigureDbContext(configuration); + + services.ConfigureDI(); + + return services; + } + + private static IServiceCollection ConfigureDbContext(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(opt => + { + opt.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); + }); + + return services; + } + + private static IServiceCollection ConfigureDI(this IServiceCollection services) + { + services + .AddScoped() + .AddScoped(); + + return services; + } + } +} diff --git a/TodoList.DataAccess/TodoList.DataAccess.csproj b/TodoList.DataAccess/TodoList.DataAccess.csproj new file mode 100644 index 0000000..81f66cb --- /dev/null +++ b/TodoList.DataAccess/TodoList.DataAccess.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/ToDoList/TodoListDBContext.cs b/TodoList.DataAccess/TodoListDBContext.cs similarity index 86% rename from ToDoList/TodoListDBContext.cs rename to TodoList.DataAccess/TodoListDBContext.cs index 77bc243..cd14c9e 100644 --- a/ToDoList/TodoListDBContext.cs +++ b/TodoList.DataAccess/TodoListDBContext.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; -using TodoList.Models; +using TodoList.Core.Models; -namespace TodoList; +namespace TodoList.DataAccess; public class TodoListDBContext(DbContextOptions options) : DbContext(options) { From 9eadefa3dbf0bf6cb74217cfc8af952c5fa59b90 Mon Sep 17 00:00:00 2001 From: Stefan Draganov Date: Sun, 10 Nov 2024 21:36:40 +0100 Subject: [PATCH 2/2] Fixed minor issues in appsettings and TodoService --- ToDoList/appsettings.Development.json | 12 ++++++------ ToDoList/appsettings.json | 2 +- TodoList.BusinessLogic/Services/TodoService.cs | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ToDoList/appsettings.Development.json b/ToDoList/appsettings.Development.json index 9e7d2e6..6b77fcf 100644 --- a/ToDoList/appsettings.Development.json +++ b/ToDoList/appsettings.Development.json @@ -6,12 +6,12 @@ "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" - }, - "JwtSettings": { - "Issuer": "test-issuer", - "Audience": "test-audience", - "SecretKey": "BEHZAD_DARA_@!_KEY_!@_BEHZAD_DARA", - "ExpirationInMinutes": "15" } + }, + "JwtSettings": { + "Issuer": "test-issuer", + "Audience": "test-audience", + "SecretKey": "BEHZAD_DARA_@!_KEY_!@_BEHZAD_DARA", + "ExpirationInMinutes": "15" } } diff --git a/ToDoList/appsettings.json b/ToDoList/appsettings.json index 11aeae9..41977f8 100644 --- a/ToDoList/appsettings.json +++ b/ToDoList/appsettings.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Data Source=localhost;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;" + "DefaultConnection": "Data Source=BEHZAD-DARA\\LOCALDB;Initial Catalog=TodoListDB;Integrated Security=True;TrustServerCertificate=true;" }, "Logging": { "LogLevel": { diff --git a/TodoList.BusinessLogic/Services/TodoService.cs b/TodoList.BusinessLogic/Services/TodoService.cs index 21343ee..049a52d 100644 --- a/TodoList.BusinessLogic/Services/TodoService.cs +++ b/TodoList.BusinessLogic/Services/TodoService.cs @@ -67,7 +67,7 @@ public async Task> Get() public async Task Update(UpdateTodoDTO input) { - var todo = await _todoRepository.GetById(input.Id); + Todo? todo = await _todoRepository.GetById(input.Id); if (todo is null) { @@ -79,6 +79,7 @@ public async Task> Get() todo.Title = input.Title; todo.IsDone = input.IsDone; + _todoRepository.Update(todo); await _todoRepository.SaveChangesAsync(); return todo;