-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
pc
authored and
pc
committed
Jul 1, 2024
1 parent
04b1bea
commit 530389e
Showing
59 changed files
with
2,573 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record AuthResponseDto(LoggedInUser User, string Token); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
public record ChangePasswordDto(string OldPassword, string NewPassword); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
public record struct DougOptionDto(string Flavor, string Topping); | ||
|
||
public record DougDto(int Id, string Name, string Image, double Price, DougOptionDto[] Options); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record LoggedInUser(Guid Id, string Name, string Email, string Address); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record OrderItemDto(long Id, int IcecreamId, string Name, int Quantity, double Price, string Flavor, string Topping) | ||
{ | ||
public double TotalPrice => Quantity * Price; | ||
} | ||
public record OrderDto(long Id, DateTime OrderdAt, double TotalPrice, int ItemsCount = 0) | ||
{ | ||
public string ItemCountDisplay => ItemsCount + (ItemsCount > 1 ? " Items" : " Item"); | ||
} | ||
|
||
public record OrderPlaceDto(OrderDto Order, OrderItemDto[] Items); | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record ResultDto(bool IsSuccess, string? ErrorMessage) | ||
{ | ||
public static ResultDto Success() => new(true, null); | ||
public static ResultDto Failure(string? errorMessage) => new(false, errorMessage); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record ResultWithDataDto<TData>(bool IsSuccess, TData Data, string? ErrorMessage) | ||
{ | ||
public static ResultWithDataDto<TData> Success(TData data) => new(true, data, null); | ||
public static ResultWithDataDto<TData> Failure(string? errorMessage) => new(false, default, errorMessage); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
|
||
public record SigninRequestDto(string Email, string Password); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
namespace DoughnutMaui.Shared.Dtos; | ||
public record SignupRequestDto(string Name, string Email, string Password, string Address); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
using DoughnutMaui.Api.Services; | ||
using DoughnutMaui.Shared.Dtos; | ||
using System.Security.Claims; | ||
|
||
namespace DoughnutMaui.Api.Endpoints; | ||
|
||
public static class Endpoints | ||
{ | ||
private static Guid GetUserId(this ClaimsPrincipal principal) => | ||
Guid.Parse(principal.FindFirstValue(ClaimTypes.NameIdentifier)!); | ||
|
||
public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder app) | ||
{ | ||
app.MapPost("/api/auth/signup", | ||
async (SignupRequestDto dto, AuthService authService) => | ||
TypedResults.Ok(await authService.SignupAsync(dto))); | ||
|
||
app.MapPost("/api/auth/signin", | ||
async (SigninRequestDto dto, AuthService authService) => | ||
TypedResults.Ok(await authService.SigninAsync(dto))); | ||
|
||
app.MapPost("/api/auth/change-password", | ||
async (ChangePasswordDto dto, ClaimsPrincipal principal, AuthService authService) => | ||
TypedResults.Ok(await authService.ChangePasswordAsync(dto, principal.GetUserId())) | ||
) | ||
.RequireAuthorization(); | ||
|
||
app.MapGet("/api/icecreams", | ||
async(DougService icecreamService) => | ||
TypedResults.Ok(await icecreamService.GetIcecreamsAsync())); | ||
|
||
var orderGroup = app.MapGroup("/api/orders").RequireAuthorization(); | ||
|
||
orderGroup.MapPost("/place-order", | ||
async (OrderPlaceDto dto, ClaimsPrincipal principal, OrderService orderService) => | ||
await orderService.PlaceOrderAsync(dto, principal.GetUserId())); | ||
|
||
orderGroup.MapGet("", | ||
async (ClaimsPrincipal principal, OrderService orderService) => | ||
TypedResults.Ok(await orderService.GetUserOrdersAsync(principal.GetUserId()))); | ||
|
||
orderGroup.MapGet("/{orderId:long}/items", | ||
async (long orderId, ClaimsPrincipal principal, OrderService orderService) => | ||
TypedResults.Ok(await orderService.GetUserOrderItemsAsync(orderId, principal.GetUserId()))); | ||
|
||
return app; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
using DoughnutMaui.Api.Data; | ||
using DoughnutMaui.Api.Data.Entities; | ||
using DoughnutMaui.Shared.Dtos; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace DoughnutMaui.Api.Services; | ||
|
||
public class AuthService(DataContext context, TokenService tokenService, PasswordService passwordService) | ||
{ | ||
private readonly DataContext _context = context; | ||
private readonly TokenService _tokenService = tokenService; | ||
private readonly PasswordService _passwordService = passwordService; | ||
|
||
public async Task<ResultWithDataDto<AuthResponseDto>> SignupAsync(SignupRequestDto dto) | ||
{ | ||
if(await _context.Users.AsNoTracking().AnyAsync(u=> u.Email == dto.Email)) | ||
{ | ||
return ResultWithDataDto<AuthResponseDto>.Failure("Email alredy exists"); | ||
} | ||
|
||
var user = new User { | ||
Email = dto.Email, | ||
Address = dto.Address, | ||
Name = dto.Name, | ||
}; | ||
|
||
(user.Salt, user.Hash) = _passwordService.GenerateSaltAndHash(dto.Password); | ||
|
||
try | ||
{ | ||
await _context.Users.AddAsync(user); | ||
await _context.SaveChangesAsync(); | ||
return GenerateAuthResponse(user); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return ResultWithDataDto<AuthResponseDto>.Failure(ex.Message); | ||
} | ||
} | ||
|
||
|
||
public async Task<ResultWithDataDto<AuthResponseDto>> SigninAsync(SigninRequestDto dto) | ||
{ | ||
var dbUser = await _context.Users | ||
.AsNoTracking() | ||
.FirstOrDefaultAsync(u=> u.Email == dto.Email); | ||
if (dbUser is null) | ||
return ResultWithDataDto<AuthResponseDto>.Failure("User does not exist"); | ||
|
||
if(!_passwordService.AreEqual(dto.Password, dbUser.Salt, dbUser.Hash)) | ||
return ResultWithDataDto<AuthResponseDto>.Failure("Incorrect password"); | ||
|
||
return GenerateAuthResponse(dbUser); | ||
} | ||
|
||
private ResultWithDataDto<AuthResponseDto> GenerateAuthResponse(User user) | ||
{ | ||
var loggedInUser = new LoggedInUser(user.Id, user.Name, user.Email, user.Address); | ||
var token = _tokenService.GenerateJwt(loggedInUser); | ||
|
||
var authResponse = new AuthResponseDto(loggedInUser, token); | ||
|
||
return ResultWithDataDto<AuthResponseDto>.Success(authResponse); | ||
} | ||
|
||
public async Task<ResultDto> ChangePasswordAsync(ChangePasswordDto dto, Guid userId) | ||
{ | ||
var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId); | ||
if (user is null) | ||
return ResultDto.Failure("Invalid request"); | ||
|
||
if(!_passwordService.AreEqual(dto.OldPassword, user.Salt, user.Hash)) | ||
{ | ||
return ResultDto.Failure("Incorrect password"); | ||
} | ||
|
||
(user.Salt, user.Hash) = _passwordService.GenerateSaltAndHash(dto.NewPassword); | ||
|
||
await _context.SaveChangesAsync(); | ||
return ResultDto.Success(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using DoughnutMaui.Api.Data; | ||
using DoughnutMaui.Shared.Dtos; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace DoughnutMaui.Api.Services; | ||
|
||
public class DougService(DataContext context) | ||
{ | ||
private readonly DataContext _context = context; | ||
|
||
public async Task<DougDto[]> GetIcecreamsAsync() => | ||
await _context.Icecreams.AsNoTracking() | ||
.Select(i=> | ||
new DougDto( | ||
i.Id, | ||
i.Name, | ||
i.Image, | ||
i.Price, | ||
i.Options | ||
.Select(o=> new DougOptionDto(o.Sprinkle, o.Topping)) | ||
.ToArray() | ||
) | ||
) | ||
.ToArrayAsync(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using DoughnutMaui.Api.Data; | ||
using DoughnutMaui.Api.Data.Entities; | ||
using DoughnutMaui.Shared.Dtos; | ||
using Microsoft.EntityFrameworkCore; | ||
|
||
namespace DoughnutMaui.Api.Services; | ||
|
||
public class OrderService(DataContext context) | ||
{ | ||
private readonly DataContext _context = context; | ||
|
||
public async Task<ResultDto> PlaceOrderAsync(OrderPlaceDto dto, Guid customerId) | ||
{ | ||
var customer = await _context.Users.FirstOrDefaultAsync(u => u.Id == customerId); | ||
if (customer is null) | ||
return ResultDto.Failure("Custome does not exist"); | ||
|
||
var orderItems = dto.Items.Select(i => | ||
new OrderItem | ||
{ | ||
Flavor = i.Flavor, | ||
DougId = i.IcecreamId, | ||
Name = i.Name, | ||
Price = i.Price, | ||
Quantity = i.Quantity, | ||
Topping = i.Topping, | ||
TotalPrice = i.TotalPrice | ||
}); | ||
|
||
var order = new Order | ||
{ | ||
CustomerId = customerId, | ||
CustomerAddress = customer.Address, | ||
CustomerEmail = customer.Email, | ||
CustomerName = customer.Name, | ||
OrderdAt = DateTime.Now, | ||
TotalPrice = orderItems.Sum(o => o.TotalPrice), | ||
Items = orderItems.ToArray() | ||
}; | ||
try | ||
{ | ||
await _context.Orders.AddAsync(order); | ||
await _context.SaveChangesAsync(); | ||
return ResultDto.Success(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
return ResultDto.Failure(ex.Message); | ||
} | ||
} | ||
|
||
public async Task<OrderDto[]> GetUserOrdersAsync(Guid userId) => | ||
await _context.Orders | ||
.Where(o => o.CustomerId == userId) | ||
.Select(o => new OrderDto(o.Id, o.OrderdAt, o.TotalPrice, o.Items.Count)) | ||
.ToArrayAsync(); | ||
public async Task<OrderItemDto[]> GetUserOrderItemsAsync(long orderId, Guid userId) => | ||
await _context.OrderItems | ||
.Where(i=> i.OrderId == orderId && i.Order.CustomerId == userId) | ||
.Select(i=> new OrderItemDto(i.Id, i.DougId, i.Name, i.Quantity, i.Price, i.Flavor, i.Topping)) | ||
.ToArrayAsync(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
|
||
namespace DoughnutMaui.Api.Services; | ||
|
||
public class PasswordService | ||
{ | ||
private const int SaltSize = 10; | ||
public (string salt, string hashedPassword) GenerateSaltAndHash(string plainPassword) | ||
{ | ||
if(string.IsNullOrWhiteSpace(plainPassword)) | ||
throw new ArgumentNullException(nameof(plainPassword)); | ||
|
||
var buffer = RandomNumberGenerator.GetBytes(SaltSize); | ||
var salt = Convert.ToBase64String(buffer); | ||
|
||
var hashedPassword = GenerateHashedPassword(plainPassword, salt); | ||
|
||
return (salt, hashedPassword); | ||
} | ||
|
||
public bool AreEqual(string plainPassword, string salt, string hashedPassword) | ||
{ | ||
var newHashedPassword = GenerateHashedPassword(plainPassword, salt); | ||
return newHashedPassword == hashedPassword; | ||
} | ||
|
||
private static string GenerateHashedPassword(string plainPassword, string salt) { | ||
var bytes = Encoding.UTF8.GetBytes(plainPassword + salt); | ||
var hash = SHA256.HashData(bytes); | ||
|
||
return Convert.ToBase64String(hash); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using DoughnutMaui.Shared.Dtos; | ||
using Microsoft.IdentityModel.Tokens; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using System.Text; | ||
|
||
namespace DoughnutMaui.Api.Services; | ||
|
||
public class TokenService(IConfiguration configuration) | ||
{ | ||
private readonly IConfiguration _configuration = configuration; | ||
|
||
public static TokenValidationParameters GetTokenValidationParameters(IConfiguration configuration) => | ||
new() | ||
{ | ||
ValidateAudience = false, | ||
ValidateIssuer = true, | ||
ValidateLifetime = true, | ||
ValidateIssuerSigningKey = true, | ||
ValidIssuer = configuration["Jwt:Issuer"], | ||
IssuerSigningKey = GetSecurityKey(configuration), | ||
}; | ||
|
||
public string GenerateJwt(LoggedInUser user) | ||
{ | ||
var securityKey = GetSecurityKey(_configuration); | ||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); | ||
|
||
var issuer = _configuration["Jwt:Issuer"]; | ||
var expireInMinutes = Convert.ToInt32(_configuration["Jwt:ExpireInMinute"]); | ||
|
||
Claim[] claims = [ | ||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), | ||
new Claim(ClaimTypes.Name, user.Name), | ||
new Claim(ClaimTypes.Email, user.Email), | ||
new Claim(ClaimTypes.StreetAddress, user.Address), | ||
]; | ||
|
||
var token = new JwtSecurityToken( | ||
issuer: issuer, | ||
audience: "*", | ||
claims: claims, | ||
expires: DateTime.Now.AddMinutes(expireInMinutes), | ||
signingCredentials: credentials); | ||
|
||
var jwt = new JwtSecurityTokenHandler().WriteToken(token); | ||
|
||
return jwt; | ||
} | ||
|
||
private static SymmetricSecurityKey GetSecurityKey(IConfiguration configuration) | ||
{ | ||
var secretKey = configuration["Jwt:SecretKey"]; | ||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey!)); | ||
return securityKey; | ||
} | ||
} |
Oops, something went wrong.