Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
c2574ac1d4 |
|||
|
dcd6ad3592 |
3 changed files with 76 additions and 2 deletions
|
|
@ -112,4 +112,35 @@ public sealed class RdvQueryApiControllerTests : IClassFixture<ApiWebServerFixtu
|
||||||
Assert.NotNull(created);
|
Assert.NotNull(created);
|
||||||
Assert.Equal("alice", created!.ClientId);
|
Assert.Equal("alice", created!.ClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PostQuery_accepts_local_datetime_and_persists_as_utc()
|
||||||
|
{
|
||||||
|
_fixture.ResetAndSeedRdvQueryGraph();
|
||||||
|
using var http = NewClient(subject: "alice");
|
||||||
|
|
||||||
|
var localEventDate = DateTime.Now.AddDays(2);
|
||||||
|
var createPayload = new
|
||||||
|
{
|
||||||
|
ActivityCode = "dev",
|
||||||
|
PerformerId = "alice",
|
||||||
|
Consent = true,
|
||||||
|
EventDate = localEventDate,
|
||||||
|
Location = new
|
||||||
|
{
|
||||||
|
Address = "3 rue du Test",
|
||||||
|
Latitude = 48.8568,
|
||||||
|
Longitude = 2.3524,
|
||||||
|
},
|
||||||
|
Reason = "Rendez-vous date locale",
|
||||||
|
Status = QueryStatus.Inserted,
|
||||||
|
};
|
||||||
|
|
||||||
|
var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken);
|
||||||
|
Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode);
|
||||||
|
|
||||||
|
var created = await createResponse.Content.ReadFromJsonAsync<RdvQuery>(TestContext.Current.CancellationToken);
|
||||||
|
Assert.NotNull(created);
|
||||||
|
Assert.Equal(DateTimeKind.Utc, created!.EventDate.Kind);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ public class RdvQueryApiController : Controller
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
// Security: the caller always posts for themselves.
|
// Security: the caller always posts for themselves.
|
||||||
query.ClientId = uid;
|
query.ClientId = uid;
|
||||||
|
query.EventDate = EnsureUtc(query.EventDate);
|
||||||
|
|
||||||
ModelState.Remove("Client");
|
ModelState.Remove("Client");
|
||||||
ModelState.Remove("ClientId");
|
ModelState.Remove("ClientId");
|
||||||
|
|
@ -140,7 +141,7 @@ public class RdvQueryApiController : Controller
|
||||||
existing.ActivityCode = query.ActivityCode;
|
existing.ActivityCode = query.ActivityCode;
|
||||||
existing.PerformerId = query.PerformerId;
|
existing.PerformerId = query.PerformerId;
|
||||||
existing.Consent = query.Consent;
|
existing.Consent = query.Consent;
|
||||||
existing.EventDate = query.EventDate;
|
existing.EventDate = EnsureUtc(query.EventDate);
|
||||||
existing.LocationType = query.LocationType;
|
existing.LocationType = query.LocationType;
|
||||||
existing.Reason = query.Reason;
|
existing.Reason = query.Reason;
|
||||||
existing.Status = query.Status;
|
existing.Status = query.Status;
|
||||||
|
|
@ -206,4 +207,14 @@ public class RdvQueryApiController : Controller
|
||||||
{
|
{
|
||||||
return _context.RdvQueries.Any(e => e.Id == id);
|
return _context.RdvQueries.Any(e => e.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DateTime EnsureUtc(DateTime value)
|
||||||
|
{
|
||||||
|
return value.Kind switch
|
||||||
|
{
|
||||||
|
DateTimeKind.Utc => value,
|
||||||
|
DateTimeKind.Local => value.ToUniversalTime(),
|
||||||
|
_ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -467,8 +467,25 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
|
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
|
var existingUser = await _userManager.FindByEmailAsync(model.Email);
|
||||||
|
if (existingUser is not null)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(model.Email), _localizer["DuplicateEmail"]);
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
|
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email };
|
||||||
var result = await _userManager.CreateAsync(user, model.Password);
|
IdentityResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = await _userManager.CreateAsync(user, model.Password);
|
||||||
|
}
|
||||||
|
catch (DbUpdateException ex) when (IsDuplicateEmailViolation(ex))
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Registration rejected: duplicate email '{Email}'.", model.Email);
|
||||||
|
ModelState.AddModelError(nameof(model.Email), _localizer["DuplicateEmail"]);
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(3, "User created a new account with password.");
|
_logger.LogInformation(3, "User created a new account with password.");
|
||||||
|
|
@ -517,6 +534,21 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDuplicateEmailViolation(Exception exception)
|
||||||
|
{
|
||||||
|
for (var current = exception; current is not null; current = current.InnerException)
|
||||||
|
{
|
||||||
|
if (current is PostgresException pg
|
||||||
|
&& pg.SqlState == PostgresErrorCodes.UniqueViolation
|
||||||
|
&& string.Equals(pg.ConstraintName, "AK_AspNetUsers_Email", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize, HttpPost, ValidateAntiForgeryToken]
|
[Authorize, HttpPost, ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> SendConfirationEmail()
|
public async Task<IActionResult> SendConfirationEmail()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue