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,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class AboutController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly string _pagesCollection = "Pages";
public AboutController(PostgreSQLService pgService)
{
_pgService = pgService;
}
public async Task<IActionResult> Index()
{
Page page = (await _pgService.GetAllAsync<Page>(_pagesCollection)).FirstOrDefault((Page p) => p.PageSlug == "about" && p.IsActive);
Console.WriteLine($"[ABOUT] Found About page: {page != null}");
if (page != null)
{
Console.WriteLine("[ABOUT] Title: " + page.Title);
Console.WriteLine($"[ABOUT] Content length: {page.Content?.Length ?? 0}");
Console.WriteLine($"[ABOUT] Image Gallery Count: {page.ImageGallery?.Count ?? 0}");
Console.WriteLine($"[ABOUT] Team Members Count: {page.TeamMembers?.Count ?? 0}");
if (page.ImageGallery != null && page.ImageGallery.Any())
{
Console.WriteLine("[ABOUT] Gallery Images: " + string.Join(", ", page.ImageGallery));
}
if (page.TeamMembers != null && page.TeamMembers.Any())
{
foreach (TeamMember teamMember in page.TeamMembers)
{
Console.WriteLine($"[ABOUT] Team: {teamMember.Name} ({teamMember.Role}) - Photo: {teamMember.PhotoUrl}");
}
}
}
if (page == null)
{
page = 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(page);
}
}

View File

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

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Route("admin")]
[Authorize(Roles = "Admin,MasterAdmin,Cashier,Accountant")]
public class AdminController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly PostgreAuthService _pgAuthService;
public AdminController(PostgreSQLService pgService, PostgreAuthService pgAuthService)
{
_pgService = pgService;
_pgAuthService = pgAuthService;
}
[HttpGet("login")]
[AllowAnonymous]
public IActionResult Login()
{
IIdentity? identity = base.User.Identity;
if (identity != null && identity.IsAuthenticated)
{
return RedirectToAction("Dashboard");
}
return View();
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login(string email, string password)
{
AdminUser adminUser = await _pgAuthService.AuthenticateAsync(email, password);
if (adminUser == null)
{
base.ViewBag.Error = "Invalid email or password";
return View();
}
ClaimsPrincipal principal = _pgAuthService.CreateClaimsPrincipal(adminUser);
await base.HttpContext.SignInAsync("Cookies", principal, new AuthenticationProperties
{
IsPersistent = true,
ExpiresUtc = DateTimeOffset.UtcNow.AddDays(30.0)
});
return RedirectToAction("Dashboard");
}
[HttpGet("logout")]
public async Task<IActionResult> Logout()
{
await base.HttpContext.SignOutAsync("Cookies");
return RedirectToAction("Login");
}
[HttpGet("dashboard")]
public async Task<IActionResult> Dashboard()
{
List<Product> products = await _pgService.GetAllAsync<Product>("Products");
List<PortfolioProject> projects = await _pgService.GetAllAsync<PortfolioProject>("PortfolioProjects");
List<BlogPost> blogPosts = await _pgService.GetAllAsync<BlogPost>("BlogPosts");
List<Page> pages = await _pgService.GetAllAsync<Page>("Pages");
SiteSettings siteSettings = await _pgService.GetSiteSettingsAsync();
base.ViewBag.ProductCount = products.Count;
base.ViewBag.ProjectCount = projects.Count;
base.ViewBag.BlogCount = blogPosts.Count;
base.ViewBag.PageCount = pages.Count;
base.ViewBag.SiteName = siteSettings?.SiteName ?? "Sky Art Shop";
base.ViewBag.AdminEmail = base.User.Identity?.Name;
return View();
}
[HttpGet("")]
public IActionResult Index()
{
return RedirectToAction("Dashboard");
}
[HttpGet("system-status")]
[Authorize]
public async Task<IActionResult> SystemStatus()
{
try
{
var value = new
{
databaseConnected = true,
dbType = "PostgreSQL",
dbHost = "localhost",
dbName = "skyartshop",
dbVersion = "16",
userCount = (await _pgService.GetAllAsync<AdminUser>("AdminUsers")).Count,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC")
};
return new JsonResult(value);
}
catch (Exception ex)
{
var value2 = new
{
databaseConnected = false,
error = ex.Message,
timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss UTC")
};
return new JsonResult(value2);
}
}
[HttpGet("change-password")]
public IActionResult ChangePassword()
{
return View();
}
[HttpGet("diagnostic")]
public IActionResult DiagnosticTest()
{
return View();
}
[HttpGet("reset-password-emergency")]
[AllowAnonymous]
public async Task<IActionResult> EmergencyPasswordReset(string confirm, string secret)
{
string text = Environment.GetEnvironmentVariable("EMERGENCY_RESET_SECRET") ?? "skyart-emergency-2025";
if (secret != text)
{
return NotFound();
}
if (confirm != "yes-reset-now")
{
return Content("Add ?confirm=yes-reset-now&secret=YOUR_SECRET to URL to reset admin password");
}
string email = "admin@skyartshop.com";
string newPassword = "Admin123!";
AdminUser adminUser = await _pgService.GetUserByEmailAsync(email);
if (adminUser == null)
{
adminUser = new AdminUser
{
Email = email,
Name = "System Administrator",
Role = "MasterAdmin",
Permissions = new List<string>
{
"manage_users", "manage_products", "manage_orders", "manage_content", "manage_settings", "view_reports", "manage_finances", "manage_inventory", "manage_customers", "manage_blog",
"manage_portfolio", "manage_pages"
},
IsActive = true,
CreatedBy = "Emergency Reset",
CreatedAt = DateTime.UtcNow
};
}
adminUser.PasswordHash = _pgAuthService.HashPassword(newPassword);
adminUser.LastLogin = DateTime.UtcNow;
await _pgService.CreateAdminUserAsync(adminUser);
return Content($"\r\n<!DOCTYPE html>\r\n<html>\r\n<head>\r\n <title>Password Reset Complete</title>\r\n <style>\r\n body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }}\r\n .success {{ background: #d4edda; border: 1px solid #c3e6cb; padding: 20px; border-radius: 5px; }}\r\n .credentials {{ background: #f8f9fa; padding: 15px; margin: 20px 0; border-left: 4px solid #28a745; }}\r\n a {{ color: #007bff; text-decoration: none; }}\r\n </style>\r\n</head>\r\n<body>\r\n <div class='success'>\r\n <h2>✓ Password Reset Successful</h2>\r\n <p>The admin password has been reset.</p>\r\n <div class='credentials'>\r\n <strong>Login Credentials:</strong><br>\r\n Email: <code>{email}</code><br>\r\n Password: <code>{newPassword}</code>\r\n </div>\r\n <p><a href='/admin/login'>→ Go to Login Page</a></p>\r\n <p><small>For security, this URL will be disabled after first successful login.</small></p>\r\n </div>\r\n</body>\r\n</html>\r\n", "text/html");
}
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword(string currentPassword, string newPassword, string confirmPassword)
{
if (string.IsNullOrWhiteSpace(currentPassword) || string.IsNullOrWhiteSpace(newPassword))
{
base.ViewBag.Error = "All fields are required";
return View();
}
if (newPassword != confirmPassword)
{
base.ViewBag.Error = "New password and confirmation do not match";
return View();
}
if (newPassword.Length < 6)
{
base.ViewBag.Error = "Password must be at least 6 characters";
return View();
}
string text = base.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
if (string.IsNullOrEmpty(text))
{
base.ViewBag.Error = "User not found";
return View();
}
AdminUser adminUser = await _pgService.GetByIdAsync<AdminUser>("AdminUsers", text);
if (adminUser == null || !_pgAuthService.VerifyPassword(currentPassword, adminUser.PasswordHash))
{
base.ViewBag.Error = "Current password is incorrect";
return View();
}
base.ViewBag.Info = "Password change temporarily disabled during migration. Contact system admin.";
return View();
}
}

View File

@@ -0,0 +1,225 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Route("admin/homepage")]
[Authorize(Roles = "Admin,MasterAdmin")]
public class AdminHomepageController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly IWebHostEnvironment _environment;
private readonly string _sectionsCollection = "HomepageSections";
private readonly string _settingsCollection = "SiteSettings";
public AdminHomepageController(PostgreSQLService pgService, IWebHostEnvironment environment)
{
_pgService = pgService;
_environment = environment;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
List<HomepageSection> sections = (await _pgService.GetAllAsync<HomepageSection>(_sectionsCollection)).OrderBy((HomepageSection s) => s.DisplayOrder).ToList();
SiteSettings siteSettings = (await _pgService.GetAllAsync<SiteSettings>(_settingsCollection)).FirstOrDefault() ?? new SiteSettings();
base.ViewBag.Settings = siteSettings;
return View(sections);
}
[HttpGet("section/{id}")]
public async Task<IActionResult> EditSection(string id)
{
HomepageSection homepageSection = await _pgService.GetByIdAsync<HomepageSection>(_sectionsCollection, id);
if (homepageSection == null)
{
base.TempData["ErrorMessage"] = "Section not found.";
return RedirectToAction("Index");
}
return View(homepageSection);
}
[HttpPost("section/update")]
public async Task<IActionResult> UpdateSection(HomepageSection section, IFormFile? imageFile, string? SelectedImageUrl)
{
base.ModelState.Remove("Content");
base.ModelState.Remove("AdditionalData");
base.ModelState.Remove("SelectedImageUrl");
if (!base.ModelState.IsValid)
{
foreach (ModelError item in base.ModelState.Values.SelectMany((ModelStateEntry v) => v.Errors))
{
Console.WriteLine("ModelState Error: " + item.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}");
HomepageSection existingSection = await _pgService.GetByIdAsync<HomepageSection>(_sectionsCollection, section.Id);
if (existingSection == null)
{
Console.WriteLine("ERROR: Section with ID " + section.Id + " not found!");
base.TempData["ErrorMessage"] = "Section not found.";
return RedirectToAction("Index");
}
Console.WriteLine("Found existing section: " + existingSection.Title);
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;
if (existingSection.AdditionalData == null)
{
existingSection.AdditionalData = new Dictionary<string, string>();
}
if (!string.IsNullOrEmpty(SelectedImageUrl))
{
existingSection.ImageUrl = SelectedImageUrl;
Console.WriteLine("Image selected from library: " + existingSection.ImageUrl);
}
else if (imageFile != null && imageFile.Length > 0)
{
string text = Path.Combine(_environment.WebRootPath, "uploads", "images");
Directory.CreateDirectory(text);
string uniqueFileName = $"{Guid.NewGuid()}_{imageFile.FileName}";
string path = Path.Combine(text, uniqueFileName);
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
await imageFile.CopyToAsync(fileStream);
}
existingSection.ImageUrl = "/uploads/images/" + uniqueFileName;
Console.WriteLine("New image uploaded: " + existingSection.ImageUrl);
}
await _pgService.UpdateAsync(_sectionsCollection, existingSection.Id, existingSection);
Console.WriteLine("Section updated successfully in database");
base.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, string? SelectedImageUrl)
{
base.ModelState.Remove("Content");
base.ModelState.Remove("AdditionalData");
if (!base.ModelState.IsValid)
{
return View(section);
}
if (!string.IsNullOrEmpty(SelectedImageUrl))
{
section.ImageUrl = SelectedImageUrl;
Console.WriteLine("Image selected from library: " + section.ImageUrl);
}
else if (imageFile != null && imageFile.Length > 0)
{
string text = Path.Combine(_environment.WebRootPath, "uploads", "images");
Directory.CreateDirectory(text);
string uniqueFileName = $"{Guid.NewGuid()}_{imageFile.FileName}";
string path = Path.Combine(text, uniqueFileName);
using (FileStream fileStream = new FileStream(path, FileMode.Create))
{
await imageFile.CopyToAsync(fileStream);
}
section.ImageUrl = "/uploads/images/" + uniqueFileName;
}
if (section.AdditionalData == null)
{
section.AdditionalData = new Dictionary<string, string>();
}
List<HomepageSection> source = await _pgService.GetAllAsync<HomepageSection>(_sectionsCollection);
section.DisplayOrder = (source.Any() ? (source.Max((HomepageSection s) => s.DisplayOrder) + 1) : 0);
section.CreatedAt = DateTime.UtcNow;
section.UpdatedAt = DateTime.UtcNow;
await _pgService.InsertAsync(_sectionsCollection, section);
base.TempData["SuccessMessage"] = "Section created successfully!";
return RedirectToAction("Index");
}
[HttpPost("section/delete/{id}")]
public async Task<IActionResult> DeleteSection(string id)
{
await _pgService.DeleteAsync<HomepageSection>(_sectionsCollection, id);
base.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++)
{
HomepageSection homepageSection = await _pgService.GetByIdAsync<HomepageSection>(_sectionsCollection, sectionIds[i]);
if (homepageSection != null)
{
homepageSection.DisplayOrder = i;
homepageSection.UpdatedAt = DateTime.UtcNow;
await _pgService.UpdateAsync(_sectionsCollection, homepageSection.Id, homepageSection);
}
}
return Json(new
{
success = true
});
}
[HttpPost("section/toggle/{id}")]
public async Task<IActionResult> ToggleSection(string id)
{
HomepageSection section = await _pgService.GetByIdAsync<HomepageSection>(_sectionsCollection, id);
if (section != null)
{
section.IsActive = !section.IsActive;
section.UpdatedAt = DateTime.UtcNow;
await _pgService.UpdateAsync(_sectionsCollection, section.Id, section);
base.TempData["SuccessMessage"] = "Section " + (section.IsActive ? "activated" : "deactivated") + " successfully!";
}
return RedirectToAction("Index");
}
[HttpPost("footer/update")]
public async Task<IActionResult> UpdateFooter(string footerText)
{
SiteSettings siteSettings = (await _pgService.GetAllAsync<SiteSettings>(_settingsCollection)).FirstOrDefault();
if (siteSettings == null)
{
siteSettings = new SiteSettings
{
FooterText = footerText
};
await _pgService.InsertAsync(_settingsCollection, siteSettings);
}
else
{
siteSettings.FooterText = footerText;
siteSettings.UpdatedAt = DateTime.UtcNow;
await _pgService.UpdateAsync(_settingsCollection, siteSettings.Id, siteSettings);
}
base.TempData["SuccessMessage"] = "Footer updated successfully!";
return RedirectToAction("Index");
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Route("admin/menu")]
[Authorize(Roles = "Admin,MasterAdmin")]
public class AdminMenuController : Controller
{
private readonly PostgreSQLService _pgService;
public AdminMenuController(PostgreSQLService pgService)
{
_pgService = pgService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
return View((await _pgService.GetAllAsync<MenuItem>("MenuItems")).OrderBy((MenuItem m) => m.DisplayOrder).ToList());
}
[HttpPost("reseed")]
public async Task<IActionResult> ReseedMenu()
{
foreach (MenuItem item in await _pgService.GetAllAsync<MenuItem>("MenuItems"))
{
await _pgService.DeleteAsync<MenuItem>("MenuItems", item.Id);
}
MenuItem[] array = new MenuItem[10]
{
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
}
};
MenuItem[] array2 = array;
foreach (MenuItem entity in array2)
{
await _pgService.InsertAsync("MenuItems", entity);
}
base.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 (!base.ModelState.IsValid)
{
return View(menuItem);
}
menuItem.CreatedAt = DateTime.UtcNow;
await _pgService.InsertAsync("MenuItems", menuItem);
base.TempData["SuccessMessage"] = "Menu item created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
MenuItem menuItem = await _pgService.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 (!base.ModelState.IsValid)
{
return View(menuItem);
}
menuItem.Id = id;
await _pgService.UpdateAsync("MenuItems", id, menuItem);
base.TempData["SuccessMessage"] = "Menu item updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _pgService.DeleteAsync<MenuItem>("MenuItems", id);
base.TempData["SuccessMessage"] = "Menu item deleted successfully!";
return RedirectToAction("Index");
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Route("admin/pages")]
[Authorize(Roles = "Admin,MasterAdmin")]
public class AdminPagesController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly SlugService _slugService;
private readonly string _pagesCollection = "Pages";
public AdminPagesController(PostgreSQLService pgService, SlugService slugService)
{
_pgService = pgService;
_slugService = slugService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
return View((await _pgService.GetAllAsync<Page>(_pagesCollection)).OrderBy((Page p) => p.PageName).ToList());
}
[HttpGet("create")]
public IActionResult Create()
{
return View(new Page());
}
[HttpPost("create")]
public async Task<IActionResult> Create(Page page)
{
if (!base.ModelState.IsValid)
{
return View(page);
}
page.CreatedAt = DateTime.UtcNow;
page.UpdatedAt = DateTime.UtcNow;
page.PageSlug = _slugService.GenerateSlug(page.PageName);
await _pgService.InsertAsync(_pagesCollection, page);
base.TempData["SuccessMessage"] = "Page created successfully!";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
Page page = await _pgService.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));
foreach (string key in form.Keys)
{
if (key.StartsWith("ImageGallery") || key.StartsWith("TeamMembers"))
{
Console.WriteLine($"[ADMIN-PAGES] {key} = {form[key]}");
}
}
if (!base.ModelState.IsValid)
{
Console.WriteLine("[ADMIN-PAGES] ModelState is INVALID");
foreach (ModelError item2 in base.ModelState.Values.SelectMany((ModelStateEntry v) => v.Errors))
{
Console.WriteLine("[ADMIN-PAGES] Error: " + item2.ErrorMessage);
}
return View(page);
}
Page page2 = await _pgService.GetByIdAsync<Page>(_pagesCollection, id);
if (page2 == null)
{
Console.WriteLine("[ADMIN-PAGES] Page not found: " + id);
return NotFound();
}
page2.PageName = page.PageName;
page2.Title = page.Title;
page2.Subtitle = page.Subtitle;
page2.Content = page.Content;
page2.IsActive = page.IsActive;
page2.UpdatedAt = DateTime.UtcNow;
page2.PageSlug = _slugService.GenerateSlug(page.PageName);
page2.AboutImage1 = form["AboutImage1"].ToString() ?? string.Empty;
page2.AboutImage2 = form["AboutImage2"].ToString() ?? string.Empty;
page2.ImageGallery = new List<string>();
foreach (string item3 in form.Keys.Where((string k) => k.StartsWith("ImageGallery[")))
{
string text = form[item3].ToString();
if (!string.IsNullOrEmpty(text))
{
page2.ImageGallery.Add(text);
}
}
page2.TeamMembers = new List<TeamMember>();
List<int> list = (from i in (from i in form.Keys.Where((string k) => k.StartsWith("TeamMembers[") && k.Contains("].Name")).Select(delegate(string k)
{
Match match = Regex.Match(k, "TeamMembers\\[(\\d+)\\]");
return (!match.Success) ? (-1) : int.Parse(match.Groups[1].Value);
})
where i >= 0
select i).Distinct()
orderby i
select i).ToList();
foreach (int item4 in list)
{
TeamMember item = new TeamMember
{
Name = form[$"TeamMembers[{item4}].Name"].ToString(),
Role = form[$"TeamMembers[{item4}].Role"].ToString(),
Bio = form[$"TeamMembers[{item4}].Bio"].ToString(),
PhotoUrl = form[$"TeamMembers[{item4}].PhotoUrl"].ToString()
};
page2.TeamMembers.Add(item);
}
Console.WriteLine($"[ADMIN-PAGES] Updating page: {page2.PageName} (Slug: {page2.PageSlug})");
Console.WriteLine("[ADMIN-PAGES] Title: " + page2.Title);
Console.WriteLine($"[ADMIN-PAGES] Content length: {page2.Content?.Length ?? 0}");
Console.WriteLine($"[ADMIN-PAGES] Image Gallery Count: {page2.ImageGallery.Count}");
Console.WriteLine($"[ADMIN-PAGES] Team Members Count: {page2.TeamMembers.Count}");
if (page2.ImageGallery.Any())
{
Console.WriteLine("[ADMIN-PAGES] Gallery Images: " + string.Join(", ", page2.ImageGallery));
}
if (page2.TeamMembers.Any())
{
foreach (TeamMember teamMember in page2.TeamMembers)
{
Console.WriteLine($"[ADMIN-PAGES] Team Member: {teamMember.Name} - {teamMember.Role} - Photo: {teamMember.PhotoUrl}");
}
}
await _pgService.UpdateAsync(_pagesCollection, id, page2);
base.TempData["SuccessMessage"] = "Page updated successfully!";
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _pgService.DeleteAsync<Page>(_pagesCollection, id);
base.TempData["SuccessMessage"] = "Page deleted successfully!";
return RedirectToAction("Index");
}
}

View File

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

View File

@@ -0,0 +1,265 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Route("admin/products")]
[Authorize(Roles = "Admin,MasterAdmin")]
public class AdminProductsController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly SlugService _slugService;
private readonly string _productsCollection = "Products";
public AdminProductsController(PostgreSQLService pgService, SlugService slugService)
{
_pgService = pgService;
_slugService = slugService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
return View((await _pgService.GetAllAsync<Product>(_productsCollection)).OrderByDescending((Product p) => p.CreatedAt).ToList());
}
[HttpGet("create")]
public IActionResult Create()
{
return View(new Product());
}
[HttpPost("create")]
public async Task<IActionResult> Create(Product product, string? ProductVariantsJson)
{
try
{
base.ModelState.Remove("ShortDescription");
base.ModelState.Remove("Description");
base.ModelState.Remove("ProductVariantsJson");
base.ModelState.Remove("Id");
base.ModelState.Remove("Slug");
base.ModelState.Remove("ImageUrl");
base.ModelState.Remove("Images");
if (!base.ModelState.IsValid)
{
foreach (ModelError item in base.ModelState.Values.SelectMany((ModelStateEntry v) => v.Errors))
{
Console.WriteLine("[CREATE] Validation Error: " + item.ErrorMessage);
}
return View(product);
}
}
catch (Exception ex)
{
Console.WriteLine("[CREATE] Error: " + ex.Message);
Console.WriteLine("[CREATE] Stack: " + ex.StackTrace);
base.TempData["ErrorMessage"] = "Error creating product: " + ex.Message;
return View(product);
}
if (!base.Request.Form.ContainsKey("IsActive"))
{
product.IsActive = false;
}
if (!base.Request.Form.ContainsKey("IsFeatured"))
{
product.IsFeatured = false;
}
if (!base.Request.Form.ContainsKey("IsTopSeller"))
{
product.IsTopSeller = false;
}
Console.WriteLine("[CREATE] ProductVariantsJson received: '" + (ProductVariantsJson ?? "NULL") + "'");
if (!string.IsNullOrEmpty(ProductVariantsJson))
{
try
{
product.Variants = JsonSerializer.Deserialize<List<ProductVariant>>(ProductVariantsJson) ?? new List<ProductVariant>();
Console.WriteLine($"[CREATE] Variants deserialized successfully: {product.Variants.Count} variants");
foreach (ProductVariant variant in product.Variants)
{
Console.WriteLine($" - {variant.ColorName} ({variant.ColorHex}) with {variant.Images?.Count ?? 0} images, Stock: {variant.StockQuantity}");
}
}
catch (Exception ex2)
{
Console.WriteLine("[CREATE] Error parsing variants: " + ex2.Message);
Console.WriteLine("[CREATE] JSON was: " + ProductVariantsJson);
product.Variants = new List<ProductVariant>();
}
}
else
{
Console.WriteLine("[CREATE] No variants provided - ProductVariantsJson is null or empty");
product.Variants = new List<ProductVariant>();
}
product.Colors = new List<string>();
product.Color = string.Empty;
try
{
product.CreatedAt = DateTime.UtcNow;
product.UpdatedAt = DateTime.UtcNow;
product.Slug = _slugService.GenerateSlug(product.Name);
await _pgService.InsertAsync(_productsCollection, product);
base.TempData["SuccessMessage"] = "Product created successfully!";
return RedirectToAction("Index");
}
catch (Exception ex3)
{
Console.WriteLine("[CREATE] Database Error: " + ex3.Message);
Console.WriteLine("[CREATE] Stack: " + ex3.StackTrace);
base.TempData["ErrorMessage"] = "Error saving product: " + ex3.Message;
return View(product);
}
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
Product product = await _pgService.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, string? ProductVariantsJson)
{
try
{
base.ModelState.Remove("Images");
base.ModelState.Remove("Slug");
base.ModelState.Remove("ShortDescription");
base.ModelState.Remove("Description");
base.ModelState.Remove("ProductVariantsJson");
base.ModelState.Remove("Id");
base.ModelState.Remove("ImageUrl");
if (!base.ModelState.IsValid)
{
foreach (ModelError item in base.ModelState.Values.SelectMany((ModelStateEntry v) => v.Errors))
{
Console.WriteLine("[EDIT] Validation Error: " + item.ErrorMessage);
}
return View("Create", product);
}
}
catch (Exception ex)
{
Console.WriteLine("[EDIT] Error: " + ex.Message);
Console.WriteLine("[EDIT] Stack: " + ex.StackTrace);
base.TempData["ErrorMessage"] = "Error updating product: " + ex.Message;
return View("Create", product);
}
if (!base.Request.Form.ContainsKey("IsActive"))
{
product.IsActive = false;
}
if (!base.Request.Form.ContainsKey("IsFeatured"))
{
product.IsFeatured = false;
}
if (!base.Request.Form.ContainsKey("IsTopSeller"))
{
product.IsTopSeller = false;
}
Product existingProduct = await _pgService.GetByIdAsync<Product>(_productsCollection, id);
if (existingProduct == null)
{
base.TempData["ErrorMessage"] = "Product not found.";
return RedirectToAction("Index");
}
existingProduct.Name = product.Name;
existingProduct.ShortDescription = product.ShortDescription;
existingProduct.Description = product.Description;
existingProduct.Price = product.Price;
existingProduct.Category = product.Category;
existingProduct.Color = product.Color;
Console.WriteLine("[EDIT] ProductVariantsJson received: '" + (ProductVariantsJson ?? "NULL") + "'");
if (!string.IsNullOrEmpty(ProductVariantsJson))
{
try
{
existingProduct.Variants = JsonSerializer.Deserialize<List<ProductVariant>>(ProductVariantsJson) ?? new List<ProductVariant>();
Console.WriteLine($"[EDIT] Variants deserialized successfully: {existingProduct.Variants.Count} variants");
foreach (ProductVariant variant in existingProduct.Variants)
{
Console.WriteLine($" - {variant.ColorName} ({variant.ColorHex}) with {variant.Images?.Count ?? 0} images, Stock: {variant.StockQuantity}");
}
}
catch (Exception ex2)
{
Console.WriteLine("[EDIT] Error parsing variants: " + ex2.Message);
Console.WriteLine("[EDIT] JSON was: " + ProductVariantsJson);
existingProduct.Variants = new List<ProductVariant>();
}
}
else
{
Console.WriteLine("[EDIT] No variants provided - ProductVariantsJson is null or empty");
existingProduct.Variants = new List<ProductVariant>();
}
existingProduct.Colors = new List<string>();
existingProduct.Color = string.Empty;
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);
if (base.Request.Form.ContainsKey("Images"))
{
List<string> list = (existingProduct.Images = (from img in base.Request.Form["Images"]
where !string.IsNullOrEmpty(img)
select (img)).ToList());
if (list.Any() && string.IsNullOrEmpty(product.ImageUrl))
{
existingProduct.ImageUrl = list[0] ?? "";
}
}
if (!string.IsNullOrEmpty(product.ImageUrl))
{
existingProduct.ImageUrl = product.ImageUrl;
}
if (!string.IsNullOrEmpty(product.SKU))
{
existingProduct.SKU = product.SKU;
}
if (product.CostPrice > 0m)
{
existingProduct.CostPrice = product.CostPrice;
}
try
{
await _pgService.UpdateAsync(_productsCollection, id, existingProduct);
base.TempData["SuccessMessage"] = "Product updated successfully!";
return RedirectToAction("Index");
}
catch (Exception ex3)
{
Console.WriteLine("[EDIT] Database Error: " + ex3.Message);
Console.WriteLine("[EDIT] Stack: " + ex3.StackTrace);
base.TempData["ErrorMessage"] = "Error saving product changes: " + ex3.Message;
return View("Create", existingProduct);
}
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _pgService.DeleteAsync<Product>(_productsCollection, id);
base.TempData["SuccessMessage"] = "Product deleted successfully!";
return RedirectToAction("Index");
}
}

View File

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

View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace SkyArtShop.Controllers;
[Route("admin/upload")]
[Authorize(Roles = "Admin,MasterAdmin")]
[IgnoreAntiforgeryToken]
public class AdminUploadController : Controller
{
private readonly IWebHostEnvironment _environment;
public AdminUploadController(IWebHostEnvironment environment)
{
_environment = environment;
}
[HttpGet("")]
public IActionResult Index()
{
string path = Path.Combine(_environment.WebRootPath, "uploads", "images");
List<string> model = new List<string>();
if (Directory.Exists(path))
{
List<string> list = (from f in Directory.GetFiles(path)
select "/uploads/images/" + Path.GetFileName(f) into f
orderby f descending
select f).ToList();
model = list;
}
return View(model);
}
[HttpPost("image")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
if (file == null || file.Length == 0L)
{
return Json(new
{
success = false,
message = "No file uploaded"
});
}
string[] source = new string[5] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
string value = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!source.Contains(value))
{
return Json(new
{
success = false,
message = "Invalid file type"
});
}
try
{
string text = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(text))
{
Directory.CreateDirectory(text);
}
string fileName = $"{Guid.NewGuid()}{value}";
string path = Path.Combine(text, fileName);
using FileStream stream = new FileStream(path, 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)
{
List<string> uploadedUrls = new List<string>();
foreach (IFormFile file in files)
{
if (file == null || file.Length == 0L)
{
continue;
}
string value = Path.GetExtension(file.FileName).ToLowerInvariant();
string[] source = new string[5] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
if (source.Contains(value))
{
string text = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(text))
{
Directory.CreateDirectory(text);
}
string fileName = $"{Guid.NewGuid()}{value}";
string path = Path.Combine(text, fileName);
using FileStream stream = new FileStream(path, 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
{
string fileName = Path.GetFileName(imageUrl);
string path = Path.Combine(_environment.WebRootPath, "uploads", "images", fileName);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
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
});
}
}
[HttpGet("list")]
public IActionResult ListImages()
{
string uploadsPath = Path.Combine(_environment.WebRootPath, "uploads", "images");
List<string> data = new List<string>();
if (Directory.Exists(uploadsPath))
{
List<string> list = (from f in Directory.GetFiles(uploadsPath)
select "/uploads/images/" + Path.GetFileName(f) into f
orderby System.IO.File.GetCreationTime(Path.Combine(uploadsPath, Path.GetFileName(f))) descending
select f).ToList();
data = list;
}
return Json(data);
}
[HttpPost("create-folder")]
public IActionResult CreateFolder([FromBody] string folderName)
{
try
{
if (string.IsNullOrWhiteSpace(folderName))
{
return Json(new
{
success = false,
message = "Folder name cannot be empty"
});
}
string text = string.Join("_", folderName.Split(Path.GetInvalidFileNameChars()));
string path = Path.Combine(_environment.WebRootPath, "uploads", "images", text);
if (Directory.Exists(path))
{
return Json(new
{
success = false,
message = "Folder already exists"
});
}
Directory.CreateDirectory(path);
return Json(new
{
success = true,
folderName = text
});
}
catch (Exception ex)
{
return Json(new
{
success = false,
message = ex.Message
});
}
}
[HttpPost("delete-folder")]
public IActionResult DeleteFolder([FromBody] string folderPath)
{
try
{
string path = Path.Combine(_environment.WebRootPath, "uploads", "images", folderPath);
if (!Directory.Exists(path))
{
return Json(new
{
success = false,
message = "Folder not found"
});
}
Directory.Delete(path, recursive: true);
return Json(new
{
success = true
});
}
catch (Exception ex)
{
return Json(new
{
success = false,
message = ex.Message
});
}
}
[HttpGet("list-folders")]
public IActionResult ListFolders()
{
try
{
string path = Path.Combine(_environment.WebRootPath, "uploads", "images");
List<object> data = new List<object>();
if (Directory.Exists(path))
{
var source = (from d in Directory.GetDirectories(path)
select new
{
name = Path.GetFileName(d),
path = Path.GetFileName(d),
fileCount = Directory.GetFiles(d).Length
}).ToList();
data = source.Cast<object>().ToList();
}
return Json(data);
}
catch (Exception ex)
{
return Json(new
{
success = false,
message = ex.Message
});
}
}
}

View File

@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
[Authorize(Roles = "Admin,MasterAdmin")]
[Route("admin/users")]
public class AdminUsersController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly PostgreAuthService _authService;
public AdminUsersController(PostgreSQLService pgService, PostgreAuthService authService)
{
_pgService = pgService;
_authService = authService;
}
[HttpGet("")]
public async Task<IActionResult> Index()
{
return View((await _pgService.GetAllAsync<AdminUser>("AdminUsers")).OrderBy((AdminUser u) => u.CreatedAt).ToList());
}
[HttpGet("create")]
public IActionResult Create()
{
base.ViewBag.Roles = GetAvailableRoles();
return View();
}
[HttpPost("create")]
public async Task<IActionResult> Create(AdminUser user, string password)
{
if (string.IsNullOrWhiteSpace(password))
{
base.ModelState.AddModelError("", "Password is required");
base.ViewBag.Roles = GetAvailableRoles();
return View(user);
}
if (await _authService.GetUserByEmailAsync(user.Email) != null)
{
base.ModelState.AddModelError("", "Email already exists");
base.ViewBag.Roles = GetAvailableRoles();
return View(user);
}
AdminUser adminUser = await _authService.CreateUserAsync(user.Email, password, user.Name, user.Role);
adminUser.Phone = user.Phone;
adminUser.Notes = user.Notes;
adminUser.Permissions = GetRolePermissions(user.Role);
adminUser.CreatedBy = base.User.Identity?.Name ?? "System";
adminUser.PasswordNeverExpires = user.PasswordNeverExpires;
adminUser.PasswordExpiresAt = (user.PasswordNeverExpires ? ((DateTime?)null) : new DateTime?(DateTime.UtcNow.AddDays(90.0)));
await _pgService.UpdateAsync("AdminUsers", adminUser.Id, adminUser);
base.TempData["Success"] = "User " + user.Name + " created successfully! They can now login.";
return RedirectToAction("Index");
}
[HttpGet("edit/{id}")]
public async Task<IActionResult> Edit(string id)
{
AdminUser adminUser = await _pgService.GetByIdAsync<AdminUser>("AdminUsers", id);
if (adminUser == null)
{
return NotFound();
}
base.ViewBag.Roles = GetAvailableRoles();
return View(adminUser);
}
[HttpPost("edit/{id}")]
public async Task<IActionResult> Edit(string id, AdminUser user, string? newPassword)
{
AdminUser adminUser = await _pgService.GetByIdAsync<AdminUser>("AdminUsers", id);
if (adminUser == null)
{
return NotFound();
}
adminUser.Name = user.Name;
adminUser.Email = user.Email;
adminUser.Role = user.Role;
adminUser.Phone = user.Phone;
adminUser.Notes = user.Notes;
adminUser.IsActive = user.IsActive;
adminUser.Permissions = GetRolePermissions(user.Role);
adminUser.PasswordNeverExpires = user.PasswordNeverExpires;
adminUser.PasswordExpiresAt = (user.PasswordNeverExpires ? ((DateTime?)null) : new DateTime?(DateTime.UtcNow.AddDays(90.0)));
if (!string.IsNullOrWhiteSpace(newPassword))
{
adminUser.PasswordHash = _authService.HashPassword(newPassword);
}
await _pgService.UpdateAsync("AdminUsers", id, adminUser);
if (!string.IsNullOrWhiteSpace(newPassword))
{
base.TempData["Success"] = "User " + user.Name + " and password updated successfully!";
}
else
{
base.TempData["Success"] = "User " + user.Name + " updated successfully!";
}
return RedirectToAction("Index");
}
[HttpPost("delete/{id}")]
public async Task<IActionResult> Delete(string id)
{
AdminUser user = await _pgService.GetByIdAsync<AdminUser>("AdminUsers", id);
if (user == null)
{
return NotFound();
}
if (user.Role == "MasterAdmin")
{
base.TempData["Error"] = "Cannot delete Master Admin!";
return RedirectToAction("Index");
}
await _pgService.DeleteAsync<AdminUser>("AdminUsers", id);
base.TempData["Success"] = "User " + user.Name + " deleted successfully!";
return RedirectToAction("Index");
}
[HttpGet("view/{id}")]
public async Task<IActionResult> ViewUser(string id)
{
AdminUser adminUser = await _pgService.GetByIdAsync<AdminUser>("AdminUsers", id);
if (adminUser == null)
{
return NotFound();
}
return View("View", adminUser);
}
private List<string> GetAvailableRoles()
{
return new List<string> { "MasterAdmin", "Admin", "Cashier", "Accountant" };
}
private List<string> GetRolePermissions(string role)
{
return role switch
{
"MasterAdmin" => new List<string>
{
"manage_users", "manage_products", "manage_orders", "manage_content", "manage_settings", "view_reports", "manage_finances", "manage_inventory", "manage_customers", "manage_blog",
"manage_portfolio", "manage_pages"
},
"Admin" => new List<string> { "manage_products", "manage_orders", "manage_content", "view_reports", "manage_inventory", "manage_customers", "manage_blog", "manage_portfolio", "manage_pages" },
"Cashier" => new List<string> { "view_products", "manage_orders", "view_customers", "process_payments" },
"Accountant" => new List<string> { "view_products", "view_orders", "view_reports", "manage_finances", "view_customers", "export_data" },
_ => new List<string>(),
};
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
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 == 0L)
{
return Json(new
{
success = false,
message = "No file uploaded"
});
}
string[] source = new string[5] { ".jpg", ".jpeg", ".png", ".gif", ".webp" };
string value = Path.GetExtension(image.FileName).ToLowerInvariant();
if (!source.Contains(value))
{
return Json(new
{
success = false,
message = "Invalid file type. Only images are allowed."
});
}
try
{
string text = Path.Combine(_environment.WebRootPath, "uploads", "images");
if (!Directory.Exists(text))
{
Directory.CreateDirectory(text);
}
string fileName = $"{Guid.NewGuid()}{value}";
string path = Path.Combine(text, fileName);
using (FileStream stream = new FileStream(path, FileMode.Create))
{
await image.CopyToAsync(stream);
}
string text2 = "/uploads/images/" + fileName;
Console.WriteLine("[API-UPLOAD] Image uploaded successfully: " + text2);
return Json(new
{
success = true,
imageUrl = text2
});
}
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,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class BlogController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly string _blogCollection = "BlogPosts";
public BlogController(PostgreSQLService pgService)
{
_pgService = pgService;
}
public async Task<IActionResult> Index()
{
List<BlogPost> model = (from p in await _pgService.GetAllAsync<BlogPost>(_blogCollection)
where p.IsPublished
orderby p.PublishedDate descending
select p).ToList();
return View(model);
}
public async Task<IActionResult> Post(string slug)
{
BlogPost blogPost = (await _pgService.GetAllAsync<BlogPost>(_blogCollection)).FirstOrDefault((BlogPost p) => p.Slug == slug && p.IsPublished);
if (blogPost == null)
{
return NotFound();
}
return View(blogPost);
}
}

View File

@@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class ContactController : Controller
{
private readonly PostgreSQLService _pgService;
public ContactController(PostgreSQLService pgService)
{
_pgService = pgService;
}
public async Task<IActionResult> Index()
{
SiteSettings model = (await _pgService.GetSiteSettingsAsync()) ?? new SiteSettings();
return View(model);
}
[HttpPost]
public IActionResult Submit(string name, string email, string phone, string subject, string message)
{
base.TempData["Success"] = "Thank you! Your message has been sent. We'll get back to you soon.";
return RedirectToAction("Index");
}
}

View File

@@ -0,0 +1,34 @@
using System.Linq;
using System.Threading.Tasks;
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 data = (await _mongoService.GetAllAsync<Product>("Products")).Select((Product p) => new
{
Id = p.Id,
Name = p.Name,
ImageUrl = 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(data);
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class HomeController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly string _productsCollection = "Products";
private readonly string _sectionsCollection = "HomepageSections";
public HomeController(PostgreSQLService pgService)
{
_pgService = pgService;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Index()
{
SiteSettings settings = await GetSiteSettings();
List<Product> topProducts = await GetTopSellerProducts();
List<HomepageSection> list = await GetHomepageSections();
base.ViewBag.Settings = settings;
base.ViewBag.TopProducts = topProducts;
base.ViewBag.Sections = list;
return View();
}
private async Task<SiteSettings> GetSiteSettings()
{
return (await _pgService.GetSiteSettingsAsync()) ?? new SiteSettings();
}
private async Task<List<Product>> GetTopSellerProducts()
{
return (await _pgService.GetAllAsync<Product>(_productsCollection)).Where((Product p) => p.IsTopSeller && p.IsActive).Take(4).ToList();
}
private async Task<List<HomepageSection>> GetHomepageSections()
{
List<HomepageSection> list = await _pgService.GetAllAsync<HomepageSection>(_sectionsCollection);
Console.WriteLine($"Total sections from DB: {list.Count}");
List<HomepageSection> list2 = (from s in list
where s.IsActive
orderby s.DisplayOrder
select s).ToList();
Console.WriteLine($"Active sections: {list2.Count}");
foreach (HomepageSection item in list2)
{
Console.WriteLine($"Section: {item.Title} | Type: {item.SectionType} | Order: {item.DisplayOrder} | Active: {item.IsActive}");
}
return list2;
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View();
}
}

View File

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

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class PortfolioController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly string _categoriesCollection = "PortfolioCategories";
private readonly string _projectsCollection = "PortfolioProjects";
public PortfolioController(PostgreSQLService pgService)
{
_pgService = pgService;
}
public async Task<IActionResult> Index()
{
List<PortfolioCategory> model = (from c in await _pgService.GetAllAsync<PortfolioCategory>(_categoriesCollection)
where c.IsActive
orderby c.DisplayOrder
select c).ToList();
return View(model);
}
public async Task<IActionResult> Category(string slug)
{
PortfolioCategory category = (await _pgService.GetAllAsync<PortfolioCategory>(_categoriesCollection)).FirstOrDefault((PortfolioCategory c) => c.Slug == slug && c.IsActive);
if (category == null)
{
return NotFound();
}
List<PortfolioProject> model = (from p in await _pgService.GetAllAsync<PortfolioProject>(_projectsCollection)
where p.CategoryId == category.Id && p.IsActive
orderby p.DisplayOrder
select p).ToList();
base.ViewBag.Category = category;
return View(model);
}
public async Task<IActionResult> Project(string id)
{
PortfolioProject portfolioProject = await _pgService.GetByIdAsync<PortfolioProject>(_projectsCollection, id);
if (portfolioProject == null)
{
return NotFound();
}
return View(portfolioProject);
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SkyArtShop.Models;
using SkyArtShop.Services;
namespace SkyArtShop.Controllers;
public class ShopController : Controller
{
private readonly PostgreSQLService _pgService;
private readonly string _productsCollection = "Products";
public ShopController(PostgreSQLService pgService)
{
_pgService = pgService;
}
public async Task<IActionResult> Index(string? category, string? sort)
{
List<Product> source = (await _pgService.GetAllAsync<Product>(_productsCollection)).Where((Product p) => p.IsActive).ToList();
if (!string.IsNullOrEmpty(category) && category != "all")
{
source = source.Where((Product p) => p.Category == category).ToList();
}
source = sort switch
{
"price-low" => source.OrderBy((Product p) => p.Price).ToList(),
"price-high" => source.OrderByDescending((Product p) => p.Price).ToList(),
"newest" => source.OrderByDescending((Product p) => p.CreatedAt).ToList(),
_ => source.OrderByDescending((Product p) => p.IsFeatured).ToList(),
};
base.ViewBag.SelectedCategory = category ?? "all";
base.ViewBag.SelectedSort = sort ?? "featured";
base.ViewBag.Categories = source.Select((Product p) => p.Category).Distinct().ToList();
return View(source);
}
public async Task<IActionResult> Product(string id)
{
Product product = await _pgService.GetByIdAsync<Product>(_productsCollection, id);
if (product == null)
{
return NotFound();
}
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"));
_ = base.HttpContext.Session.Id;
if (base.HttpContext.Connection.RemoteIpAddress?.ToString() == null)
{
}
List<Product> source = await _pgService.GetAllAsync<Product>(_productsCollection);
List<ProductView> source2 = new List<ProductView>();
Dictionary<string, int> productViewCounts = (from v in source2
group v by v.ProductId).ToDictionary((IGrouping<string, ProductView> g) => g.Key, (IGrouping<string, ProductView> g) => g.Count());
List<Product> list = (from p in source
where p.IsActive && p.Id != id
orderby ((p.Category == product.Category) ? 100 : 0) + (productViewCounts.ContainsKey(p.Id ?? "") ? productViewCounts[p.Id ?? ""] : 0) + (p.IsFeatured ? 50 : 0) + p.UnitsSold * 2 descending
select p).Take(4).ToList();
base.ViewBag.RelatedProducts = list;
return View(product);
}
}