From 065effae3d763e7b7c96819c89b1cded69dd80ea Mon Sep 17 00:00:00 2001 From: devRaGonSa Date: Mon, 28 Apr 2025 21:42:35 +0200 Subject: [PATCH] Edicion y agregacion de direcciones --- WebVentaCoche.sln | 2 +- .../Controllers/AccountController.cs | 86 ++++ .../Controllers/AddressController .cs | 83 +++ WebVentaCoche/Controllers/CartController.cs | 94 ++++ WebVentaCoche/Controllers/HomeController.cs | 35 +- WebVentaCoche/Controllers/OrderController.cs | 9 +- .../Controllers/ProductsController.cs | 4 - WebVentaCoche/Controllers/UserController.cs | 5 +- .../DataBase/ApplicationDbContext.cs | 12 + WebVentaCoche/Helpers/CartSessionHelper.cs | 76 +++ WebVentaCoche/Helpers/IUserHelper.cs | 1 + WebVentaCoche/Helpers/MappingProfile.cs | 18 + WebVentaCoche/Helpers/UserHelper.cs | 5 + ...424122629_ChangePriceToDecimal.Designer.cs | 418 +++++++++++++++ .../20250424122629_ChangePriceToDecimal.cs | 22 + ...0250428182320_AddAddressEntity.Designer.cs | 478 ++++++++++++++++++ .../20250428182320_AddAddressEntity.cs | 50 ++ .../ApplicationDbContextModelSnapshot.cs | 60 +++ WebVentaCoche/Models/Address.cs | 32 ++ WebVentaCoche/Models/Cart.cs | 19 + WebVentaCoche/Models/CartProduct.cs | 9 + WebVentaCoche/Models/CartViewModel.cs | 8 + WebVentaCoche/Models/User.cs | 1 + WebVentaCoche/Program.cs | 98 ++-- WebVentaCoche/ViewModels/SecurityViewModel.cs | 32 ++ .../ViewModels/UserDetailsViewModel.cs | 37 ++ WebVentaCoche/Views/Account/Addresses.cshtml | 130 +++++ WebVentaCoche/Views/Account/Details.cshtml | 48 ++ WebVentaCoche/Views/Account/Security.cshtml | 8 + WebVentaCoche/Views/Account/Settings.cshtml | 16 + WebVentaCoche/Views/Cart/Index.cshtml | 97 ++++ .../Views/Home/ProductsDetailsHome.cshtml | 90 +++- .../Views/Shared/_AccountLayout.cshtml | 39 ++ .../Views/Shared/_AccountLayout.cshtml.css | 48 ++ WebVentaCoche/Views/Shared/_Layout.cshtml | 57 ++- WebVentaCoche/WebVentaCoche.csproj | 1 + 36 files changed, 2132 insertions(+), 96 deletions(-) create mode 100644 WebVentaCoche/Controllers/AccountController.cs create mode 100644 WebVentaCoche/Controllers/AddressController .cs create mode 100644 WebVentaCoche/Controllers/CartController.cs create mode 100644 WebVentaCoche/Helpers/CartSessionHelper.cs create mode 100644 WebVentaCoche/Helpers/MappingProfile.cs create mode 100644 WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.Designer.cs create mode 100644 WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.cs create mode 100644 WebVentaCoche/Migrations/20250428182320_AddAddressEntity.Designer.cs create mode 100644 WebVentaCoche/Migrations/20250428182320_AddAddressEntity.cs create mode 100644 WebVentaCoche/Models/Address.cs create mode 100644 WebVentaCoche/Models/Cart.cs create mode 100644 WebVentaCoche/Models/CartProduct.cs create mode 100644 WebVentaCoche/Models/CartViewModel.cs create mode 100644 WebVentaCoche/ViewModels/SecurityViewModel.cs create mode 100644 WebVentaCoche/ViewModels/UserDetailsViewModel.cs create mode 100644 WebVentaCoche/Views/Account/Addresses.cshtml create mode 100644 WebVentaCoche/Views/Account/Details.cshtml create mode 100644 WebVentaCoche/Views/Account/Security.cshtml create mode 100644 WebVentaCoche/Views/Account/Settings.cshtml create mode 100644 WebVentaCoche/Views/Cart/Index.cshtml create mode 100644 WebVentaCoche/Views/Shared/_AccountLayout.cshtml create mode 100644 WebVentaCoche/Views/Shared/_AccountLayout.cshtml.css diff --git a/WebVentaCoche.sln b/WebVentaCoche.sln index 4201103..b3bc2bf 100644 --- a/WebVentaCoche.sln +++ b/WebVentaCoche.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebVentaCoche", "WebVentaCoche\WebVentaCoche.csproj", "{95AAD8ED-D4F9-4972-81EB-36FCD0FA2C7D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebVentaCoche", "WebVentaCoche\WebVentaCoche.csproj", "{95AAD8ED-D4F9-4972-81EB-36FCD0FA2C7D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/WebVentaCoche/Controllers/AccountController.cs b/WebVentaCoche/Controllers/AccountController.cs new file mode 100644 index 0000000..da16343 --- /dev/null +++ b/WebVentaCoche/Controllers/AccountController.cs @@ -0,0 +1,86 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; +using System.Threading.Tasks; +using WebVentaCoche.DataBase; +using WebVentaCoche.Helpers; +using WebVentaCoche.ViewModels; +using Microsoft.EntityFrameworkCore; + +namespace WebVentaCoche.Controllers +{ + [Authorize] + public class AccountController : Controller + { + private readonly IUserHelper _userHelper; + private readonly ApplicationDbContext _context; + + public AccountController(IUserHelper userHelper, ApplicationDbContext context) + { + _userHelper = userHelper; + _context = context; + } + + //GET:/Account/Settings + [HttpGet] + public async Task Settings() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var user = await _userHelper.GetUserByIdAsync(userId); + if (user == null) return NotFound(); + + var addresses = await _context.Addresses + .Where(a => a.UserId == userId) + .ToListAsync(); + + var vm = new UserDetailsViewModel + { + Id = user.Id, + Name = user.Name, + Surname = user.Surname, + Email = user.Email, + PhoneNumber = user.PhoneNumber, + UserType = user.UserType, + Addresses = addresses.Select(a => new AddressViewModel + { + Id = a.Id, + Street = a.Street, + City = a.City, + State = a.State, + ZipCode = a.ZipCode, + Country = a.Country + }).ToList() + }; + + return View(vm); + } + + //GET:/Account/Addresses + [HttpGet] + public async Task Addresses() + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var addresses = await _context.Addresses.Where(a => a.UserId == userId).ToListAsync(); + + var vm = addresses.Select(a => new AddressViewModel + { + Id = a.Id, + Street = a.Street, + City = a.City, + State = a.State, + ZipCode = a.ZipCode, + Country = a.Country + }).ToList(); + + return View(vm); + } + + //GET:/Account/Security + [HttpGet] + public IActionResult Security() + { + //TODO:VM con políticas de contraseña, etc. + return View(); + } + } +} diff --git a/WebVentaCoche/Controllers/AddressController .cs b/WebVentaCoche/Controllers/AddressController .cs new file mode 100644 index 0000000..dc7018e --- /dev/null +++ b/WebVentaCoche/Controllers/AddressController .cs @@ -0,0 +1,83 @@ +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using WebVentaCoche.DataBase; +using WebVentaCoche.Models; +using WebVentaCoche.ViewModels; + +namespace WebVentaCoche.Controllers +{ + [Authorize] + public class AddressController : Controller + { + private readonly ApplicationDbContext _context; + + public AddressController(ApplicationDbContext context) + { + _context = context; + } + + //POST:/Address/Create + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Create(AddressViewModel input) + { + if (!ModelState.IsValid) + return RedirectToAction("Addresses", "Account"); + + var entity = new Address + { + Street = input.Street, + City = input.City, + State = input.State, + ZipCode = input.ZipCode, + Country = input.Country, + UserId = User.FindFirstValue(ClaimTypes.NameIdentifier)! + }; + + _context.Addresses.Add(entity); + await _context.SaveChangesAsync(); + return RedirectToAction("Addresses", "Account"); + } + + //POST:/Address/Edit/{id} + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Edit(int id, Address model) + { + if (id != model.Id) + return BadRequest(); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var address = await _context.Addresses.FindAsync(id); + + if (address == null || address.UserId != userId) + return NotFound(); + + address.Street = model.Street; + address.City = model.City; + address.State = model.State; + address.ZipCode = model.ZipCode; + address.Country = model.Country; + + await _context.SaveChangesAsync(); + return RedirectToAction("Addresses", "Account"); + } + + //POST: /Address/Delete/{id} + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(int id) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var address = await _context.Addresses.FindAsync(id); + if (address == null || address.UserId != userId) + return NotFound(); + + _context.Addresses.Remove(address); + await _context.SaveChangesAsync(); + return RedirectToAction("Addresses", "Account"); + } + } +} diff --git a/WebVentaCoche/Controllers/CartController.cs b/WebVentaCoche/Controllers/CartController.cs new file mode 100644 index 0000000..38b98b2 --- /dev/null +++ b/WebVentaCoche/Controllers/CartController.cs @@ -0,0 +1,94 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using WebVentaCoche.DataBase; +using WebVentaCoche.Helpers; +using WebVentaCoche.Models; +using System.Linq; + +namespace WebVentaCoche.Controllers +{ + public class CartController : Controller + { + private readonly ApplicationDbContext _context; + + public CartController(ApplicationDbContext context) + { + _context = context; + } + + // GET: /Cart/Index + // Muestra la lista de productos del carrito y el total. + public IActionResult Index() + { + // Diccionario de IDs -> Cantidades desde sesión (o cualquier fuente) + var itemsDict = CartSessionHelper.GetCartItems(HttpContext.Session); + + // Crear tu ViewModel + var viewModel = new CartViewModel(); + + foreach (var kvp in itemsDict) + { + var productId = kvp.Key; + var amount = kvp.Value; + var product = _context.Products.Find(productId); + + if (product != null) + { + viewModel.Products.Add(new CartProduct + { + Product = product, + Amount = amount + }); + } + } + + return View(viewModel); + } + + // POST: /Cart/AddProductToCart + // Añade el producto al carrito y devuelve un JSON con el nuevo contador + [HttpPost] + public IActionResult AddProductToCart(int id) + { + var product = _context.Products.Find(id); + if (product == null) + { + return NotFound(); + } + + // Añade a la sesión + CartSessionHelper.AddToCart(HttpContext.Session, id); + + // Devuelve la cuenta actualizada para actualizar el badge + var productIds = CartSessionHelper.GetCartItems(HttpContext.Session); + int count = productIds.Count; + + return Json(new { success = true, cartCount = count }); + } + + // POST: /Cart/Remove + // Elimina un producto según su ID + [HttpPost] + public IActionResult Remove(int id) + { + CartSessionHelper.RemoveFromCart(HttpContext.Session, id); + return RedirectToAction("Index"); + } + + // POST: /Cart/Clear + // Vacía todo el carrito (la sesión) + [HttpPost] + public IActionResult Clear() + { + CartSessionHelper.ClearCart(HttpContext.Session); + return RedirectToAction("Index"); + } + + // GET: /Cart/Checkout + // (Opcional) Muestra un formulario o datos para la compra/pago. + public IActionResult Checkout() + { + return View(); + } + } +} diff --git a/WebVentaCoche/Controllers/HomeController.cs b/WebVentaCoche/Controllers/HomeController.cs index 118059c..966fa37 100644 --- a/WebVentaCoche/Controllers/HomeController.cs +++ b/WebVentaCoche/Controllers/HomeController.cs @@ -1,6 +1,9 @@ +// Controllers/HomeController.cs +using System.Diagnostics; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using System.Diagnostics; +using Microsoft.Extensions.Logging; using WebVentaCoche.DataBase; using WebVentaCoche.Models; @@ -11,45 +14,41 @@ namespace WebVentaCoche.Controllers private readonly ILogger _logger; private readonly ApplicationDbContext _context; - public HomeController(ILogger logger, ApplicationDbContext context) { _logger = logger; _context = context; } - public IActionResult Index() - { - return View(); - } + public IActionResult Index() => View(); - public IActionResult Privacy() - { - return View(); - } + public IActionResult Privacy() => View(); [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() - { - return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); - } + => View(new ErrorViewModel + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier + }); + //GET:/Home/ProductsHome public async Task ProductsHome() { - var products = await _context.Products.ToListAsync(); + var products = await _context.Products.AsNoTracking().ToListAsync(); return View(products); } + //GET:/Home/ProductsDetailsHome/5 public async Task ProductsDetailsHome(int id) { + if (id <= 0) + return BadRequest(); + var product = await _context.Products.FindAsync(id); - if (product == null) - { + if (product == null) return NotFound(); - } return View(product); } - } } diff --git a/WebVentaCoche/Controllers/OrderController.cs b/WebVentaCoche/Controllers/OrderController.cs index 3d448ca..881bf3b 100644 --- a/WebVentaCoche/Controllers/OrderController.cs +++ b/WebVentaCoche/Controllers/OrderController.cs @@ -16,7 +16,7 @@ namespace WebVentaCoche.Controllers _context = context; } - // GET: Order/Index + //GET:Order/Index public async Task Index() { var orders = await _context.Orders @@ -25,7 +25,7 @@ namespace WebVentaCoche.Controllers return View(orders); } - // GET: Order/Details/5 + //GET:Order/Details/5 public async Task Details(int? id) { if (id == null) @@ -45,7 +45,7 @@ namespace WebVentaCoche.Controllers return View(order); } - // GET: Order/Edit/5 + //GET:Order/Edit/{id} public async Task Edit(int? id) { if (id == null) @@ -62,7 +62,7 @@ namespace WebVentaCoche.Controllers return View(order); } - // POST: Order/Edit/5 + //POST:Order/Edit/{id} [HttpPost] [ValidateAntiForgeryToken] public async Task Edit(int id, Order order) @@ -97,7 +97,6 @@ namespace WebVentaCoche.Controllers return View(order); } - // Método auxiliar para verificar si la orden existe private bool OrderExists(int id) { return _context.Orders.Any(e => e.Id == id); diff --git a/WebVentaCoche/Controllers/ProductsController.cs b/WebVentaCoche/Controllers/ProductsController.cs index bd2b101..cf3ff5d 100644 --- a/WebVentaCoche/Controllers/ProductsController.cs +++ b/WebVentaCoche/Controllers/ProductsController.cs @@ -14,7 +14,6 @@ namespace WebVentaCoche.Controllers _context = context; } - // Lista de productos paginada public async Task Index(int page = 1, int pageSize = 10) { var products = await _context.Products @@ -28,7 +27,6 @@ namespace WebVentaCoche.Controllers return View(products); } - // Ver detalles de un producto public async Task Details(int id) { var product = await _context.Products.FindAsync(id); @@ -39,14 +37,12 @@ namespace WebVentaCoche.Controllers return View(product); } - // Página para añadir producto [HttpGet] public IActionResult Create() { return View(); } - // Procesar el POST para añadir producto [HttpPost] public async Task Create(Product product, IFormFile Image) { diff --git a/WebVentaCoche/Controllers/UserController.cs b/WebVentaCoche/Controllers/UserController.cs index 5e72e28..277722c 100644 --- a/WebVentaCoche/Controllers/UserController.cs +++ b/WebVentaCoche/Controllers/UserController.cs @@ -137,7 +137,7 @@ namespace WebVentaCoche.Controllers SenConfirmationEmail(user, token, confirmationLink); // Redirige al usuario a la página de verificación de email TempData["Error"] = "Debes confirmar tu email antes de iniciar sesión."; - + return RedirectToAction("Index", "Home"); } @@ -225,5 +225,8 @@ namespace WebVentaCoche.Controllers }; } + + + } } diff --git a/WebVentaCoche/DataBase/ApplicationDbContext.cs b/WebVentaCoche/DataBase/ApplicationDbContext.cs index 045278b..225c57e 100644 --- a/WebVentaCoche/DataBase/ApplicationDbContext.cs +++ b/WebVentaCoche/DataBase/ApplicationDbContext.cs @@ -15,5 +15,17 @@ namespace WebVentaCoche.DataBase public DbSet Users { get; set; } public DbSet Orders { get; set; } public DbSet OrderDetails { get; set; } + public DbSet
Addresses { get; set; } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity
() + .HasOne(a => a.User) + .WithMany(u => u.Addresses) + .HasForeignKey(a => a.UserId) + .OnDelete(DeleteBehavior.Cascade); + } + } } diff --git a/WebVentaCoche/Helpers/CartSessionHelper.cs b/WebVentaCoche/Helpers/CartSessionHelper.cs new file mode 100644 index 0000000..d30b471 --- /dev/null +++ b/WebVentaCoche/Helpers/CartSessionHelper.cs @@ -0,0 +1,76 @@ +using Microsoft.AspNetCore.Http; +using System.Text.Json; + +namespace WebVentaCoche.Helpers +{ + public static class CartSessionHelper + { + private const string CartSessionKey = "CartItems"; + + //Obtiene el diccionario: ProductId -> Quantity + public static Dictionary GetCartItems(ISession session) + { + var json = session.GetString(CartSessionKey); + if (string.IsNullOrEmpty(json)) + return new Dictionary(); + + return JsonSerializer.Deserialize>(json) + ?? new Dictionary(); + } + + //Guarda el diccionario en la sesión + private static void SaveCartItems(ISession session, Dictionary items) + { + var json = JsonSerializer.Serialize(items); + session.SetString(CartSessionKey, json); + } + + //Añade un producto al carrito + public static void AddToCart(ISession session, int productId) + { + var items = GetCartItems(session); + + if (items.ContainsKey(productId)) + items[productId]++; + else + items[productId] = 1; + + SaveCartItems(session, items); + } + + //Actualiza la cantidad de un producto. Si la cantidad es 0 o menor, lo elimina + public static void UpdateQuantity(ISession session, int productId, int quantity) + { + var items = GetCartItems(session); + if (items.ContainsKey(productId)) + { + if (quantity <= 0) + { + items.Remove(productId); + } + else + { + items[productId] = quantity; + } + SaveCartItems(session, items); + } + } + + //Elimina un producto del carrito + public static void RemoveFromCart(ISession session, int productId) + { + var items = GetCartItems(session); + if (items.ContainsKey(productId)) + { + items.Remove(productId); + } + SaveCartItems(session, items); + } + + //Vacía el carrito entero + public static void ClearCart(ISession session) + { + session.Remove(CartSessionKey); + } + } +} diff --git a/WebVentaCoche/Helpers/IUserHelper.cs b/WebVentaCoche/Helpers/IUserHelper.cs index 0378189..cf28b56 100644 --- a/WebVentaCoche/Helpers/IUserHelper.cs +++ b/WebVentaCoche/Helpers/IUserHelper.cs @@ -16,5 +16,6 @@ namespace WebVentaCoche.Helpers Task GenerateEmailConfirmationTokenAsync(User user); Task ConfirmEmailAsync(User user, string token); Task IsEmailConfirmedAsync(User user); + Task GetUserByIdAsync(string id); } } diff --git a/WebVentaCoche/Helpers/MappingProfile.cs b/WebVentaCoche/Helpers/MappingProfile.cs new file mode 100644 index 0000000..3dbb009 --- /dev/null +++ b/WebVentaCoche/Helpers/MappingProfile.cs @@ -0,0 +1,18 @@ +using AutoMapper; +using WebVentaCoche.Models; +using WebVentaCoche.ViewModels; + +namespace WebVentaCoche.Helpers +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap() + .ForMember(dest => dest.Addresses, + opt => opt.MapFrom(src => src.Addresses)); + + CreateMap(); + } + } +} diff --git a/WebVentaCoche/Helpers/UserHelper.cs b/WebVentaCoche/Helpers/UserHelper.cs index d361f82..6335ddf 100644 --- a/WebVentaCoche/Helpers/UserHelper.cs +++ b/WebVentaCoche/Helpers/UserHelper.cs @@ -75,5 +75,10 @@ namespace WebVentaCoche.Helpers { return await _userManager.IsEmailConfirmedAsync(user); } + public async Task GetUserByIdAsync(string id) + { + return await _userManager.FindByIdAsync(id); + } + } } diff --git a/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.Designer.cs b/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.Designer.cs new file mode 100644 index 0000000..d0c4b81 --- /dev/null +++ b/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.Designer.cs @@ -0,0 +1,418 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using WebVentaCoche.DataBase; + +#nullable disable + +namespace WebVentaCoche.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250424122629_ChangePriceToDecimal")] + partial class ChangePriceToDecimal + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("ShippedDate") + .HasColumnType("datetime2"); + + b.Property("ShippingAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("LongDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("ShortDescription") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.HasOne("WebVentaCoche.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b => + { + b.HasOne("WebVentaCoche.Models.Order", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebVentaCoche.Models.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.Navigation("OrderDetails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.cs b/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.cs new file mode 100644 index 0000000..e4785a1 --- /dev/null +++ b/WebVentaCoche/Migrations/20250424122629_ChangePriceToDecimal.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WebVentaCoche.Migrations +{ + /// + public partial class ChangePriceToDecimal : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.Designer.cs b/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.Designer.cs new file mode 100644 index 0000000..4a7a183 --- /dev/null +++ b/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.Designer.cs @@ -0,0 +1,478 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using WebVentaCoche.DataBase; + +#nullable disable + +namespace WebVentaCoche.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250428182320_AddAddressEntity")] + partial class AddAddressEntity + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ZipCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OrderDate") + .HasColumnType("datetime2"); + + b.Property("ShippedDate") + .HasColumnType("datetime2"); + + b.Property("ShippingAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("decimal(18,2)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("OrderId") + .HasColumnType("int"); + + b.Property("ProductId") + .HasColumnType("int"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("UnitPrice") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderDetails"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("LongDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.Property("ShortDescription") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Surname") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("UserType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("WebVentaCoche.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Address", b => + { + b.HasOne("WebVentaCoche.Models.User", "User") + .WithMany("Addresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.HasOne("WebVentaCoche.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b => + { + b.HasOne("WebVentaCoche.Models.Order", "Order") + .WithMany("OrderDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("WebVentaCoche.Models.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.Order", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("WebVentaCoche.Models.User", b => + { + b.Navigation("Addresses"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.cs b/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.cs new file mode 100644 index 0000000..5aab98b --- /dev/null +++ b/WebVentaCoche/Migrations/20250428182320_AddAddressEntity.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WebVentaCoche.Migrations +{ + /// + public partial class AddAddressEntity : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Addresses", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Street = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + City = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + State = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + ZipCode = table.Column(type: "nvarchar(20)", maxLength: 20, nullable: false), + Country = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Addresses", x => x.Id); + table.ForeignKey( + name: "FK_Addresses_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Addresses_UserId", + table: "Addresses", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Addresses"); + } + } +} diff --git a/WebVentaCoche/Migrations/ApplicationDbContextModelSnapshot.cs b/WebVentaCoche/Migrations/ApplicationDbContextModelSnapshot.cs index 2c3098e..066098c 100644 --- a/WebVentaCoche/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/WebVentaCoche/Migrations/ApplicationDbContextModelSnapshot.cs @@ -155,6 +155,50 @@ namespace WebVentaCoche.Migrations b.ToTable("AspNetUserTokens", (string)null); }); + modelBuilder.Entity("WebVentaCoche.Models.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("City") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Country") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Street") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("ZipCode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Addresses"); + }); + modelBuilder.Entity("WebVentaCoche.Models.Order", b => { b.Property("Id") @@ -375,6 +419,17 @@ namespace WebVentaCoche.Migrations .IsRequired(); }); + modelBuilder.Entity("WebVentaCoche.Models.Address", b => + { + b.HasOne("WebVentaCoche.Models.User", "User") + .WithMany("Addresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("WebVentaCoche.Models.Order", b => { b.HasOne("WebVentaCoche.Models.User", "User") @@ -409,6 +464,11 @@ namespace WebVentaCoche.Migrations { b.Navigation("OrderDetails"); }); + + modelBuilder.Entity("WebVentaCoche.Models.User", b => + { + b.Navigation("Addresses"); + }); #pragma warning restore 612, 618 } } diff --git a/WebVentaCoche/Models/Address.cs b/WebVentaCoche/Models/Address.cs new file mode 100644 index 0000000..7de1d72 --- /dev/null +++ b/WebVentaCoche/Models/Address.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace WebVentaCoche.Models +{ + public class Address + { + [Key] + public int Id { get; set; } + + [Required, StringLength(100)] + public string Street { get; set; } + + [Required, StringLength(50)] + public string City { get; set; } + + [StringLength(50)] + public string State { get; set; } + + [Required, StringLength(20)] + public string ZipCode { get; set; } + + [Required, StringLength(50)] + public string Country { get; set; } + + [Required] + public string UserId { get; set; } + + [ForeignKey(nameof(UserId))] + public User User { get; set; } + } +} diff --git a/WebVentaCoche/Models/Cart.cs b/WebVentaCoche/Models/Cart.cs new file mode 100644 index 0000000..072fe47 --- /dev/null +++ b/WebVentaCoche/Models/Cart.cs @@ -0,0 +1,19 @@ +namespace WebVentaCoche.Models +{ + public class Cart + { + public int Id { get; set; } + + public string? UserId { get; set; } + + public List Products { get; set; } = new List(); + + public decimal Total + { + get + { + return Products.Sum(p => p.Price); + } + } + } +} diff --git a/WebVentaCoche/Models/CartProduct.cs b/WebVentaCoche/Models/CartProduct.cs new file mode 100644 index 0000000..35fb4ab --- /dev/null +++ b/WebVentaCoche/Models/CartProduct.cs @@ -0,0 +1,9 @@ +namespace WebVentaCoche.Models +{ + public class CartProduct + { + public Product Product { get; set; } + public int Amount { get; set; } + public decimal Subtotal => Product.Price * Amount; + } +} diff --git a/WebVentaCoche/Models/CartViewModel.cs b/WebVentaCoche/Models/CartViewModel.cs new file mode 100644 index 0000000..05e422b --- /dev/null +++ b/WebVentaCoche/Models/CartViewModel.cs @@ -0,0 +1,8 @@ +namespace WebVentaCoche.Models +{ + public class CartViewModel + { + public List Products { get; set; } = new List(); + public decimal Total => Products.Sum(p => p.Subtotal); + } +} diff --git a/WebVentaCoche/Models/User.cs b/WebVentaCoche/Models/User.cs index 1dee853..fc99c76 100644 --- a/WebVentaCoche/Models/User.cs +++ b/WebVentaCoche/Models/User.cs @@ -8,5 +8,6 @@ namespace WebVentaCoche.Models public string Name { get; set; } public string Surname { get; set; } public UserType UserType { get; set; } + public ICollection
Addresses { get; set; } = new List
(); } } \ No newline at end of file diff --git a/WebVentaCoche/Program.cs b/WebVentaCoche/Program.cs index e78e722..1687ff3 100644 --- a/WebVentaCoche/Program.cs +++ b/WebVentaCoche/Program.cs @@ -1,8 +1,10 @@ +// Program.cs using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.Text; +using AutoMapper; using WebVentaCoche.DataBase; using WebVentaCoche.Helpers; using WebVentaCoche.Models; @@ -10,77 +12,95 @@ using WebVentaCoche.Services; var builder = WebApplication.CreateBuilder(args); -// Add services to the container. -builder.Services.AddControllersWithViews(); - +//Configuración de Entity Framework + SQL Server builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")) + .EnableSensitiveDataLogging() +); -builder.Services.AddScoped(); -builder.Services.AddTransient(); -builder.Services.AddIdentity(x => +//Identity + JWT + Helpers +builder.Services.AddIdentity(opts => { - x.User.RequireUniqueEmail = true; - x.Password.RequireDigit = false; - x.Password.RequiredUniqueChars = 0; - x.Password.RequireLowercase = false; - x.Password.RequireNonAlphanumeric = false; - x.Password.RequireUppercase = false; + opts.User.RequireUniqueEmail = true; + opts.Password.RequireDigit = false; + opts.Password.RequireLowercase = false; + opts.Password.RequireNonAlphanumeric = false; + opts.Password.RequireUppercase = false; + opts.Password.RequiredUniqueChars = 0; }) -.AddEntityFrameworkStores() -.AddDefaultTokenProviders(); + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) -.AddJwtBearer(x => x.TokenValidationParameters = new TokenValidationParameters -{ - ValidateIssuer = false, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:SecretToken"]!)), - ClockSkew = TimeSpan.Zero -}); + .AddJwtBearer(opts => + { + opts.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.UTF8.GetBytes(builder.Configuration["JWT:SecretToken"]!) + ), + ClockSkew = TimeSpan.Zero + }; + }); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddTransient(); builder.Services.AddTransient(); +//MVC + Session + Memoria +builder.Services.AddControllersWithViews(); builder.Services.AddMemoryCache(); +builder.Services.AddSession(opts => +{ + opts.IdleTimeout = TimeSpan.FromMinutes(30); + opts.Cookie.HttpOnly = true; + // opts.Cookie.SecurePolicy = CookieSecurePolicy.Always; //produccion +}); +builder.Services.AddAutoMapper(cfg => +{ + cfg.AddProfile(); +}); var app = builder.Build(); -SeedData(app); - -void SeedData(WebApplication app) +//Pipeline de middleware +if (app.Environment.IsDevelopment()) { - IServiceScopeFactory? scopedFactory = app.Services.GetService(); - - using (IServiceScope? scope = scopedFactory!.CreateScope()) - { - SeedDb? service = scope.ServiceProvider.GetService(); - service!.SeedAsync().Wait(); - } + // En dev vemos la excepción completa y stack-trace en el navegador + app.UseDeveloperExceptionPage(); } -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) +else { app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } - app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); +app.UseSession(); + app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}"); + pattern: "{controller=Home}/{action=Index}/{id?}" +); + +// 5. (Opcional) Seed de datos +using (var scope = app.Services.CreateScope()) +{ + var seeder = scope.ServiceProvider.GetRequiredService(); + seeder.SeedAsync().Wait(); +} app.Run(); diff --git a/WebVentaCoche/ViewModels/SecurityViewModel.cs b/WebVentaCoche/ViewModels/SecurityViewModel.cs new file mode 100644 index 0000000..f5e2707 --- /dev/null +++ b/WebVentaCoche/ViewModels/SecurityViewModel.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace WebVentaCoche.ViewModels +{ + public class SecurityViewModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Contraseña actual")] + public string CurrentPassword { get; set; } = null!; + + [Required] + [StringLength(100, ErrorMessage = "La {0} debe tener al menos {2} y como máximo {1} caracteres.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Nueva contraseña")] + public string NewPassword { get; set; } = null!; + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Confirmar nueva contraseña")] + [Compare("NewPassword", ErrorMessage = "La nueva contraseña y la confirmación no coinciden.")] + public string ConfirmNewPassword { get; set; } = null!; + + [Display(Name = "Autenticación multifactor habilitada")] + public bool Is2faEnabled { get; set; } + + //Opcional: codigo de verificación para MFA + [DataType(DataType.Text)] + [Display(Name = "Código de verificación MFA")] + public string? TwoFactorCode { get; set; } + } +} diff --git a/WebVentaCoche/ViewModels/UserDetailsViewModel.cs b/WebVentaCoche/ViewModels/UserDetailsViewModel.cs new file mode 100644 index 0000000..e5789ef --- /dev/null +++ b/WebVentaCoche/ViewModels/UserDetailsViewModel.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using WebVentaCoche.Enums; + +namespace WebVentaCoche.ViewModels +{ + public class UserDetailsViewModel + { + public string Id { get; set; } = null!; + + public string Name { get; set; } = null!; + + public string Surname { get; set; } = null!; + + public string Email { get; set; } = null!; + + public string PhoneNumber { get; set; } = null!; + + public UserType UserType { get; set; } + + public List Addresses { get; set; } = new(); + } + + public class AddressViewModel + { + public int? Id { get; set; } + + public string Street { get; set; } = null!; + + public string City { get; set; } = null!; + + public string State { get; set; } = null!; + + public string ZipCode { get; set; } = null!; + + public string Country { get; set; } = null!; + } +} diff --git a/WebVentaCoche/Views/Account/Addresses.cshtml b/WebVentaCoche/Views/Account/Addresses.cshtml new file mode 100644 index 0000000..141fc5e --- /dev/null +++ b/WebVentaCoche/Views/Account/Addresses.cshtml @@ -0,0 +1,130 @@ +@model IEnumerable + +@{ + Layout = "_AccountLayout"; + var addresses = Model.ToList(); +} + +

Mis Direcciones

+ +@if (!addresses.Any()) +{ +

No tienes direcciones registradas.

+} + +
+ +
+ +@if (addresses.Any()) +{ +
+ @foreach (var addr in addresses) + { +
+
+

+ @addr.Street
+ @addr.City, @addr.State @addr.ZipCode
+ @addr.Country +

+ +
+
+ } +
+} + + + +@section Scripts { + +} diff --git a/WebVentaCoche/Views/Account/Details.cshtml b/WebVentaCoche/Views/Account/Details.cshtml new file mode 100644 index 0000000..2ca1975 --- /dev/null +++ b/WebVentaCoche/Views/Account/Details.cshtml @@ -0,0 +1,48 @@ +@model WebVentaCoche.ViewModels.UserDetailsViewModel + +@{ + ViewData["Title"] = "Detalles de Cuenta"; +} + +
+

Detalles del Usuario

+ +
+
+
+
@Model.Name @Model.Surname
+
+
Email
+
@Model.Email
+
Teléfono
+
@Model.PhoneNumber
+
Tipo
+
@Model.UserType
+
+
+
+
+ +

Direcciones

+ @if (Model.Addresses.Any()) + { +
+
+ @foreach (var addr in Model.Addresses) + { +
+

+ @addr.Street
+ @addr.City, @addr.State @addr.ZipCode
+ @addr.Country +

+
+ } +
+
+ } + else + { +

No has agregado ninguna dirección todavía.

+ } +
diff --git a/WebVentaCoche/Views/Account/Security.cshtml b/WebVentaCoche/Views/Account/Security.cshtml new file mode 100644 index 0000000..ef0764a --- /dev/null +++ b/WebVentaCoche/Views/Account/Security.cshtml @@ -0,0 +1,8 @@ +@model WebVentaCoche.ViewModels.SecurityViewModel +@{ + Layout = "_AccountLayout"; +} + +

Seguridad de la Cuenta

+

Aquí podrás cambiar tu contraseña, configurar MFA, etc.

+ diff --git a/WebVentaCoche/Views/Account/Settings.cshtml b/WebVentaCoche/Views/Account/Settings.cshtml new file mode 100644 index 0000000..4745115 --- /dev/null +++ b/WebVentaCoche/Views/Account/Settings.cshtml @@ -0,0 +1,16 @@ +@model WebVentaCoche.ViewModels.UserDetailsViewModel +@{ + Layout = "_AccountLayout"; +} + +

Mi Cuenta

+
+
Nombre
+
@Model.Name @Model.Surname
+
Email
+
@Model.Email
+
Teléfono
+
@Model.PhoneNumber
+
Tipo
+
@Model.UserType
+
diff --git a/WebVentaCoche/Views/Cart/Index.cshtml b/WebVentaCoche/Views/Cart/Index.cshtml new file mode 100644 index 0000000..ea882dd --- /dev/null +++ b/WebVentaCoche/Views/Cart/Index.cshtml @@ -0,0 +1,97 @@ +@model WebVentaCoche.Models.CartViewModel + +@{ + ViewData["Title"] = "Carrito de Compras"; +} + +
+

Carrito

+ + @if (Model.Products == null || !Model.Products.Any()) + { +

No hay productos en tu carrito.

+ } + else + { +

Productos en el carrito: @Model.Products.Count

+
+ + + + + + + + + + + + + + @foreach (var cartItem in Model.Products) + { + + + + + + + + + + + + + + } + +
ImagenProductoPrecioCantidadSubtotal
+ @if (!string.IsNullOrEmpty(cartItem.Product.ImagePath)) + { + @cartItem.Product.Name + } + else + { + Imagen no disponible + } + @cartItem.Product.Name@cartItem.Product.Price € +
+ + + +
+
@cartItem.Subtotal € +
+ + +
+
+ +
+

Total: @Model.Total €

+ +
+
+ +
+ + + Proceder al Pago + +
+
+ } +
diff --git a/WebVentaCoche/Views/Home/ProductsDetailsHome.cshtml b/WebVentaCoche/Views/Home/ProductsDetailsHome.cshtml index 2bb6057..06bf80d 100644 --- a/WebVentaCoche/Views/Home/ProductsDetailsHome.cshtml +++ b/WebVentaCoche/Views/Home/ProductsDetailsHome.cshtml @@ -4,19 +4,83 @@

@Model.Name

-
-

@Model.LongDescription

-

Precio: @Model.Price €

-
-
- @if (!string.IsNullOrEmpty(Model.ImagePath)) - { - @Model.Name - } - else - { - Imagen no disponible - } +
+
+
+
+
+

@Model.LongDescription

+
+

Precio: @Model.Price €

+ +
+ + +
+
+
+
+
+ @if (!string.IsNullOrEmpty(Model.ImagePath)) + { + @Model.Name + } + else + { + Imagen no disponible + } +
+
+
+ +@section Scripts { + +} + diff --git a/WebVentaCoche/Views/Shared/_AccountLayout.cshtml b/WebVentaCoche/Views/Shared/_AccountLayout.cshtml new file mode 100644 index 0000000..23d4798 --- /dev/null +++ b/WebVentaCoche/Views/Shared/_AccountLayout.cshtml @@ -0,0 +1,39 @@ +@using System.Security.Claims +@inject IHttpContextAccessor HttpContextAccessor + +@{ + Layout = "_Layout"; + + var userId = HttpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + string currentAction = ViewContext.RouteData.Values["action"]?.ToString() ?? ""; +} + +
+
+ + +
+ @RenderBody() +
+
+
+ +@RenderSection("Scripts", required: false) diff --git a/WebVentaCoche/Views/Shared/_AccountLayout.cshtml.css b/WebVentaCoche/Views/Shared/_AccountLayout.cshtml.css new file mode 100644 index 0000000..c187c02 --- /dev/null +++ b/WebVentaCoche/Views/Shared/_AccountLayout.cshtml.css @@ -0,0 +1,48 @@ +/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification +for details on configuring this project to bundle and minify static web assets. */ + +a.navbar-brand { + white-space: normal; + text-align: center; + word-break: break-all; +} + +a { + color: #0077cc; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.border-top { + border-top: 1px solid #e5e5e5; +} +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} + +button.accept-policy { + font-size: 1rem; + line-height: inherit; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + white-space: nowrap; + line-height: 60px; +} diff --git a/WebVentaCoche/Views/Shared/_Layout.cshtml b/WebVentaCoche/Views/Shared/_Layout.cshtml index 670ee5b..dae5510 100644 --- a/WebVentaCoche/Views/Shared/_Layout.cshtml +++ b/WebVentaCoche/Views/Shared/_Layout.cshtml @@ -1,21 +1,34 @@ -@{ +@using WebVentaCoche.Helpers +@using WebVentaCoche.Models +@using System.Security.Claims +@inject IHttpContextAccessor HttpContextAccessor + + +@{ + // Determina si es la página de gestión para ajustar la clase body var isGestionPage = Context.Request.Path.ToString().StartsWith("/Products"); + + // Obtener lista de IDs de productos almacenados en sesión + var cartItemIds = CartSessionHelper.GetCartItems(HttpContextAccessor.HttpContext.Session); + int cartItemCount = cartItemIds.Count; + var userId = HttpContextAccessor.HttpContext?.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value; } + - - - @ViewData["Title"] - WebVentaCoche + - + + + - + - -
+

Bienvenido a WebVentaCoche

Tu tienda de confianza para productos de coches.

-
@RenderBody()
-

© 2024 WebVentaCoche. Todos los derechos reservados.

+ @RenderSection("Scripts", required: false) diff --git a/WebVentaCoche/WebVentaCoche.csproj b/WebVentaCoche/WebVentaCoche.csproj index f52e477..6a99885 100644 --- a/WebVentaCoche/WebVentaCoche.csproj +++ b/WebVentaCoche/WebVentaCoche.csproj @@ -7,6 +7,7 @@ +