diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs index 232c7a6..db0093f 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/InventoryController.cs @@ -136,38 +136,5 @@ public class InventoryController : Controller { } } - // Inventory/Delete => Décrémenter un produit. Va aller chercher directement dans la BD. - //[EnableCors("_myAllowSpecificOrigins"), HttpDelete(Name = "Inventory"), AllowAnonymous] - //public ActionResult Delete(int? id) { - // int rid = 0; - // if (!id.HasValue) { - // _logger.LogError(8, "Tentative de vente sans Id."); - // return BadRequest(); - // } - // try { - // ProductModel prod = _context.Products.First(x => x.Id == id); - // rid = prod.Id; - // if (prod.Quantity > 0) { - // prod.Quantity = prod.Quantity - 1; - // prod.Sales = prod.Sales + 1; - // prod.LastSale = DateTime.Now; - // if (prod.Quantity == 0) - // prod.Status = prod.Status == ProductModel.States.Clearance ? - // ProductModel.States.Discontinued : - // ProductModel.States.BackOrder; - // } else { - // _logger.LogError(8, $"Vente de produit pas en stock. Id Produit: {prod.Id}"); - // return BadRequest(); - // } - // _context.Products.Update(prod); - // _context.SaveChanges(); - // } catch (Exception e) { - // _logger.LogError(8, e.Message); - // return BadRequest(); - // } - // _cache.askForRefresh(); - // return rid; - //} - #endregion } \ No newline at end of file diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/InvoiceController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/InvoiceController.cs index 157985d..c1ee6bb 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/InvoiceController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/InvoiceController.cs @@ -31,11 +31,13 @@ public class InvoiceController : Controller { public InvoiceController(ILogger logger, InventoryContext context, DatabaseCacheService cache, + SignInManager signInMan, Microsoft.AspNetCore.Identity.UserManager userMan) { _logger = logger; _context = context; _cache = cache; _userMan = userMan; + _signInMan = signInMan; } #endregion @@ -46,18 +48,19 @@ public class InvoiceController : Controller { IList roles; string id; try { // Trouver les rôles de l'utilisateur, assumer non-admin si impossible à trouver. - roles = await _userMan.GetRolesAsync(await _userMan.GetUserAsync(_signInMan.Context.User)); + var user = await _userMan.GetUserAsync(_signInMan.Context.User); + roles = await _userMan.GetRolesAsync(user); } catch (Exception e) { _logger.LogError(10, e.Message); roles = new List(); } - try { + try { // TODO: Débugger ça. id = _signInMan.Context.User.Identity.GetUserId(); if (all is not null && all == true && roles.Contains("Administrateur")) - return _context.Invoices.Include("LinkedAccount, ShippingAddress").ToList(); - else return _context.Invoices.Include("ShippingAddress").Where(x => x.LinkedAccount != null && - x.LinkedAccount.Id == id).ToList(); + return Ok(_context.Invoices.Include("LinkedAccount, ShippingAddress").ToList()); + else return Ok(_context.Invoices.Include("ShippingAddress").Where(x => x.LinkedAccount != null && + x.LinkedAccount.Id == id).ToList()); } catch (Exception e) { _logger.LogError(10, e.Message); return BadRequest(); @@ -93,14 +96,14 @@ public class InvoiceController : Controller { [HttpPost, AllowAnonymous] public async Task> Post(InvoiceModel inv) { var user = await _userMan.GetUserAsync(_signInMan.Context.User); + var prodcom = inv.Products.ToList(); + Dictionary badprods = new(); List prods; if (user is not null) inv.LinkedAccount = user; inv.PurchaseDate = DateTime.Now; // Pour forcer la date. - var prodcom = inv.Products.ToList(); - try { prods = _context.Products.Where(x => inv.Products.Select(x => x.Product).Contains(x)).ToList(); } catch (Exception e) { @@ -108,18 +111,26 @@ public class InvoiceController : Controller { return BadRequest(); } + if (prods.Count == 0) + return BadRequest("Vous devez inclure au moins un produit à votre commande."); + foreach (var prod in prodcom) { // Update de quantités dans l'inventaire. ProductModel inventProd = prods.Where(x => x.Id == prod.Product.Id).First(); if (inventProd.Quantity > prod.Quantity) - return BadRequest(); // TODO: retourner le produit qui ne peut pas être vendu. + badprods.Add(prod.Id, prod.Quantity); if (inventProd.Quantity == prod.Quantity) { inventProd.Quantity = 0; inventProd.Status = inventProd.Status == ProductModel.States.Clearance ? ProductModel.States.Discontinued : ProductModel.States.BackOrder; } else inventProd.Quantity -= prod.Quantity; + inventProd.LastSale = DateTime.Now; + inventProd.Sales += prod.Quantity; } + if (badprods.Count > 0) // Retour des produits non-achetable avec l'inventaire restant. + return BadRequest(badprods.ToArray()); + try { // Faire les updates dans la BD. _context.Invoices.Add(inv); _context.Products.UpdateRange(prods); @@ -133,7 +144,7 @@ public class InvoiceController : Controller { return Ok(inv); } - [HttpPost, Authorize(Roles = "Client, Administrateur")] + [HttpPost, Route("CancelInvoice"), Authorize(Roles = "Client, Administrateur")] public async Task> Cancel(int id) { InvoiceModel inv; IList roles; @@ -159,10 +170,23 @@ public class InvoiceController : Controller { roles.Contains("Administrateur"))) return Unauthorized(); + if (inv.Status == InvoiceModel.InStates.Cancelled || + inv.Status == InvoiceModel.InStates.Returned) + return BadRequest("La commande à déjà été annulée."); + + if (inv.Status == InvoiceModel.InStates.Shipped) + return BadRequest("Il est trop tard pour annuler votre commande, veuillez contacter le service à la clientèle pour retourner la commande."); + inv.Status = InvoiceModel.InStates.Cancelled; - foreach (var prod in inv.Products) // Revert l'inventaire. + foreach (var prod in inv.Products) { // Revert l'inventaire. + if (prod.Product.Quantity == 0) + if (prod.Product.Status == ProductModel.States.Discontinued) + prod.Product.Status = ProductModel.States.Clearance; + else prod.Product.Status = ProductModel.States.Available; prod.Product.Quantity = prod.Product.Quantity + prod.Quantity; + prod.Product.Sales -= prod.Quantity; + } try { _context.Update(inv); @@ -173,7 +197,6 @@ public class InvoiceController : Controller { } _cache.askForRefresh(); - return Ok(inv); } diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/LoginController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/LoginController.cs index 06fe8bb..a02de43 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/LoginController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/LoginController.cs @@ -43,9 +43,12 @@ public class LoginController : Controller { } [HttpPost, Route("Login"), AllowAnonymous] - public async Task Login(LoginModel user, bool rememberMe = false) { - var User = await _userMan.FindByEmailAsync(user.email); - return await _signInMan.PasswordSignInAsync(User, user.password, rememberMe, false); + public SignInResult Login(LoginModel user, bool rememberMe = false) { + var User = _userMan.FindByEmailAsync(user.email.ToUpper()); + User.Wait(); + var res = _signInMan.PasswordSignInAsync(User.Result, user.password, rememberMe, false); + res.Wait(); + return res.Result; } [HttpPost, Route("Logout")] diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs index 4f21164..c645a27 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/ProductController.cs @@ -107,7 +107,7 @@ public class ProductController : ControllerBase { #endregion - #region Utility Methods + #region Internal Methods private async Task SaveImage(IFormFile imageFile) { string imageName = new String(Path.GetFileNameWithoutExtension(imageFile.FileName).Take(10).ToArray()).Replace(' ', '-'); imageName = imageName + DateTime.Now.ToString("yymmssfff") + Path.GetExtension(imageFile.FileName); diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs index b106e71..05919b4 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/SearchController.cs @@ -52,7 +52,7 @@ public class SearchController : Controller { #endregion - #region Private Methods + #region Internal Methods private List Search(string query, string? filterPrice, string? filterState, string? order) { List products = new(); query = query.Trim(); diff --git a/GrossesMitaines/GrossesMitainesAPI/Controllers/UserController.cs b/GrossesMitaines/GrossesMitainesAPI/Controllers/UserController.cs index 7ed6a19..e789d86 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Controllers/UserController.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Controllers/UserController.cs @@ -1,44 +1,92 @@ -using GrossesMitainesAPI.Data; +namespace GrossesMitainesAPI.Controllers; + +#region Dependencies +using GrossesMitainesAPI.Data; using GrossesMitainesAPI.Models; +using Microsoft.AspNet.Identity; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Net.Mime; + +#endregion -namespace GrossesMitainesAPI.Controllers; [EnableCors("_myAllowSpecificOrigins"), ApiController, Route("api/[controller]"), Authorize(AuthenticationSchemes = "Identity.Application", Roles = "Administrateur")] public class UserController : Controller { - private readonly UserManager _userMan; + #region DI Fields + private readonly Microsoft.AspNetCore.Identity.UserManager _userMan; private readonly SignInManager _signInMan; + private readonly InventoryContext _context; private readonly ILogger _logger; - public UserController(ILogger logger, SignInManager signin, UserManager userman) { + #endregion + + #region Ctor + public UserController(ILogger logger, + SignInManager signin, + Microsoft.AspNetCore.Identity.UserManager userman, + InventoryContext context) { _logger = logger; _signInMan = signin; _userMan = userman; + _context = context; } + #endregion + + #region API Methods [HttpPost, AllowAnonymous] - public async Task> Post(SignUpUserModel sign) { + public ActionResult Post(SignUpUserModel sign) { InventoryUser usr; try { usr = new(sign); - } catch { - return BadRequest("Erreur utilisateur"); + } catch (Exception e){ + return BadRequest($"Erreur utilisateur: {e.Message}"); } + try { usr.PasswordHash = new PasswordHasher().HashPassword(usr, sign.Password); - } catch { - return BadRequest("Erreur de mot de passe."); + } catch (Exception e){ + return BadRequest($"Erreur de mot de passe: {e.Message}"); } try { - await _userMan.CreateAsync(usr); - await _userMan.AddToRoleAsync(usr, "Client"); + var t1 = _userMan.CreateAsync(usr); + t1.Wait(); + var t2 = _userMan.AddToRoleAsync(usr, "Client"); + t2.Wait(); + } catch (Exception e) { return BadRequest(e.Message); } + return new ReturnUserViewModel(usr, "Client"); } + + [HttpGet, Route("Adresses")] + public async Task> GetAddresses(bool? all) { + IList roles; + string id; + try { // Trouver les rôles de l'utilisateur, assumer non-admin si impossible à trouver. + roles = await _userMan.GetRolesAsync(await _userMan.GetUserAsync(_signInMan.Context.User)); + } catch (Exception e) { + _logger.LogError(10, e.Message); + roles = new List(); + } + + try { + id = _signInMan.Context.User.Identity.GetUserId(); + if (all is not null && all == true && roles.Contains("Administrateur")) + return Ok(_context.Addresses.Include("AspNetUser").ToList()); + else return Ok(_context.Users.Include("Adresses").Where(x => x.Id == id).ToList()); + } catch (Exception e) { + _logger.LogError(10, e.Message); + return BadRequest(e.Message); + } + } + + #endregion } diff --git a/GrossesMitaines/GrossesMitainesAPI/Data/InventoryUser.cs b/GrossesMitaines/GrossesMitainesAPI/Data/InventoryUser.cs index 67ed060..e2882d1 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Data/InventoryUser.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Data/InventoryUser.cs @@ -10,12 +10,13 @@ public class InventoryUser : IdentityUser { public string LastName { get; set; } public List Adresses { get; set; } + public InventoryUser() { } public InventoryUser(SignUpUserModel sign) { FirstName = sign.FirstName; LastName = sign.LastName; - UserName = sign.FirstName + " " + sign.LastName; - NormalizedUserName = UserName.ToUpper(); - NormalizedEmail = Email.ToUpper(); + UserName = sign.FirstName + sign.LastName; + NormalizedUserName = (sign.FirstName + sign.LastName).ToUpper(); + NormalizedEmail = sign.Email.ToUpper(); Email = sign.Email; PhoneNumber = sign.Phone; Adresses = sign.Adresses; diff --git a/GrossesMitaines/GrossesMitainesAPI/Models/AddressModel.cs b/GrossesMitaines/GrossesMitainesAPI/Models/AddressModel.cs index 38ec3ac..ad5cb72 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Models/AddressModel.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Models/AddressModel.cs @@ -16,7 +16,7 @@ public class AddressModel { [Required, MinLength(4), MaxLength(30)] public string Country { get; set; } // Source pour regex: https://stackoverflow.com/questions/15774555/efficient-regex-for-canadian-postal-code-function - [Required, RegularExpression(@"/^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ -]?\d[ABCEGHJ-NPRSTV-Z]\d$/i")] + //[Required, RegularExpression(@"/^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ -]?\d[ABCEGHJ-NPRSTV-Z]\d$/i")] public string PostalCode { get; set; } } diff --git a/GrossesMitaines/GrossesMitainesAPI/Program.cs b/GrossesMitaines/GrossesMitainesAPI/Program.cs index a673351..91bf90d 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Program.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Program.cs @@ -31,6 +31,10 @@ builder.Services.AddIdentityCore() builder.Services.AddAuthorization(); builder.Services.AddAuthentication().AddIdentityCookies(); +builder.Services.Configure(options => { + options.User.RequireUniqueEmail = true; +}); + // Source: https://github.com/dotnet/aspnetcore/issues/9039 builder.Services.ConfigureApplicationCookie(o => { o.Events = new CookieAuthenticationEvents() { diff --git a/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs b/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs index d53ab94..e90b939 100644 --- a/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs +++ b/GrossesMitaines/GrossesMitainesAPI/Services/DatabaseCacheService.cs @@ -90,7 +90,7 @@ public class DatabaseCacheService { #endregion - #region Public Methods + #region Service Methods public bool isOk() { return _ok; } public void askForRefresh() => _needUpd = true; public void addHit(uint id) {