Skip to content

Commit ba0d90d

Browse files
committed
231124
1 parent abca414 commit ba0d90d

28 files changed

+544
-158
lines changed

TodoListBlazorWasm.Api/Controllers/LoginController.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@ public LoginController(IConfiguration configuration, SignInManager<User> signInM
2929
}
3030

3131
[HttpPost]
32-
public async ValueTask<IActionResult> Login([Required] LoginRequest request) => !(await _signInManager.PasswordSignInAsync(request.UserName!, request.Password!, false, false)).Succeeded
33-
? BadRequest(new LoginResponse
34-
{
35-
Success = false,
36-
Error = "Username or Password are invalid"
37-
})
38-
: Ok(new LoginResponse
32+
public async ValueTask<IActionResult> Login([Required] LoginRequest request) => (await _signInManager.PasswordSignInAsync(request.UserName!, request.Password!, false, false)).Succeeded
33+
? Ok(new LoginResponse
3934
{
4035
Success = true,
4136
Token = new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_configuration["JwtIssuer"], _configuration["JwtAudience"], new[]
4237
{
4338
new Claim(Name, request.UserName!)
4439
}, expires: Now.AddDays(_configuration["JwtExpiryInDays"]!.ToInt(1)), signingCredentials: new SigningCredentials(new SymmetricSecurityKey(UTF8.GetBytes(_configuration["JwtSecurityKey"] ?? string.Empty)), HmacSha256)))
40+
})
41+
: BadRequest(new LoginResponse
42+
{
43+
Success = false,
44+
Error = "Username or Password are invalid"
4545
});
4646
}

TodoListBlazorWasm.Api/Controllers/TasksController.cs

Lines changed: 84 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using TodoListBlazorWasm.Api.Repositories;
44
using TodoListBlazorWasm.Models.Requests.Task;
55
using TodoListBlazorWasm.Models.Responses;
6+
using YANLib;
67
using static System.DateTime;
78
using static TodoListBlazorWasm.Models.Enums.Status;
89

@@ -12,12 +13,12 @@ namespace TodoListBlazorWasm.Api.Controllers;
1213
[ApiController]
1314
public sealed class TasksController : ControllerBase
1415
{
15-
private readonly ITaskRepository _taskRepository;
16+
private readonly ITaskRepository _repository;
1617

17-
public TasksController(ITaskRepository taskRepository) => _taskRepository = taskRepository;
18+
public TasksController(ITaskRepository repository) => _repository = repository;
1819

1920
[HttpGet]
20-
public async ValueTask<IActionResult> GetAll() => Ok((await _taskRepository.GetAll()).Select(x => new TaskResponse
21+
public async ValueTask<IActionResult> GetAll() => Ok((await _repository.GetAll()).Select(x => new TaskResponse
2122
{
2223
Id = x.Id,
2324
Name = x.Name,
@@ -36,7 +37,7 @@ public sealed class TasksController : ControllerBase
3637
[HttpGet("{id}")]
3738
public async ValueTask<IActionResult> Get(Guid id)
3839
{
39-
var ent = await _taskRepository.Get(id);
40+
var ent = await _repository.Get(id);
4041

4142
return Ok(ent is null ? default : new TaskResponse
4243
{
@@ -46,7 +47,7 @@ public async ValueTask<IActionResult> Get(Guid id)
4647
Status = ent.Status,
4748
CreatedAt = ent.CreatedAt,
4849
UpdatedAt = ent.UpdatedAt,
49-
Assignee = ent.Assignee is null ? null : new UserResponse
50+
Assignee = ent.Assignee is null ? default : new UserResponse
5051
{
5152
Id = ent.Assignee.Id,
5253
FirstName = ent.Assignee.FirstName,
@@ -56,47 +57,99 @@ public async ValueTask<IActionResult> Get(Guid id)
5657
}
5758

5859
[HttpPost]
59-
public async ValueTask<IActionResult> Create([Required] TaskCreateRequest request) => !ModelState.IsValid ? BadRequest(ModelState) : Ok(await _taskRepository.Create(new Entities.Task
60+
public async ValueTask<IActionResult> Create([Required] TaskCreateRequest request)
6061
{
61-
Id = request.Id,
62-
Name = request.Name,
63-
AssigneeId = request.AssigneeId,
64-
Priority = request.Priority,
65-
Status = Open,
66-
CreatedAt = Now
67-
}));
62+
if (!ModelState.IsValid)
63+
{
64+
return BadRequest(ModelState);
65+
}
6866

69-
[HttpPut("{id}")]
67+
var rslt = await _repository.Create(new Entities.Task
68+
{
69+
Id = request.Id,
70+
Name = request.Name,
71+
AssigneeId = request.AssigneeId,
72+
Priority = request.Priority,
73+
Status = Open,
74+
CreatedAt = Now
75+
});
76+
77+
return rslt is null ? Problem() : Ok(new TaskResponse
78+
{
79+
Id = rslt!.Id,
80+
Name = rslt.Name,
81+
Priority = rslt.Priority,
82+
Status = rslt.Status,
83+
CreatedAt = rslt.CreatedAt,
84+
UpdatedAt = rslt.UpdatedAt,
85+
Assignee = rslt.Assignee is null ? default : new UserResponse
86+
{
87+
Id = rslt.Assignee.Id,
88+
FirstName = rslt.Assignee.FirstName,
89+
LastName = rslt.Assignee.LastName,
90+
}
91+
});
92+
}
93+
94+
[HttpPatch("{id}")]
7095
public async ValueTask<IActionResult> Update(Guid id, [Required] TaskUpdateRequest request)
7196
{
7297
if (!ModelState.IsValid)
7398
{
7499
return BadRequest(ModelState);
75100
}
76-
else
101+
102+
var ent = await _repository.Get(id);
103+
104+
if (ent is null)
105+
{
106+
return NotFound($"{id} is not found!");
107+
}
108+
109+
if (request.Name!.IsNotWhiteSpaceAndNull())
110+
{
111+
ent.Name = request.Name!;
112+
}
113+
114+
if (request.AssigneeId.HasValue)
77115
{
78-
var ent = await _taskRepository.Get(id);
79-
80-
return ent is null
81-
? NotFound($"{id} is not found!")
82-
: Ok(await _taskRepository.Update(new Entities.Task
83-
{
84-
Id = id,
85-
Name = request.Name,
86-
AssigneeId = request.AssigneeId,
87-
Priority = request.Priority,
88-
Status = request.Status,
89-
CreatedAt = ent.CreatedAt,
90-
UpdatedAt = Now
91-
}));
116+
ent.AssigneeId = request.AssigneeId.Value;
92117
}
118+
119+
if (request.Priority.HasValue)
120+
{
121+
ent.Priority = request.Priority.Value;
122+
}
123+
124+
if (request.Status.HasValue)
125+
{
126+
ent.Status = request.Status.Value;
127+
}
128+
129+
var rslt = await _repository.Update(ent);
130+
131+
return rslt is null ? Problem() : Ok(new TaskResponse
132+
{
133+
Id = rslt.Id,
134+
Name = rslt.Name,
135+
Priority = rslt.Priority,
136+
Status = rslt.Status,
137+
CreatedAt = rslt.CreatedAt,
138+
UpdatedAt = rslt.UpdatedAt,
139+
Assignee = rslt.Assignee is null ? default : new UserResponse
140+
{
141+
Id = rslt.Assignee.Id,
142+
FirstName = rslt.Assignee.FirstName,
143+
LastName = rslt.Assignee.LastName,
144+
}
145+
});
93146
}
94147

95148
[HttpDelete("{id}")]
96149
public async ValueTask<IActionResult> Delete(Guid id)
97150
{
98-
var ent = await _taskRepository.Get(id);
151+
var ent = await _repository.Get(id);
99152

100-
return ent is null ? NotFound($"{id} is not found!") : Ok(await _taskRepository.Delete(ent));
153+
return ent is null ? NotFound($"{id} is not found!") : Ok(await _repository.Delete(ent));
101154
}
102155
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using Microsoft.AspNetCore.Identity;
2+
using Microsoft.AspNetCore.Mvc;
3+
using System.ComponentModel.DataAnnotations;
4+
using TodoListBlazorWasm.Api.Entities;
5+
using TodoListBlazorWasm.Api.Repositories;
6+
using TodoListBlazorWasm.Models.Requests.User;
7+
using TodoListBlazorWasm.Models.Responses;
8+
using YANLib;
9+
using static System.Guid;
10+
11+
namespace TodoListBlazorWasm.Api.Controllers;
12+
13+
[Route("api/users")]
14+
[ApiController]
15+
public sealed class UserController : ControllerBase
16+
{
17+
private readonly IUserRepository _repository;
18+
private readonly IPasswordHasher<User> _passwordHasher = new PasswordHasher<User>();
19+
20+
public UserController(IUserRepository repository) => _repository = repository;
21+
22+
[HttpGet]
23+
public async ValueTask<IActionResult> GetAll() => Ok((await _repository.GetAll()).Select(x => new UserResponse
24+
{
25+
Id = x.Id,
26+
FirstName = x.FirstName,
27+
LastName = x.LastName
28+
}));
29+
30+
[HttpGet("{id}")]
31+
public async ValueTask<IActionResult> Get(Guid id)
32+
{
33+
var ent = await _repository.Get(id);
34+
35+
return Ok(ent is null ? default : new UserResponse
36+
{
37+
Id = ent.Id,
38+
FirstName = ent.FirstName,
39+
LastName = ent.LastName
40+
});
41+
}
42+
43+
[HttpPost]
44+
public async ValueTask<IActionResult> Create([Required] UserCreateRequest request)
45+
{
46+
if (!ModelState.IsValid)
47+
{
48+
return BadRequest(ModelState);
49+
}
50+
51+
var ent = new User
52+
{
53+
Id = NewGuid(),
54+
Email = request.Email,
55+
NormalizedEmail = request.Email.ToUpperInvariant(),
56+
PhoneNumber = request.PhoneNumber,
57+
FirstName = request.FirstName,
58+
LastName = request.LastName,
59+
UserName = request.UserName,
60+
NormalizedUserName = request.UserName.ToUpperInvariant()
61+
};
62+
63+
ent.PasswordHash = _passwordHasher.HashPassword(ent, request.Password);
64+
65+
var rslt = await _repository.Create(ent);
66+
67+
return rslt is null ? Problem() : Ok(new UserResponse
68+
{
69+
Id = rslt.Id,
70+
FirstName = rslt.FirstName,
71+
LastName = rslt.LastName
72+
});
73+
}
74+
75+
[HttpPatch("{id}")]
76+
public async ValueTask<IActionResult> Update(Guid id, UserUpdateRequest request)
77+
{
78+
var ent = await _repository.Get(id);
79+
80+
if (ent is null)
81+
{
82+
return NotFound($"{id} is not found!");
83+
}
84+
85+
if (request.FirstName!.IsNotWhiteSpaceAndNull())
86+
{
87+
ent.FirstName = request.FirstName!;
88+
}
89+
90+
if (request.LastName!.IsNotWhiteSpaceAndNull())
91+
{
92+
ent.LastName = request.LastName!;
93+
}
94+
95+
if (request.Email!.IsNotWhiteSpaceAndNull())
96+
{
97+
ent.Email = request.Email;
98+
ent.NormalizedEmail = request.Email!.ToUpperInvariant();
99+
}
100+
101+
if (request.PhoneNumber!.IsNotWhiteSpaceAndNull())
102+
{
103+
ent.PhoneNumber = request.PhoneNumber;
104+
}
105+
106+
if (request.Password!.IsNotWhiteSpaceAndNull())
107+
{
108+
ent.PasswordHash = _passwordHasher.HashPassword(ent, request.Password!);
109+
}
110+
111+
var rslt = await _repository.Update(ent);
112+
113+
return rslt is null ? Problem() : Ok(new UserResponse
114+
{
115+
Id = rslt.Id,
116+
FirstName = rslt.FirstName,
117+
LastName = rslt.LastName
118+
});
119+
}
120+
121+
[HttpDelete("{id}")]
122+
public async ValueTask<IActionResult> Delete(Guid id)
123+
{
124+
var ent = await _repository.Get(id);
125+
126+
return ent is null ? NotFound($"{id} is not found!") : Ok(await _repository.Delete(ent));
127+
}
128+
}

TodoListBlazorWasm.Api/Data/TodoListDbContextSeed.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class TodoListDbContextSeed
1313
{
1414
private readonly IPasswordHasher<User> _passwordHasher = new PasswordHasher<User>();
1515

16-
public async Task SeedAsync(TodoListDbContext context, ILogger<TodoListDbContextSeed> logger)
16+
public async Task SeedAsync(ILogger<TodoListDbContextSeed> logger, TodoListDbContext context)
1717
{
1818
if (!context.Users.Any())
1919
{

TodoListBlazorWasm.Api/Entities/User.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,6 @@ public sealed class User : IdentityUser<Guid>
1010

1111
[MaxLength(100)]
1212
public required string LastName { get; set; }
13+
14+
public ICollection<Task>? Tasks { get; set; }
1315
}

TodoListBlazorWasm.Api/Extensions/HostExtension.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ public static IHost MigrateDbContext<TContext>(this IHost host, Action<TContext,
1313
{
1414
using (var scope = host.Services.CreateScope())
1515
{
16-
var service = scope.ServiceProvider;
17-
var logger = service.GetRequiredService<ILogger<TContext>>();
16+
var svc = scope.ServiceProvider;
17+
var logger = svc.GetRequiredService<ILogger<TContext>>();
1818

1919
try
2020
{
2121
logger.LogInformation("Migrating database associated with context {DbContextName}", typeof(TContext).Name);
2222

23-
var retries = 10;
23+
var rtrys = 10;
2424

2525
Handle<SqlException>().WaitAndRetry(
26-
retryCount: retries,
26+
retryCount: rtrys,
2727
sleepDurationProvider: r => FromSeconds(Pow(2, r)),
28-
onRetry: (e, t, r, c) => logger.LogWarning(e, "[{Prefix}] Exception {ExceptionType} with message {Message} detected on attempt {Retry} of {Retries}", nameof(TContext), e.GetType().Name, e.Message, r, retries)
29-
).Execute(() => InvokeSeeder(seeder!, service.GetService<TContext>(), service));
28+
onRetry: (e, t, r, c) => logger.LogWarning(e, "[{Prefix}] Exception {ExceptionType} with message {Message} detected on attempt {Retry} of {Retries}", nameof(TContext), e.GetType().Name, e.Message, r, rtrys)
29+
).Execute(() => InvokeSeeder(seeder, svc.GetService<TContext>(), svc));
3030
logger.LogInformation("Migrated database associated with context {DbContext}", typeof(TContext).Name);
3131
}
3232
catch (Exception ex)

0 commit comments

Comments
 (0)