Fix admin route access and backend configuration

- Added /admin redirect to login page in nginx config
- Fixed backend server.js route ordering for proper admin handling
- Updated authentication middleware and routes
- Added user management routes
- Configured PostgreSQL integration
- Updated environment configuration
This commit is contained in:
Local Server
2025-12-13 22:34:11 -06:00
parent 8bb6430a70
commit 703ab57984
253 changed files with 29870 additions and 157 deletions

View File

@@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class AboutController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _pagesCollection = "Pages";
public AboutController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public async Task<IActionResult> Index()
{
var pages = await _mongoService.GetAllAsync<Page>(_pagesCollection);
var aboutPage = pages.FirstOrDefault(p => p.PageSlug == "about" && p.IsActive);
Console.WriteLine($"[ABOUT] Found About page: {aboutPage != null}");
if (aboutPage != null)
{
Console.WriteLine($"[ABOUT] Title: {aboutPage.Title}");
Console.WriteLine($"[ABOUT] Content length: {aboutPage.Content?.Length ?? 0}");
Console.WriteLine($"[ABOUT] Image Gallery Count: {aboutPage.ImageGallery?.Count ?? 0}");
Console.WriteLine($"[ABOUT] Team Members Count: {aboutPage.TeamMembers?.Count ?? 0}");
if (aboutPage.ImageGallery != null && aboutPage.ImageGallery.Any())
{
Console.WriteLine($"[ABOUT] Gallery Images: {string.Join(", ", aboutPage.ImageGallery)}");
}
if (aboutPage.TeamMembers != null && aboutPage.TeamMembers.Any())
{
foreach (var member in aboutPage.TeamMembers)
{
Console.WriteLine($"[ABOUT] Team: {member.Name} ({member.Role}) - Photo: {member.PhotoUrl}");
}
}
}
if (aboutPage == null)
{
aboutPage = new Page
{
PageName = "About",
PageSlug = "about",
Title = "About Sky Art Shop",
Subtitle = "Creating moments, one craft at a time",
Content = "<h2>Our Story</h2><p>Sky Art Shop specializes in scrapbooking, journaling, cardmaking, and collaging stationery.</p>",
ImageGallery = new List<string>(),
TeamMembers = new List<TeamMember>()
};
}
return View(aboutPage);
}
}
}

View File

@@ -0,0 +1,86 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/blog")]
[Authorize(Roles="Admin")]
public class AdminBlogController : Controller
{
private readonly MongoDBService _mongoService;
private readonly SlugService _slugService;
private readonly string _blogCollection = "BlogPosts";
public AdminBlogController(MongoDBService mongoService, SlugService slugService)
{
_mongoService = mongoService;
_slugService = slugService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var posts = await _mongoService.GetAllAsync<BlogPost>(_blogCollection);
return View(posts.OrderByDescending(p => p.CreatedAt).ToList());
}
[HttpGet("create")]
public IActionResult Create() => View(new BlogPost());
[HttpPost("create")]
public async Task<IActionResult> Create(BlogPost post)
{
if (!ModelState.IsValid)
{
return View(post);
}
post.CreatedAt = DateTime.UtcNow;
post.UpdatedAt = DateTime.UtcNow;
post.PublishedDate = DateTime.UtcNow;
post.Slug = _slugService.GenerateSlug(post.Title);
await _mongoService.InsertAsync(_blogCollection, post);
TempData["SuccessMessage"] = "Blog post created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
var post = await _mongoService.GetByIdAsync<BlogPost>(_blogCollection, id);
if (post == null) return NotFound();
return View(post);
}
[HttpPost("edit/{id}")]
public async Task<IActionResult> Edit(string id, BlogPost post)
{
if (!ModelState.IsValid)
{
return View(post);
}
post.Id = id;
post.UpdatedAt = DateTime.UtcNow;
post.Slug = _slugService.GenerateSlug(post.Title);
await _mongoService.UpdateAsync(_blogCollection, id, post);
TempData["SuccessMessage"] = "Blog post updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _mongoService.DeleteAsync<BlogPost>(_blogCollection, id);
TempData["SuccessMessage"] = "Blog post deleted successfully!";
return RedirectToAction("Index");
}
private string GenerateSlug(string text)
{
return _slugService.GenerateSlug(text);
}
}
}

View File

@@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin")]
[Authorize(Roles = "Admin")]
public class AdminController : Controller
{
private readonly MongoDBService _mongoService;
private readonly SignInManager<SkyArtShop.Data.ApplicationUser> _signInManager;
private readonly UserManager<SkyArtShop.Data.ApplicationUser> _userManager;
public AdminController(MongoDBService mongoService,
SignInManager<SkyArtShop.Data.ApplicationUser> signInManager,
UserManager<SkyArtShop.Data.ApplicationUser> userManager)
{
_mongoService = mongoService;
_signInManager = signInManager;
_userManager = userManager;
}
[HttpGet("login")]
[AllowAnonymous]
public IActionResult Login()
{
if (User.Identity?.IsAuthenticated == true)
{
return RedirectToAction("Dashboard");
}
return View();
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(string email, string password)
{
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
ViewBag.Error = "Invalid email or password";
return View();
}
var result = await _signInManager.PasswordSignInAsync(user, password, true, false);
if (!result.Succeeded)
{
ViewBag.Error = "Invalid email or password";
return View();
}
return RedirectToAction("Dashboard");
}
[HttpGet("logout")]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("Login");
}
[HttpGet("dashboard")]
public async Task<IActionResult> Dashboard()
{
var products = await _mongoService.GetAllAsync<Product>("Products");
var projects = await _mongoService.GetAllAsync<PortfolioProject>("PortfolioProjects");
var blogPosts = await _mongoService.GetAllAsync<BlogPost>("BlogPosts");
var pages = await _mongoService.GetAllAsync<Page>("Pages");
var settings = (await _mongoService.GetAllAsync<SiteSettings>("SiteSettings")).FirstOrDefault();
ViewBag.ProductCount = products.Count;
ViewBag.ProjectCount = projects.Count;
ViewBag.BlogCount = blogPosts.Count;
ViewBag.PageCount = pages.Count;
ViewBag.SiteName = settings?.SiteName ?? "Sky Art Shop";
ViewBag.AdminEmail = User.Identity?.Name;
return View();
}
[HttpGet("")]
public IActionResult Index() => RedirectToAction("Dashboard");
}
}

View File

@@ -0,0 +1,225 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
using System.Text.Json;
namespace SkyArtShop.Controllers
{
[Route("admin/homepage")]
[Authorize(Roles = "Admin")]
public class AdminHomepageController : Controller
{
private readonly MongoDBService _mongoService;
private readonly IWebHostEnvironment _environment;
private readonly string _sectionsCollection = "HomepageSections";
private readonly string _settingsCollection = "SiteSettings";
public AdminHomepageController(MongoDBService mongoService, IWebHostEnvironment environment)
{
_mongoService = mongoService;
_environment = environment;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var sections = await _mongoService.GetAllAsync<HomepageSection>(_sectionsCollection);
sections = sections.OrderBy(s => s.DisplayOrder).ToList();
var settingsList = await _mongoService.GetAllAsync<SiteSettings>(_settingsCollection);
var settings = settingsList.FirstOrDefault() ?? new SiteSettings();
ViewBag.Settings = settings;
return View(sections);
}
[HttpGet("section/{id}")]
public async Task<IActionResult> EditSection(string id)
{
var section = await _mongoService.GetByIdAsync<HomepageSection>(_sectionsCollection, id);
if (section == null)
{
TempData["ErrorMessage"] = "Section not found.";
return RedirectToAction("Index");
}
return View(section);
}
[HttpPost("section/update")]
public async Task<IActionResult> UpdateSection(HomepageSection section, IFormFile? imageFile)
{
// Remove Content validation error since it's optional for some section types
ModelState.Remove("Content");
ModelState.Remove("AdditionalData");
if (!ModelState.IsValid)
{
foreach (var error in ModelState.Values.SelectMany(v => v.Errors))
{
Console.WriteLine($"ModelState Error: {error.ErrorMessage}");
}
return View("EditSection", section);
}
Console.WriteLine($"Updating section with ID: {section.Id}");
Console.WriteLine($"Title: {section.Title}");
Console.WriteLine($"Subtitle: {section.Subtitle}");
Console.WriteLine($"Content length: {section.Content?.Length ?? 0}");
Console.WriteLine($"IsActive: {section.IsActive}");
// Get existing section to preserve data
var existingSection = await _mongoService.GetByIdAsync<HomepageSection>(_sectionsCollection, section.Id!);
if (existingSection == null)
{
Console.WriteLine($"ERROR: Section with ID {section.Id} not found!");
TempData["ErrorMessage"] = "Section not found.";
return RedirectToAction("Index");
}
Console.WriteLine($"Found existing section: {existingSection.Title}");
// Update fields
existingSection.SectionType = section.SectionType;
existingSection.Title = section.Title ?? string.Empty;
existingSection.Subtitle = section.Subtitle ?? string.Empty;
existingSection.Content = section.Content ?? string.Empty;
existingSection.ButtonText = section.ButtonText ?? string.Empty;
existingSection.ButtonUrl = section.ButtonUrl ?? string.Empty;
existingSection.IsActive = section.IsActive;
existingSection.UpdatedAt = DateTime.UtcNow;
// Handle image upload
if (imageFile != null && imageFile.Length > 0)
{
var uploadsFolder = Path.Combine(_environment.WebRootPath, "uploads", "images");
Directory.CreateDirectory(uploadsFolder);
var uniqueFileName = $"{Guid.NewGuid()}_{imageFile.FileName}";
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await imageFile.CopyToAsync(fileStream);
}
existingSection.ImageUrl = $"/uploads/images/{uniqueFileName}";
Console.WriteLine($"New image uploaded: {existingSection.ImageUrl}");
}
await _mongoService.UpdateAsync(_sectionsCollection, existingSection.Id!, existingSection);
Console.WriteLine($"Section updated successfully in database");
TempData["SuccessMessage"] = "Section updated successfully!";
return RedirectToAction("Index");
}
[HttpGet("section/create")]
public IActionResult CreateSection()
{
return View();
}
[HttpPost("section/create")]
public async Task<IActionResult> CreateSection(HomepageSection section, IFormFile? imageFile)
{
// Remove Content validation error since it's optional for some section types
ModelState.Remove("Content");
ModelState.Remove("AdditionalData");
if (!ModelState.IsValid)
{
return View(section);
}
// Handle image upload
if (imageFile != null && imageFile.Length > 0)
{
var uploadsFolder = Path.Combine(_environment.WebRootPath, "uploads", "images");
Directory.CreateDirectory(uploadsFolder);
var uniqueFileName = $"{Guid.NewGuid()}_{imageFile.FileName}";
var filePath = Path.Combine(uploadsFolder, uniqueFileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await imageFile.CopyToAsync(fileStream);
}
section.ImageUrl = $"/uploads/images/{uniqueFileName}";
}
// Get the highest display order and add 1
var allSections = await _mongoService.GetAllAsync<HomepageSection>(_sectionsCollection);
section.DisplayOrder = allSections.Any() ? allSections.Max(s => s.DisplayOrder) + 1 : 0;
section.CreatedAt = DateTime.UtcNow;
section.UpdatedAt = DateTime.UtcNow;
await _mongoService.InsertAsync(_sectionsCollection, section);
TempData["SuccessMessage"] = "Section created successfully!";
return RedirectToAction("Index");
}
[HttpPost("section/delete/{id}")]
public async Task<IActionResult> DeleteSection(string id)
{
await _mongoService.DeleteAsync<HomepageSection>(_sectionsCollection, id);
TempData["SuccessMessage"] = "Section deleted successfully!";
return RedirectToAction("Index");
}
[HttpPost("section/reorder")]
public async Task<IActionResult> ReorderSections([FromBody] List<string> sectionIds)
{
for (int i = 0; i < sectionIds.Count; i++)
{
var section = await _mongoService.GetByIdAsync<HomepageSection>(_sectionsCollection, sectionIds[i]);
if (section != null)
{
section.DisplayOrder = i;
section.UpdatedAt = DateTime.UtcNow;
await _mongoService.UpdateAsync(_sectionsCollection, section.Id!, section);
}
}
return Json(new { success = true });
}
[HttpPost("section/toggle/{id}")]
public async Task<IActionResult> ToggleSection(string id)
{
var section = await _mongoService.GetByIdAsync<HomepageSection>(_sectionsCollection, id);
if (section != null)
{
section.IsActive = !section.IsActive;
section.UpdatedAt = DateTime.UtcNow;
await _mongoService.UpdateAsync(_sectionsCollection, section.Id!, section);
TempData["SuccessMessage"] = $"Section {(section.IsActive ? "activated" : "deactivated")} successfully!";
}
return RedirectToAction("Index");
}
[HttpPost("footer/update")]
public async Task<IActionResult> UpdateFooter(string footerText)
{
var settingsList = await _mongoService.GetAllAsync<SiteSettings>(_settingsCollection);
SiteSettings? settings = settingsList.FirstOrDefault();
if (settings == null)
{
settings = new SiteSettings { FooterText = footerText };
await _mongoService.InsertAsync(_settingsCollection, settings);
}
else
{
settings.FooterText = footerText;
settings.UpdatedAt = DateTime.UtcNow;
await _mongoService.UpdateAsync(_settingsCollection, settings.Id!, settings);
}
TempData["SuccessMessage"] = "Footer updated successfully!";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,113 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/menu")]
[Authorize(Roles = "Admin")]
public class AdminMenuController : Controller
{
private readonly MongoDBService _mongoService;
public AdminMenuController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var menuItems = await _mongoService.GetAllAsync<MenuItem>("MenuItems");
return View(menuItems.OrderBy(m => m.DisplayOrder).ToList());
}
[HttpPost("reseed")]
public async Task<IActionResult> ReseedMenu()
{
// Delete all existing menu items
var existingItems = await _mongoService.GetAllAsync<MenuItem>("MenuItems");
foreach (var item in existingItems)
{
await _mongoService.DeleteAsync<MenuItem>("MenuItems", item.Id!);
}
// Add new menu items
var defaultMenuItems = new[]
{
new MenuItem { Label = "Home", Url = "/", DisplayOrder = 1, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Shop", Url = "/Shop", DisplayOrder = 2, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Top Sellers", Url = "/#top-sellers", DisplayOrder = 3, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Promotion", Url = "/#promotion", DisplayOrder = 4, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Portfolio", Url = "/Portfolio", DisplayOrder = 5, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Blog", Url = "/Blog", DisplayOrder = 6, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "About", Url = "/About", DisplayOrder = 7, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "Instagram", Url = "#instagram", DisplayOrder = 8, IsActive = true, ShowInNavbar = false, ShowInDropdown = true },
new MenuItem { Label = "Contact", Url = "/Contact", DisplayOrder = 9, IsActive = true, ShowInNavbar = true, ShowInDropdown = true },
new MenuItem { Label = "My Wishlist", Url = "#wishlist", DisplayOrder = 10, IsActive = true, ShowInNavbar = false, ShowInDropdown = true }
};
foreach (var item in defaultMenuItems)
{
await _mongoService.InsertAsync("MenuItems", item);
}
TempData["SuccessMessage"] = "Menu items reseeded successfully!";
return RedirectToAction("Index");
}
[HttpGet("create")]
public IActionResult Create()
{
return View(new MenuItem());
}
[HttpPost("create")]
public async Task<IActionResult> Create(MenuItem menuItem)
{
if (!ModelState.IsValid)
{
return View(menuItem);
}
menuItem.CreatedAt = DateTime.UtcNow;
await _mongoService.InsertAsync("MenuItems", menuItem);
TempData["SuccessMessage"] = "Menu item created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
var menuItem = await _mongoService.GetByIdAsync<MenuItem>("MenuItems", id);
if (menuItem == null)
{
return NotFound();
}
return View(menuItem);
}
[HttpPost("edit/{id}")]
public async Task<IActionResult> Edit(string id, MenuItem menuItem)
{
if (!ModelState.IsValid)
{
return View(menuItem);
}
menuItem.Id = id;
await _mongoService.UpdateAsync("MenuItems", id, menuItem);
TempData["SuccessMessage"] = "Menu item updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _mongoService.DeleteAsync<MenuItem>("MenuItems", id);
TempData["SuccessMessage"] = "Menu item deleted successfully!";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,167 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/pages")]
[Authorize(Roles = "Admin")]
public class AdminPagesController : Controller
{
private readonly MongoDBService _mongoService;
private readonly SlugService _slugService;
private readonly string _pagesCollection = "Pages";
public AdminPagesController(MongoDBService mongoService, SlugService slugService)
{
_mongoService = mongoService;
_slugService = slugService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var pages = await _mongoService.GetAllAsync<Page>(_pagesCollection);
return View(pages.OrderBy(p => p.PageName).ToList());
}
[HttpGet("create")]
public IActionResult Create() => View(new Page());
[HttpPost("create")]
public async Task<IActionResult> Create(Page page)
{
if (!ModelState.IsValid)
{
return View(page);
}
page.CreatedAt = DateTime.UtcNow;
page.UpdatedAt = DateTime.UtcNow;
page.PageSlug = _slugService.GenerateSlug(page.PageName);
await _mongoService.InsertAsync(_pagesCollection, page);
TempData["SuccessMessage"] = "Page created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
var page = await _mongoService.GetByIdAsync<Page>(_pagesCollection, id);
if (page == null) return NotFound();
return View(page);
}
[HttpPost("edit/{id}")]
public async Task<IActionResult> Edit(string id, [FromForm] Page page, IFormCollection form)
{
Console.WriteLine("[ADMIN-PAGES] === FORM SUBMISSION DEBUG ===");
Console.WriteLine($"[ADMIN-PAGES] Form Keys: {string.Join(", ", form.Keys)}");
// Debug: Check what's in the form
foreach (var key in form.Keys)
{
if (key.StartsWith("ImageGallery") || key.StartsWith("TeamMembers"))
{
Console.WriteLine($"[ADMIN-PAGES] {key} = {form[key]}");
}
}
if (!ModelState.IsValid)
{
Console.WriteLine("[ADMIN-PAGES] ModelState is INVALID");
foreach (var error in ModelState.Values.SelectMany(v => v.Errors))
{
Console.WriteLine($"[ADMIN-PAGES] Error: {error.ErrorMessage}");
}
return View(page);
}
// Get existing page to preserve data
var existingPage = await _mongoService.GetByIdAsync<Page>(_pagesCollection, id);
if (existingPage == null)
{
Console.WriteLine($"[ADMIN-PAGES] Page not found: {id}");
return NotFound();
}
// Update basic fields
existingPage.PageName = page.PageName;
existingPage.Title = page.Title;
existingPage.Subtitle = page.Subtitle;
existingPage.Content = page.Content;
existingPage.IsActive = page.IsActive;
existingPage.UpdatedAt = DateTime.UtcNow;
existingPage.PageSlug = _slugService.GenerateSlug(page.PageName);
// Manually parse ImageGallery from form
existingPage.ImageGallery = new List<string>();
foreach (var key in form.Keys.Where(k => k.StartsWith("ImageGallery[")))
{
var value = form[key].ToString();
if (!string.IsNullOrEmpty(value))
{
existingPage.ImageGallery.Add(value);
}
}
// Manually parse TeamMembers from form
existingPage.TeamMembers = new List<TeamMember>();
var memberIndices = form.Keys
.Where(k => k.StartsWith("TeamMembers[") && k.Contains("].Name"))
.Select(k =>
{
var match = System.Text.RegularExpressions.Regex.Match(k, @"TeamMembers\[(\d+)\]");
return match.Success ? int.Parse(match.Groups[1].Value) : -1;
})
.Where(i => i >= 0)
.Distinct()
.OrderBy(i => i)
.ToList();
foreach (var index in memberIndices)
{
var member = new TeamMember
{
Name = form[$"TeamMembers[{index}].Name"].ToString(),
Role = form[$"TeamMembers[{index}].Role"].ToString(),
Bio = form[$"TeamMembers[{index}].Bio"].ToString(),
PhotoUrl = form[$"TeamMembers[{index}].PhotoUrl"].ToString()
};
existingPage.TeamMembers.Add(member);
}
Console.WriteLine($"[ADMIN-PAGES] Updating page: {existingPage.PageName} (Slug: {existingPage.PageSlug})");
Console.WriteLine($"[ADMIN-PAGES] Title: {existingPage.Title}");
Console.WriteLine($"[ADMIN-PAGES] Content length: {existingPage.Content?.Length ?? 0}");
Console.WriteLine($"[ADMIN-PAGES] Image Gallery Count: {existingPage.ImageGallery.Count}");
Console.WriteLine($"[ADMIN-PAGES] Team Members Count: {existingPage.TeamMembers.Count}");
if (existingPage.ImageGallery.Any())
{
Console.WriteLine($"[ADMIN-PAGES] Gallery Images: {string.Join(", ", existingPage.ImageGallery)}");
}
if (existingPage.TeamMembers.Any())
{
foreach (var member in existingPage.TeamMembers)
{
Console.WriteLine($"[ADMIN-PAGES] Team Member: {member.Name} - {member.Role} - Photo: {member.PhotoUrl}");
}
}
await _mongoService.UpdateAsync(_pagesCollection, id, existingPage);
TempData["SuccessMessage"] = "Page updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _mongoService.DeleteAsync<Page>(_pagesCollection, id);
TempData["SuccessMessage"] = "Page deleted successfully!";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,155 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/portfolio")]
[Authorize(Roles="Admin")]
public class AdminPortfolioController : Controller
{
private readonly MongoDBService _mongoService;
private readonly SlugService _slugService;
private readonly string _categoriesCollection = "PortfolioCategories";
private readonly string _projectsCollection = "PortfolioProjects";
public AdminPortfolioController(MongoDBService mongoService, SlugService slugService)
{
_mongoService = mongoService;
_slugService = slugService;
}
[HttpGet("categories")]
public async Task<IActionResult> Categories()
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
return View(categories.OrderBy(c => c.DisplayOrder).ToList());
}
[HttpGet("category/create")]
public IActionResult CreateCategory() => View(new PortfolioCategory());
[HttpPost("category/create")]
public async Task<IActionResult> CreateCategory(PortfolioCategory category)
{
if (!ModelState.IsValid)
{
return View(category);
}
category.CreatedAt = DateTime.UtcNow;
category.UpdatedAt = DateTime.UtcNow;
category.Slug = _slugService.GenerateSlug(category.Name);
await _mongoService.InsertAsync(_categoriesCollection, category);
TempData["SuccessMessage"] = "Category created successfully!";
return RedirectToAction("Categories");
}
[HttpGet("category/edit/{id}")]
public async Task<IActionResult> EditCategory(string id)
{
var category = await _mongoService.GetByIdAsync<PortfolioCategory>(_categoriesCollection, id);
if (category == null) return NotFound();
return View(category);
}
[HttpPost("category/edit/{id}")]
public async Task<IActionResult> EditCategory(string id, PortfolioCategory category)
{
if (!ModelState.IsValid)
{
return View(category);
}
category.Id = id;
category.UpdatedAt = DateTime.UtcNow;
category.Slug = _slugService.GenerateSlug(category.Name);
await _mongoService.UpdateAsync(_categoriesCollection, id, category);
TempData["SuccessMessage"] = "Category updated successfully!";
return RedirectToAction("Categories");
}
[HttpPost("category/delete/{id}")]
public async Task<IActionResult> DeleteCategory(string id)
{
await _mongoService.DeleteAsync<PortfolioCategory>(_categoriesCollection, id);
TempData["SuccessMessage"] = "Category deleted successfully!";
return RedirectToAction("Categories");
}
[HttpGet("projects")]
public async Task<IActionResult> Projects(string? categoryId)
{
var projects = await _mongoService.GetAllAsync<PortfolioProject>(_projectsCollection);
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
if (!string.IsNullOrEmpty(categoryId))
{
projects = projects.Where(p => p.CategoryId == categoryId).ToList();
}
ViewBag.Categories = categories.Where(c => c.IsActive).ToList();
ViewBag.SelectedCategory = categoryId;
return View(projects.OrderBy(p => p.DisplayOrder).ToList());
}
[HttpGet("project/create")]
public async Task<IActionResult> CreateProject()
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
ViewBag.Categories = categories.Where(c => c.IsActive).ToList();
return View(new PortfolioProject());
}
[HttpPost("project/create")]
public async Task<IActionResult> CreateProject(PortfolioProject project)
{
if (!ModelState.IsValid)
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
ViewBag.Categories = categories.Where(c => c.IsActive).ToList();
return View(project);
}
project.CreatedAt = DateTime.UtcNow;
project.UpdatedAt = DateTime.UtcNow;
await _mongoService.InsertAsync(_projectsCollection, project);
TempData["SuccessMessage"] = "Project created successfully!";
return RedirectToAction("Projects");
}
[HttpGet("project/edit/{id}")]
public async Task<IActionResult> EditProject(string id)
{
var project = await _mongoService.GetByIdAsync<PortfolioProject>(_projectsCollection, id);
if (project == null) return NotFound();
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
ViewBag.Categories = categories.Where(c => c.IsActive).ToList();
return View(project);
}
[HttpPost("project/edit/{id}")]
public async Task<IActionResult> EditProject(string id, PortfolioProject project)
{
if (!ModelState.IsValid)
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
ViewBag.Categories = categories.Where(c => c.IsActive).ToList();
return View(project);
}
project.Id = id;
project.UpdatedAt = DateTime.UtcNow;
await _mongoService.UpdateAsync(_projectsCollection, id, project);
TempData["SuccessMessage"] = "Project updated successfully!";
return RedirectToAction("Projects");
}
[HttpPost("project/delete/{id}")]
public async Task<IActionResult> DeleteProject(string id)
{
await _mongoService.DeleteAsync<PortfolioProject>(_projectsCollection, id);
TempData["SuccessMessage"] = "Project deleted successfully!";
return RedirectToAction("Projects");
}
}
}

View File

@@ -0,0 +1,172 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/products")]
[Authorize(Roles = "Admin")]
public class AdminProductsController : Controller
{
private readonly MongoDBService _mongoService;
private readonly SlugService _slugService;
private readonly string _productsCollection = "Products";
public AdminProductsController(MongoDBService mongoService, SlugService slugService)
{
_mongoService = mongoService;
_slugService = slugService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var products = await _mongoService.GetAllAsync<Product>(_productsCollection);
return View(products.OrderByDescending(p => p.CreatedAt).ToList());
}
[HttpGet("create")]
public IActionResult Create() => View(new Product());
[HttpPost("create")]
public async Task<IActionResult> Create(Product product)
{
// Remove validation errors for optional fields
ModelState.Remove("ShortDescription");
ModelState.Remove("Description");
if (!ModelState.IsValid)
{
return View(product);
}
// Ensure checkbox defaults when unchecked
if (!Request.Form.ContainsKey("IsActive")) product.IsActive = false;
if (!Request.Form.ContainsKey("IsFeatured")) product.IsFeatured = false;
if (!Request.Form.ContainsKey("IsTopSeller")) product.IsTopSeller = false;
// Handle multiple colors from form
var colors = Request.Form["Colors"].Where(c => !string.IsNullOrEmpty(c)).Select(c => c!).ToList();
product.Colors = colors.Any() ? colors : new List<string>();
// Debug logging
Console.WriteLine($"[CREATE] Colors received: {colors.Count}");
if (colors.Any())
{
Console.WriteLine($"[CREATE] Colors: {string.Join(", ", colors)}");
}
product.CreatedAt = DateTime.UtcNow;
product.UpdatedAt = DateTime.UtcNow;
product.Slug = _slugService.GenerateSlug(product.Name);
await _mongoService.InsertAsync(_productsCollection, product);
TempData["SuccessMessage"] = "Product created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
var product = await _mongoService.GetByIdAsync<Product>(_productsCollection, id);
if (product == null) return NotFound();
return View("Create", product);
}
[HttpPost("edit/{id}")]
public async Task<IActionResult> Edit(string id, Product product)
{
// Remove validation errors for optional fields
ModelState.Remove("Images");
ModelState.Remove("Slug");
ModelState.Remove("ShortDescription");
ModelState.Remove("Description");
if (!ModelState.IsValid)
{
return View("Create", product);
}
// Ensure checkbox defaults when unchecked
if (!Request.Form.ContainsKey("IsActive")) product.IsActive = false;
if (!Request.Form.ContainsKey("IsFeatured")) product.IsFeatured = false;
if (!Request.Form.ContainsKey("IsTopSeller")) product.IsTopSeller = false;
// Get existing product to preserve data
var existingProduct = await _mongoService.GetByIdAsync<Product>(_productsCollection, id);
if (existingProduct == null)
{
TempData["ErrorMessage"] = "Product not found.";
return RedirectToAction("Index");
}
// Update editable fields
existingProduct.Name = product.Name;
existingProduct.ShortDescription = product.ShortDescription;
existingProduct.Description = product.Description;
existingProduct.Price = product.Price;
existingProduct.Category = product.Category;
existingProduct.Color = product.Color;
// Handle multiple colors from form
var colors = Request.Form["Colors"].Where(c => !string.IsNullOrEmpty(c)).Select(c => c!).ToList();
existingProduct.Colors = colors.Any() ? colors : new List<string>();
// Debug logging
Console.WriteLine($"[EDIT] Colors received: {colors.Count}");
if (colors.Any())
{
Console.WriteLine($"[EDIT] Colors: {string.Join(", ", colors)}");
}
Console.WriteLine($"[EDIT] Product Colors property: {existingProduct.Colors?.Count ?? 0}");
existingProduct.StockQuantity = product.StockQuantity;
existingProduct.IsFeatured = product.IsFeatured;
existingProduct.IsTopSeller = product.IsTopSeller;
existingProduct.IsActive = product.IsActive;
existingProduct.UpdatedAt = DateTime.UtcNow;
existingProduct.Slug = _slugService.GenerateSlug(product.Name);
// Update images
if (Request.Form.ContainsKey("Images"))
{
var images = Request.Form["Images"].Where(img => !string.IsNullOrEmpty(img)).Select(img => img!).ToList();
existingProduct.Images = images;
// Set first image as main ImageUrl if not explicitly set
if (images.Any() && string.IsNullOrEmpty(product.ImageUrl))
{
existingProduct.ImageUrl = images[0] ?? "";
}
}
// Preserve or update ImageUrl
if (!string.IsNullOrEmpty(product.ImageUrl))
{
existingProduct.ImageUrl = product.ImageUrl;
}
// Update SKU and CostPrice if provided
if (!string.IsNullOrEmpty(product.SKU))
{
existingProduct.SKU = product.SKU;
}
if (product.CostPrice > 0)
{
existingProduct.CostPrice = product.CostPrice;
}
await _mongoService.UpdateAsync(_productsCollection, id, existingProduct);
TempData["SuccessMessage"] = "Product updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _mongoService.DeleteAsync<Product>(_productsCollection, id);
TempData["SuccessMessage"] = "Product deleted successfully!";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/settings")]
[Authorize(Roles="Admin")]
public class AdminSettingsController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _settingsCollection = "SiteSettings";
public AdminSettingsController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
var settingsList = await _mongoService.GetAllAsync<SiteSettings>(_settingsCollection);
var settings = settingsList.FirstOrDefault();
if (settings == null)
{
settings = new SiteSettings();
await _mongoService.InsertAsync(_settingsCollection, settings);
}
return View(settings);
}
[HttpPost("update")]
public async Task<IActionResult> Update(SiteSettings settings)
{
if (!ModelState.IsValid)
{
return View("Index", settings);
}
settings.UpdatedAt = DateTime.UtcNow;
if (!string.IsNullOrEmpty(settings.Id))
{
await _mongoService.UpdateAsync(_settingsCollection, settings.Id, settings);
}
else
{
await _mongoService.InsertAsync(_settingsCollection, settings);
}
TempData["SuccessMessage"] = "Site settings updated successfully!";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,106 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("admin/upload")]
[Authorize(Roles="Admin")]
public class AdminUploadController : Controller
{
private readonly IWebHostEnvironment _environment;
public AdminUploadController(IWebHostEnvironment environment)
{
_environment = environment;
}
[HttpGet("")]
public IActionResult Index()
{
var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads", "images");
var images = new List<string>();
if (Directory.Exists(uploadsPath))
{
var files = Directory.GetFiles(uploadsPath)
.Select(f => $"/uploads/images/{Path.GetFileName(f)}")
.OrderByDescending(f => f)
.ToList();
images = files;
}
return View(images);
}
[HttpPost("image")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
if (file == null || file.Length == 0)
{
return Json(new { success = false, message = "No file uploaded" });
}
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return Json(new { success = false, message = "Invalid file type" });
}
try
{
var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath);
var fileName = $"{Guid.NewGuid()}{extension}";
var filePath = Path.Combine(uploadsPath, fileName);
using var stream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
return Json(new { success = true, url = $"/uploads/images/{fileName}" });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
[HttpPost("multiple")]
public async Task<IActionResult> UploadMultiple(List<IFormFile> files)
{
var uploadedUrls = new List<string>();
foreach (var file in files)
{
if (file == null || file.Length == 0) continue;
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
if (!allowedExtensions.Contains(extension)) continue;
var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(uploadsPath)) Directory.CreateDirectory(uploadsPath);
var fileName = $"{Guid.NewGuid()}{extension}";
var filePath = Path.Combine(uploadsPath, fileName);
using var stream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
uploadedUrls.Add($"/uploads/images/{fileName}");
}
return Json(new { success = true, urls = uploadedUrls });
}
[HttpPost("delete")]
public IActionResult DeleteImage([FromBody] string imageUrl)
{
try
{
var fileName = Path.GetFileName(imageUrl);
var filePath = Path.Combine(_environment.WebRootPath, "uploads", "images", fileName);
if (System.IO.File.Exists(filePath))
{
System.IO.File.Delete(filePath);
return Json(new { success = true });
}
return Json(new { success = false, message = "File not found" });
}
catch (Exception ex)
{
return Json(new { success = false, message = ex.Message });
}
}
}
}

View File

@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
namespace SkyArtShop.Controllers
{
[Route("api/upload")]
[Authorize(Roles = "Admin")]
public class ApiUploadController : Controller
{
private readonly IWebHostEnvironment _environment;
public ApiUploadController(IWebHostEnvironment environment)
{
_environment = environment;
}
[HttpPost("image")]
public async Task<IActionResult> UploadImage(IFormFile image)
{
if (image == null || image.Length == 0)
{
return Json(new { success = false, message = "No file uploaded" });
}
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
var extension = Path.GetExtension(image.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
{
return Json(new { success = false, message = "Invalid file type. Only images are allowed." });
}
try
{
var uploadsPath = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(uploadsPath))
{
Directory.CreateDirectory(uploadsPath);
}
var fileName = $"{Guid.NewGuid()}{extension}";
var filePath = Path.Combine(uploadsPath, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await image.CopyToAsync(stream);
}
var imageUrl = $"/uploads/images/{fileName}";
Console.WriteLine($"[API-UPLOAD] Image uploaded successfully: {imageUrl}");
return Json(new { success = true, imageUrl = imageUrl });
}
catch (Exception ex)
{
Console.WriteLine($"[API-UPLOAD] Upload failed: {ex.Message}");
return Json(new { success = false, message = $"Upload failed: {ex.Message}" });
}
}
}
}

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class BlogController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _blogCollection = "BlogPosts";
public BlogController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public async Task<IActionResult> Index()
{
var posts = await _mongoService.GetAllAsync<BlogPost>(_blogCollection);
var publishedPosts = posts
.Where(p => p.IsPublished)
.OrderByDescending(p => p.PublishedDate)
.ToList();
return View(publishedPosts);
}
public async Task<IActionResult> Post(string slug)
{
var posts = await _mongoService.GetAllAsync<BlogPost>(_blogCollection);
var post = posts.FirstOrDefault(p => p.Slug == slug && p.IsPublished);
if (post == null)
{
return NotFound();
}
return View(post);
}
}
}

View File

@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class ContactController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _settingsCollection = "SiteSettings";
public ContactController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public async Task<IActionResult> Index()
{
var settingsList = await _mongoService.GetAllAsync<SiteSettings>(_settingsCollection);
var settings = settingsList.FirstOrDefault() ?? new SiteSettings();
return View(settings);
}
[HttpPost]
public IActionResult Submit(string name, string email, string phone, string subject, string message)
{
// Here you would implement email sending logic
// For now, just return a success message
TempData["Success"] = "Thank you! Your message has been sent. We'll get back to you soon.";
return RedirectToAction("Index");
}
}
}

View File

@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("diagnostics")]
public class DiagnosticsController : Controller
{
private readonly MongoDBService _mongoService;
public DiagnosticsController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
[HttpGet("products")]
public async Task<IActionResult> Products()
{
var products = await _mongoService.GetAllAsync<Product>("Products");
var report = products.Select(p => new
{
p.Id,
p.Name,
p.ImageUrl,
ImagesCount = p.Images?.Count ?? 0,
FirstImage = p.Images?.FirstOrDefault(),
HasImageUrl = !string.IsNullOrEmpty(p.ImageUrl),
HasImages = p.Images != null && p.Images.Any()
}).ToList();
return Json(report);
}
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class HomeController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _settingsCollection = "SiteSettings";
private readonly string _productsCollection = "Products";
private readonly string _sectionsCollection = "HomepageSections";
public HomeController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Index()
{
var settings = await GetSiteSettings();
var topProducts = await GetTopSellerProducts();
var sections = await GetHomepageSections();
ViewBag.Settings = settings;
ViewBag.TopProducts = topProducts;
ViewBag.Sections = sections;
return View();
}
private async Task<SiteSettings> GetSiteSettings()
{
var settingsList = await _mongoService.GetAllAsync<SiteSettings>(_settingsCollection);
return settingsList.FirstOrDefault() ?? new SiteSettings();
}
private async Task<List<Product>> GetTopSellerProducts()
{
var products = await _mongoService.GetAllAsync<Product>(_productsCollection);
return products.Where(p => p.IsTopSeller && p.IsActive).Take(4).ToList();
}
private async Task<List<HomepageSection>> GetHomepageSections()
{
var sections = await _mongoService.GetAllAsync<HomepageSection>(_sectionsCollection);
Console.WriteLine($"Total sections from DB: {sections.Count}");
var activeSections = sections.Where(s => s.IsActive).OrderBy(s => s.DisplayOrder).ToList();
Console.WriteLine($"Active sections: {activeSections.Count}");
foreach (var section in activeSections)
{
Console.WriteLine($"Section: {section.Title} | Type: {section.SectionType} | Order: {section.DisplayOrder} | Active: {section.IsActive}");
}
return activeSections;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View();
}
}
}

View File

@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
[Route("page")]
public class PageController : Controller
{
private readonly MongoDBService _mongoService;
public PageController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
[HttpGet("{slug}")]
public async Task<IActionResult> Index(string slug)
{
var pages = await _mongoService.GetAllAsync<Page>("Pages");
var page = pages.FirstOrDefault(p => p.PageSlug == slug && p.IsActive);
if (page == null)
{
return NotFound();
}
return View("View", page);
}
}
}

View File

@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class PortfolioController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _categoriesCollection = "PortfolioCategories";
private readonly string _projectsCollection = "PortfolioProjects";
public PortfolioController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public async Task<IActionResult> Index()
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
var activeCategories = categories.Where(c => c.IsActive).OrderBy(c => c.DisplayOrder).ToList();
return View(activeCategories);
}
public async Task<IActionResult> Category(string slug)
{
var categories = await _mongoService.GetAllAsync<PortfolioCategory>(_categoriesCollection);
var category = categories.FirstOrDefault(c => c.Slug == slug && c.IsActive);
if (category == null)
{
return NotFound();
}
var projects = await _mongoService.GetAllAsync<PortfolioProject>(_projectsCollection);
var categoryProjects = projects
.Where(p => p.CategoryId == category.Id && p.IsActive)
.OrderBy(p => p.DisplayOrder)
.ToList();
ViewBag.Category = category;
return View(categoryProjects);
}
public async Task<IActionResult> Project(string id)
{
var project = await _mongoService.GetByIdAsync<PortfolioProject>(_projectsCollection, id);
if (project == null)
{
return NotFound();
}
return View(project);
}
}
}

View File

@@ -0,0 +1,102 @@
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers
{
public class ShopController : Controller
{
private readonly MongoDBService _mongoService;
private readonly string _productsCollection = "Products";
public ShopController(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public async Task<IActionResult> Index(string? category, string? sort)
{
var products = await _mongoService.GetAllAsync<Product>(_productsCollection);
var activeProducts = products.Where(p => p.IsActive).ToList();
// Filter by category if specified
if (!string.IsNullOrEmpty(category) && category != "all")
{
activeProducts = activeProducts.Where(p => p.Category == category).ToList();
}
// Sort products
activeProducts = sort switch
{
"price-low" => activeProducts.OrderBy(p => p.Price).ToList(),
"price-high" => activeProducts.OrderByDescending(p => p.Price).ToList(),
"newest" => activeProducts.OrderByDescending(p => p.CreatedAt).ToList(),
_ => activeProducts.OrderByDescending(p => p.IsFeatured).ToList()
};
ViewBag.SelectedCategory = category ?? "all";
ViewBag.SelectedSort = sort ?? "featured";
ViewBag.Categories = activeProducts.Select(p => p.Category).Distinct().ToList();
return View(activeProducts);
}
public async Task<IActionResult> Product(string id)
{
var product = await _mongoService.GetByIdAsync<Product>(_productsCollection, id);
if (product == null)
{
return NotFound();
}
// Debug logging
Console.WriteLine($"[SHOP] Product ID: {id}");
Console.WriteLine($"[SHOP] Product Name: {product.Name}");
Console.WriteLine($"[SHOP] Colors Count: {product.Colors?.Count ?? 0}");
if (product.Colors != null && product.Colors.Any())
{
Console.WriteLine($"[SHOP] Colors: {string.Join(", ", product.Colors)}");
}
Console.WriteLine($"[SHOP] Legacy Color: {product.Color ?? "null"}");
// Track product view
var sessionId = HttpContext.Session.Id;
var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var productView = new ProductView
{
ProductId = id,
SessionId = sessionId,
IpAddress = ipAddress,
ViewedAt = DateTime.UtcNow
};
await _mongoService.InsertAsync("ProductViews", productView);
// Get related products based on:
// 1. Same category
// 2. Most viewed products
// 3. Exclude current product
var allProducts = await _mongoService.GetAllAsync<Product>(_productsCollection);
var allViews = await _mongoService.GetAllAsync<ProductView>("ProductViews");
// Count views for each product
var productViewCounts = allViews
.GroupBy(v => v.ProductId)
.ToDictionary(g => g.Key, g => g.Count());
var relatedProducts = allProducts
.Where(p => p.IsActive && p.Id != id)
.OrderByDescending(p =>
(p.Category == product.Category ? 100 : 0) + // Same category bonus
(productViewCounts.ContainsKey(p.Id ?? "") ? productViewCounts[p.Id ?? ""] : 0) + // View count
(p.IsFeatured ? 50 : 0) + // Featured bonus
(p.UnitsSold * 2) // Sales popularity
)
.Take(4)
.ToList();
ViewBag.RelatedProducts = relatedProducts;
return View(product);
}
}
}