From 9383a2fb09ffb60cfe63683106945bd688affa59 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Wed, 1 Jun 2022 21:13:43 +0200 Subject: feat: Initial commit after clean slate --- src/Controllers/AccountController.cs | 119 +++++++++++ src/Controllers/CategoriesController.cs | 106 ++++++++++ src/Controllers/DocumentsController.cs | 76 +++++++ src/Controllers/MainControllerBase.cs | 26 +++ src/Controllers/OrdersController.cs | 339 ++++++++++++++++++++++++++++++++ src/Controllers/ProductsController.cs | 121 ++++++++++++ src/Controllers/RootController.cs | 75 +++++++ 7 files changed, 862 insertions(+) create mode 100644 src/Controllers/AccountController.cs create mode 100644 src/Controllers/CategoriesController.cs create mode 100644 src/Controllers/DocumentsController.cs create mode 100644 src/Controllers/MainControllerBase.cs create mode 100644 src/Controllers/OrdersController.cs create mode 100644 src/Controllers/ProductsController.cs create mode 100644 src/Controllers/RootController.cs (limited to 'src/Controllers') diff --git a/src/Controllers/AccountController.cs b/src/Controllers/AccountController.cs new file mode 100644 index 0000000..ab80b78 --- /dev/null +++ b/src/Controllers/AccountController.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using VSH.Data; +using VSH.Data.Payloads; +using VSH.Data.Results; +using IOL.Helpers; +using VSH.Data.Database; + +namespace VSH.Controllers; + +public class AccountController : MainControllerBase +{ + private readonly IAuthenticationService _authentication; + private readonly MainDbContext _context; + private readonly IStringLocalizer _localizer; + + public AccountController( + MainDbContext context, + IStringLocalizer localizer, + IAuthenticationService authentication + ) { + _context = context; + _localizer = localizer; + _authentication = authentication; + } + + [ValidateAntiForgeryToken] + [HttpPost("login")] + public ActionResult Login(LoginPayload payload) { + if (!ModelState.IsValid) + BadRequest(ModelState); + var user = _context.Users.SingleOrDefault(u => u.Username == payload.Username); + if (user == default || !user.VerifyPassword(payload.Password)) + return BadRequest(new ErrorResult(_localizer["Ugyldig brukernavn eller passord"])); + + var claims = new List { + new(ClaimTypes.NameIdentifier, user.Id.ToString()), + new(ClaimTypes.Name, user.Username), + }; + var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + var authenticationProperties = new AuthenticationProperties { + AllowRefresh = true, + IssuedUtc = DateTimeOffset.UtcNow, + }; + + if (payload.Persist) { + authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(6); + authenticationProperties.IsPersistent = true; + } + + HttpContext.SignInAsync(principal, authenticationProperties); + return Ok(); + } + + [Authorize] + [HttpPost("update-password")] + public ActionResult UpdatePassword(UpdatePasswordPayload payload) { + if (payload.NewPassword.IsNullOrWhiteSpace()) { + return BadRequest(new ErrorResult(_localizer["Ugyldig skjema"], + _localizer["Nytt passord er påkrevd"])); + } + + if (payload.NewPassword.Length < 6) { + return BadRequest(new ErrorResult(_localizer["Ugyldig skjema"], + _localizer["Nytt passord må minst inneholde 6 karakterer"])); + } + + var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + HttpContext.SignOutAsync(); + return Redirect("/"); + } + + + user.HashAndSetPassword(payload.NewPassword); + user.Updated = DateTime.UtcNow; + _context.SaveChanges(); + return Ok(); + } + + [AllowAnonymous] + [HttpGet("create-initial")] + public ActionResult CreateInitialUser() { + if (_context.Users.Any()) return Redirect("/kontoret"); + var user = new User("admin@ivarlovlie.no"); + user.SetBaseValues(); + user.HashAndSetPassword("ivar123"); + _context.Users.Add(user); + _context.SaveChanges(); + return Redirect("/kontoret"); + } + + [AllowAnonymous] + [HttpGet("me")] + public async Task GetLoggedInUser() { + var authres = + await _authentication.AuthenticateAsync(HttpContext, CookieAuthenticationDefaults.AuthenticationScheme); + if (authres.Succeeded) + return Ok(LoggedInUser); + + await HttpContext.SignOutAsync(); + return StatusCode(403); + } + + [HttpGet("logout")] + public ActionResult Logout() { + HttpContext.SignOutAsync(); + return Ok(); + } +} \ No newline at end of file diff --git a/src/Controllers/CategoriesController.cs b/src/Controllers/CategoriesController.cs new file mode 100644 index 0000000..5ef87ea --- /dev/null +++ b/src/Controllers/CategoriesController.cs @@ -0,0 +1,106 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Localization; +using VSH.Data; +using VSH.Data.Database; +using VSH.Data.Enums; +using VSH.Data.Results; +using IOL.Helpers; + +namespace VSH.Controllers; + +public class CategoriesController : MainControllerBase +{ + private readonly MainDbContext _context; + private readonly IStringLocalizer _localizer; + + public CategoriesController(MainDbContext context, IStringLocalizer localizer) { + _context = context; + _localizer = localizer; + } + + [HttpGet] + public ActionResult GetCategories() { + return Ok(_context.Categories.OrderBy(c => c.Created)); + } + + [HttpGet("with-products")] + public ActionResult GetCategoriesWithProducts() { + return Ok(_context.Categories.Include(c => c.Products) + .OrderBy(c => c.Created)); + } + + [HttpGet("{id}")] + public ActionResult GetCategory(Guid id) { + return Ok(_context.Categories.SingleOrDefault(c => c.Id == id)); + } + + [HttpGet("{id}/enable")] + public ActionResult EnableCategory(Guid id) { + var category = _context.Categories.SingleOrDefault(c => c.Id == id); + if (category == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + category.VisibilityState = CategoryVisibility.DEFAULT; + category.Update(); + _context.SaveChanges(); + return Ok(); + } + + [HttpGet("{id}/disable")] + public ActionResult DisableCategory(Guid id) { + var category = _context.Categories.SingleOrDefault(c => c.Id == id); + if (category == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + category.VisibilityState = CategoryVisibility.DISABLED; + category.Update(); + _context.SaveChanges(); + return Ok(); + } + + [HttpGet("create")] + public ActionResult CreateCategoryAsync(string name) { + if (name.IsNullOrWhiteSpace()) + return BadRequest(new ErrorResult(_localizer["Ugyldig skjema"], _localizer["Navn er påkrevd"])); + + if (_context.Categories.Any(c => c.Name == name)) + return BadRequest(new ErrorResult(_localizer["Ugyldig skjema"], + _localizer["En kategori med det navnet finnes allerede"])); + + var newCategory = new Category(name); + newCategory.SetBaseValues(); + _context.Categories.Add(newCategory); + _context.SaveChanges(); + return Ok(newCategory); + } + + [HttpGet("{id}/update")] + public ActionResult UpdateCategoryAsync(Guid id, string newName) { + var category = _context.Categories.SingleOrDefault(c => c.Id == id); + if (category == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + if (newName.IsNullOrWhiteSpace()) + return BadRequest(new ErrorResult(_localizer["Ugyldig skjema"], + _localizer["Det nye navnet kan ikke være tomt"])); + category.Update(new Category(newName)); + _context.SaveChanges(); + return Ok(); + } + + [HttpDelete("{id}/delete")] + public ActionResult DeleteCategoryAsync(Guid id) { + var category = _context.Categories.Include(c => c.Products).SingleOrDefault(c => c.Id == id); + if (category == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + if (category.Products.Any()) { + category.VisibilityState = CategoryVisibility.DELETED; + _context.SaveChanges(); + } else { + _context.Categories.Remove(category); + _context.SaveChanges(); + } + + return Ok(); + } +} \ No newline at end of file diff --git a/src/Controllers/DocumentsController.cs b/src/Controllers/DocumentsController.cs new file mode 100644 index 0000000..b2a6122 --- /dev/null +++ b/src/Controllers/DocumentsController.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using IOL.Helpers; +using Microsoft.AspNetCore.Mvc; +using VSH.Data; +using VSH.Data.Database; +using VSH.Data.Enums; +using VSH.Data.Static; +using VSH.Utilities; + +namespace VSH.Controllers; + +public class DocumentsController : MainControllerBase +{ + private readonly MainDbContext _context; + + public DocumentsController(MainDbContext context) { + _context = context; + } + + [HttpGet("{type}")] + public ActionResult GetDocument(DocumentType type) { + var document = _context.Documents.SingleOrDefault(d => d.Type == type); + return Ok(document?.Content); + } + + [HttpPost("{type}")] + public ActionResult UpdateDocument([FromRoute] DocumentType type, [FromForm] string content) { + var document = _context.Documents.SingleOrDefault(d => d.Type == type); + if (document == default) { + var newDocument = new Document { + Content = content, + Type = type, + Created = DateTime.UtcNow + }; + _context.Documents.Add(newDocument); + _context.SaveChanges(); + return Ok(newDocument.Content); + } + + document.Content = content; + document.Updated = DateTime.UtcNow; + _context.SaveChanges(); + return Ok(document.Content); + } + + [HttpPost("upload-images")] + public ActionResult UploadImages() { + var files = Request.Form.Files; + if (files.Count == 0) + return BadRequest(); + var filePaths = new List(); + var fileNames = new List(); + foreach (var file in files) { + var fileName = RandomString.Generate(4, + true) + + "_" + + file.FileName; + var filePath = Path.Combine(AppPaths.DocumentImages.HostPath, + fileName); + using var writeStream = new FileStream(filePath, + FileMode.CreateNew); + file.CopyTo(writeStream); + filePaths.Add(new FileInfo(filePath)); + fileNames.Add(fileName); + } + + foreach (var filePath in filePaths) { + ImageFunctions.EnsureNormalOrLessImageResolution(filePath); + } + + return Ok(fileNames); + } +} \ No newline at end of file diff --git a/src/Controllers/MainControllerBase.cs b/src/Controllers/MainControllerBase.cs new file mode 100644 index 0000000..4f8f47f --- /dev/null +++ b/src/Controllers/MainControllerBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using System.Security.Claims; +using IOL.Helpers; +using Microsoft.AspNetCore.Mvc; + +namespace VSH.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class MainControllerBase : ControllerBase +{ + public string CurrentHost => Request.GetRequestHost(); + + + protected LoggedInUserModel LoggedInUser => new() { + Username = User.Identity?.Name, + Id = User.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value.ToGuid() ?? default + }; + + protected class LoggedInUserModel + { + public Guid Id { get; set; } + public string Username { get; set; } + } +} \ No newline at end of file diff --git a/src/Controllers/OrdersController.cs b/src/Controllers/OrdersController.cs new file mode 100644 index 0000000..39c0818 --- /dev/null +++ b/src/Controllers/OrdersController.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using IOL.Helpers; +using IOL.VippsEcommerce; +using IOL.VippsEcommerce.Models.Api; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using VSH.Data; +using VSH.Data.Database; +using VSH.Data.Dtos; +using VSH.Data.Enums; +using VSH.Data.Results; +using VSH.Services; +using VSH.Utilities; + +namespace VSH.Controllers; + +public class OrdersController : MainControllerBase +{ + private readonly MainDbContext _context; + private readonly OrderService _orderService; + private readonly IVippsEcommerceService _vippsService; + private readonly EmailService _emailService; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public OrdersController( + MainDbContext context, + IConfiguration configuration, + OrderService orderService, + IVippsEcommerceService vippsService, + EmailService emailService, + ILogger logger + ) { + _context = context; + _configuration = configuration; + _orderService = orderService; + _vippsService = vippsService; + _logger = logger; + _emailService = emailService; + } + + [HttpGet] + public ActionResult GetOrders(string filter = default) { + return filter switch { + "not-cancelled" => Ok(_context.Orders.Where(c => c.Status != OrderStatus.CANCELLED) + .OrderByDescending(c => c.Created)), + _ => Ok(_context.Orders.OrderByDescending(c => c.Created)) + }; + } + + [HttpPost("submit")] + public async Task SubmitOrder(Order payload) { + var validationResult = _orderService.ValidateOrder(payload); + if (!validationResult.IsValid) + return BadRequest(validationResult); + payload.SetBaseValues(); + var orderRef = RandomString.Generate(6); + var isOrderRefUsed = _context.Orders.Any(c => c.OrderReference == orderRef); + + while (isOrderRefUsed) { + orderRef = RandomString.Generate(6); + isOrderRefUsed = _context.Orders.Any(c => c.OrderReference == orderRef); + } + + payload.OrderReference = orderRef; + + foreach (var payloadProduct in payload.Products) { + var dbProduct = _context.Products.SingleOrDefault(c => c.Id == payloadProduct.Id); + if (dbProduct == default) { + throw new ApplicationException("Could not find product in order payload that passed validation"); + } + + payloadProduct.PriceAtTimeOfOrder = dbProduct.Price; + } + + switch (payload.PaymentType) { + case OrderPaymentType.VIPPS: { + payload.Id = Guid.NewGuid(); + payload.Status = OrderStatus.AWAITING_VIPPS; + await _context.Orders.AddAsync(payload); + await _context.SaveChangesAsync(); + var vippsUrl = await _orderService.GetVippsPaymentUrlAsync(payload, CurrentHost); + return Ok(vippsUrl); + } + case OrderPaymentType.INVOICE_BY_EMAIL: { + payload.Status = OrderStatus.AWAITING_INVOICE; + await _context.Orders.AddAsync(payload); + await _context.SaveChangesAsync(); + + await _emailService.SendEmailAsync("Ny ordre - VSH", + payload.GetAdminMailContent(CurrentHost), + _configuration.GetOrderStatusEmailRecipients()); + await _emailService.SendEmailAsync("Din ordre hos Vinjesvingen Handel AS", + payload.GetCustomerMailContent(CurrentHost), + payload.ContactInfo.EmailAddress); + return Ok("/status/" + payload.OrderReference + "?clearCart=true"); + } + default: { + return BadRequest("No PaymentType was specified"); + } + } + } + + [HttpGet("{orderId}/details")] + public ActionResult GetOrderDetails(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) { + return NotFound(); + } + + var products = new List(); + + foreach (var orderProduct in order.Products) { + var dbProduct = _context.Products.SingleOrDefault(c => c.Id == orderProduct.Id); + if (dbProduct == default) + continue; + products.Add(new AdminViewOrderDto.OrderDetailProduct { + Id = orderProduct.Id, + Count = orderProduct.NumberOfItems, + PayedPrice = orderProduct.PriceAtTimeOfOrder, + Name = dbProduct.Name + }); + } + + var x = new AdminViewOrderDto { + Comment = order.Comment, + Id = order.Id, + Products = products, + Status = order.Status, + ContactInformation = order.ContactInfo, + OrderDate = order.Created, + OrderReference = order.OrderReference, + PaymentType = order.PaymentType, + VippsId = order.VippsTransactionId, + VippsStatus = order.VippsStatus.ToString(), + VippsTransactionStatus = order.VippsTransactionStatus.ToString() + }; + + return x; + } + + + [HttpGet("{orderId}/capture")] + public async Task CaptureVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.CapturePaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpGet("{orderId}/cancel")] + public async Task CancelVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.CancelPaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpGet("{orderId}/refund")] + public async Task RefundVippsPayment(Guid orderId) { + var order = _context.Orders.SingleOrDefault(c => c.Id == orderId); + if (order == default) + return NotFound(); + try { + var response = await _vippsService.RefundPaymentAsync(order.OrderReference, + new VippsPaymentActionRequest { + Transaction = new TTransaction { + TransactionText = + order.OrderReference + + "_" + + DateTime.UtcNow.ToString("O") + } + }); + if (response == default) + return StatusCode(500); + order.VippsStatus = response.TransactionInfo.Status; + await _context.SaveChangesAsync(); + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderId", orderId); + return StatusCode(500); + } + } + + [HttpPost("validate-products")] + public ActionResult ValidateOrderProducts(Order payload) { + return _orderService.ValidateOrderProducts(payload); + } + + [HttpPost("validate")] + public ActionResult ValidateOrder(Order payload) { + return _orderService.ValidateOrder(payload); + } + + /// + /// This is used for all payment updates performed at vipps and can be called anytime from vipps + /// This endpoint updates the order status and save the body as an VippsResponses.InitiationResponse in db + /// + /// + /// + [HttpPost("vipps/callbacks-for-payment-update/v2/payments/{orderReference}")] + public async Task VippsPaymentUpdateCallback([FromRoute] string orderReference) { + var bodyContent = await new StreamReader(Request.Body).ReadToEndAsync(); + _logger.LogInformation("Recieved vipps payment update callback for order: " + orderReference); + _logger.LogDebug("With body: " + bodyContent); + + var order = _context.Orders.SingleOrDefault(c => c.OrderReference == orderReference); + if (order == default) { + _logger.LogWarning("Could not find order: " + orderReference + ", in database"); + return Ok(); + } + + try { + var response = JsonSerializer.Deserialize(bodyContent); + if (response == default) { + _logger.LogCritical("Could not deserialize vipps response into " + + nameof(VippsPaymentInitiationCallbackResponse)); + return Ok(); + } + + order.VippsTransactionId = response.TransactionInfo.TransactionId; + order.VippsTransactionStatus = response.TransactionInfo.StatusEnum(); + order.Status = order.VippsTransactionStatus switch { + ETransactionStatus.SALE or ETransactionStatus.RESERVED => OrderStatus.COMPLETED, + ETransactionStatus.REJECTED or + ETransactionStatus.CANCELLED or + ETransactionStatus.AUTO_CANCEL => OrderStatus.CANCELLED, + ETransactionStatus.SALE_FAILED or ETransactionStatus.RESERVE_FAILED => OrderStatus.FAILED, + _ => OrderStatus.FAILED + }; + await _context.SaveChangesAsync(); + + if (order.Status != OrderStatus.COMPLETED) { + _logger.LogWarning("OrderStatus is unsatisfactory for this stage of payment history, returning to cart page with error message"); + if (response.TransactionInfo != default) { + _logger.LogWarning("Vipps TransactionStatus is " + response.TransactionInfo?.Status); + } else { + _logger.LogWarning("Vipps TransactionInfo is empty"); + } + } + + return Ok(); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderid", order.Id); + _logger.LogCritical(e, "WHOOOOOOOOOOOPS"); + return Ok(); + } + } + + /// + /// This endpoint is specified in the intiation request to vipps, regardless of the result of a capture in vipps, vipps will send customers to this endpoint. + /// This endpoint decides where a customer is sent based on the result of a vipps payment request + /// + /// + /// + [HttpGet("vipps/payment-callback/{orderReference}")] + public async Task VippsPaymentCallback([FromRoute] string orderReference) { + _logger.LogInformation("Recieved vipps payment callback for order: " + orderReference); + + var order = _context.Orders.SingleOrDefault(c => c.OrderReference == orderReference); + if (order == default) { + _logger.LogWarning("Could not find order: " + orderReference + ", in database"); + return Redirect("/handlekorg?error=failed"); + } + + try { + if (order.Status == OrderStatus.CANCELLED) { + return Redirect("/handlekorg?error=cancelled"); + } + + if (order.Status != OrderStatus.COMPLETED) { + _logger.LogWarning("OrderStatus is unsatisfactory for this stage of payment history, returning to cart page with error message"); + + return Redirect("/handlekorg?error=failed"); + } + + await _emailService.SendEmailAsync("Ny ordre - VSH", + order.GetAdminMailContent(CurrentHost), + _configuration.GetOrderStatusEmailRecipients()); + + await _emailService.SendEmailAsync("Din ordre hos Vinjesvingen Handel AS", + order.GetCustomerMailContent(CurrentHost), + order.ContactInfo.EmailAddress); + + return Redirect("/status/" + orderReference + "?clearCart=true"); + } catch (Exception e) { + Console.WriteLine(e); + e.Data.Add("orderid", order.Id); + _logger.LogCritical(e, "WHOOOOOOOOOOOPS"); + return Redirect("/handlekorg?error=failed"); + } + } +} diff --git a/src/Controllers/ProductsController.cs b/src/Controllers/ProductsController.cs new file mode 100644 index 0000000..9bd7e93 --- /dev/null +++ b/src/Controllers/ProductsController.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using IOL.Helpers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Localization; +using VSH.Data; +using VSH.Data.Database; +using VSH.Data.Enums; +using VSH.Data.Results; +using VSH.Data.Static; +using VSH.Utilities; + +namespace VSH.Controllers; + +[Authorize] +public class ProductsController : MainControllerBase +{ + private readonly MainDbContext _context; + private readonly IStringLocalizer _localizer; + + public ProductsController( + MainDbContext context, + IStringLocalizer localizer + ) { + _context = context; + _localizer = localizer; + } + + [HttpGet] + [AllowAnonymous] + public ActionResult GetAllProducts() { + return Ok(_context.Products.Include(c => c.Category)); + } + + [HttpGet("{id}")] + [AllowAnonymous] + public Product GetProduct(Guid id) { + return _context.Products + .Include(c => c.Category) + .SingleOrDefault(c => c.Id == id); + } + + [HttpPost("upload-images")] + public ActionResult UploadImages() { + if (Request.Form.Files.Count == 0) + return BadRequest(); + var filePaths = new List(); + var fileNames = new List(); + foreach (var file in Request.Form.Files) { + var fileName = RandomString.Generate(8, true) + file.FileName.ExtractExtension(); + var filePath = Path.Combine(AppPaths.ProductImages.HostPath, fileName); + using var writeStream = new FileStream(filePath, FileMode.CreateNew); + file.CopyTo(writeStream); + filePaths.Add(new FileInfo(filePath)); + fileNames.Add(fileName); + } + + foreach (var filePath in filePaths) { + ImageFunctions.CreateProductImageCollection(filePath); + } + + return Ok(fileNames); + } + + [HttpPost("create")] + public ActionResult CreateProductAsync(Product payload) { + payload.SetBaseValues(); + var category = _context.Categories.SingleOrDefault(p => p.Id == payload.Category.Id); + if (category == default) + return BadRequest(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + payload.Category = category; + payload.Slug = payload.Name.Slugified(); + _context.Products.Add(payload); + _context.SaveChanges(); + return Ok(); + } + + + [HttpPost("{id}/update")] + public ActionResult UpdateProductAsync([FromRoute] Guid id, [FromBody] Product payload) { + var product = _context.Products.SingleOrDefault(p => p.Id == id); + if (product == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne produktet"])); + var category = _context.Categories.AsNoTracking().SingleOrDefault(c => c.Id == payload.Category.Id); + if (category == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne kategorien"])); + + var newImages = payload.Images.Select(c => new ProductImage(c.FileName, c.Order)).ToList(); + + product.Images = newImages; + product.Category = category; + product.Name = payload.Name; + product.Slug = product.Name.Slugified(); + product.Description = payload.Description; + product.Price = payload.Price; + product.Count = payload.Count; + product.PriceSuffix = payload.PriceSuffix; + product.ShowOnFrontpage = payload.ShowOnFrontpage; + product.Updated = DateTime.UtcNow; + _context.SaveChanges(); + + return Ok(); + } + + [HttpDelete("{id}/delete")] + public ActionResult DeleteProductAsync(Guid id) { + var product = _context.Products.SingleOrDefault(p => p.Id == id); + if (product == default) + return NotFound(new ErrorResult(_localizer["Kunne ikke finne produktet"])); + + product.VisibilityState = ProductVisibility.DELETED; + product.Updated = DateTime.UtcNow; + + _context.SaveChanges(); + return Ok(); + } +} \ No newline at end of file diff --git a/src/Controllers/RootController.cs b/src/Controllers/RootController.cs new file mode 100644 index 0000000..6a31d46 --- /dev/null +++ b/src/Controllers/RootController.cs @@ -0,0 +1,75 @@ +using System; +using System.Text; +using IOL.VippsEcommerce; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using VSH.Data.Static; + +namespace VSH.Controllers; + +public class RootController : MainControllerBase +{ + private readonly IVippsEcommerceService _vippsEcommerceService; + private readonly IConfiguration _configuration; + + public RootController(IConfiguration configuration, IVippsEcommerceService vippsEcommerceService) { + _configuration = configuration; + _vippsEcommerceService = vippsEcommerceService; + } + + [HttpGet("~/kontoret")] + public ActionResult AdminRedirect() { + return Redirect("/kontoret/produkter"); + } + + [Authorize] + [HttpGet("~/info")] + public ActionResult GetInfo() { + var infoString = new StringBuilder(); + infoString.AppendLine("Variables"); + infoString.AppendLine($" - CurrentHost: {CurrentHost}"); + infoString.AppendLine("Environment"); + infoString.AppendLine($" - DB_HOST: {_configuration.GetValue("DB_HOST", "unset")}"); + infoString.AppendLine($" - DB_PORT: {_configuration.GetValue("DB_PORT", "unset")}"); + infoString.AppendLine($" - DB_USER: {_configuration.GetValue("DB_USER", "unset")}"); + infoString.AppendLine($" - DB_PASSWORD: {HiddenIfSet(_configuration.GetValue("DB_PASSWORD", "unset"))}"); + infoString.AppendLine($" - DB_NAME: {_configuration.GetValue("DB_NAME", "unset")}"); + infoString.AppendLine($" - SENDGRID_API_KEY: {HiddenIfSet(_configuration.GetValue("SENDGRID_API_KEY", "unset"))}"); + infoString.AppendLine($" - MAIL_FROM_ADDRESS: {_configuration.GetValue("MAIL_FROM_ADDRESS", "unset")}"); + infoString.AppendLine($" - MAIL_REPLY_TO_ADDRESS: {_configuration.GetValue("MAIL_REPLY_TO_ADDRESS", "unset")}"); + infoString.AppendLine($" - MAIL_FROM_NAME: {_configuration.GetValue("MAIL_FROM_NAME", "unset")}"); + infoString.AppendLine("Headers"); + infoString.AppendLine($" - X-Forwarded-For: {Request.Headers["X-Forwarded-For"]}"); + infoString.AppendLine($" - X-Real-IP: {Request.Headers["X-Real-IP"]}"); + infoString.AppendLine($" - X-Forwarded-Host: {Request.Headers["X-Forwarded-Host"]}"); + infoString.AppendLine($" - X-Forwarded-Proto: {Request.Headers["X-Forwarded-Proto"]}"); + return Ok(infoString.ToString()); + } + + [Authorize] + [HttpGet("~/vipps-config")] + public ActionResult GetVippsConfiguration() { + return Ok(_vippsEcommerceService.Configuration); + } + + private string HiddenIfSet(string value) { + return value == "unset" ? "unset" : "*****"; + } + + [HttpPost("~/set-culture")] + public ActionResult SetCulture( + [FromForm] string culture, + [FromQuery] string returnUrl + ) { + Response.Cookies.Append(AppCookies.Culture.Name, + CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)), + new CookieOptions { + Expires = DateTimeOffset.UtcNow.AddYears(1) + }); + + return LocalRedirect(returnUrl); + } +} \ No newline at end of file -- cgit v1.3