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

134
Services/AuthService.cs Normal file
View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using SkyArtShop.Models;
namespace SkyArtShop.Services;
public class AuthService
{
private readonly MongoDBService _mongoService;
public AuthService(MongoDBService mongoService)
{
_mongoService = mongoService;
}
public string HashPassword(string password)
{
using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
byte[] array = new byte[16];
randomNumberGenerator.GetBytes(array);
using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, array, 10000, HashAlgorithmName.SHA256);
byte[] bytes = rfc2898DeriveBytes.GetBytes(32);
byte[] array2 = new byte[48];
Array.Copy(array, 0, array2, 0, 16);
Array.Copy(bytes, 0, array2, 16, 32);
return Convert.ToBase64String(array2);
}
public bool VerifyPassword(string password, string hashedPassword)
{
try
{
byte[] array = Convert.FromBase64String(hashedPassword);
byte[] array2 = new byte[16];
Array.Copy(array, 0, array2, 0, 16);
using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, array2, 10000, HashAlgorithmName.SHA256);
byte[] bytes = rfc2898DeriveBytes.GetBytes(32);
for (int i = 0; i < 32; i++)
{
if (array[i + 16] != bytes[i])
{
return false;
}
}
return true;
}
catch
{
return false;
}
}
public async Task<AdminUser?> AuthenticateAsync(string email, string password)
{
AdminUser user = (await _mongoService.GetAllAsync<AdminUser>("AdminUsers")).FirstOrDefault((AdminUser u) => u.Email.ToLower() == email.ToLower() && u.IsActive);
if (user == null || !VerifyPassword(password, user.PasswordHash))
{
return null;
}
user.LastLogin = DateTime.UtcNow;
await _mongoService.UpdateAsync("AdminUsers", user.Id, user);
return user;
}
public ClaimsPrincipal CreateClaimsPrincipal(AdminUser user)
{
List<Claim> list = new List<Claim>
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", user.Id),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", user.Email),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", user.Name),
new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", user.Role)
};
foreach (string permission in user.Permissions)
{
list.Add(new Claim("Permission", permission));
}
ClaimsIdentity identity = new ClaimsIdentity(list, "Cookies");
return new ClaimsPrincipal(identity);
}
public async Task<AdminUser?> GetUserByIdAsync(string userId)
{
return await _mongoService.GetByIdAsync<AdminUser>("AdminUsers", userId);
}
public async Task<AdminUser?> GetUserByEmailAsync(string email)
{
return (await _mongoService.GetAllAsync<AdminUser>("AdminUsers")).FirstOrDefault((AdminUser u) => u.Email.ToLower() == email.ToLower());
}
public async Task<AdminUser> CreateUserAsync(string email, string password, string name, string role = "Admin")
{
AdminUser user = new AdminUser
{
Email = email,
PasswordHash = HashPassword(password),
Name = name,
Role = role,
IsActive = true,
CreatedAt = DateTime.UtcNow
};
await _mongoService.InsertAsync("AdminUsers", user);
return user;
}
public async Task<bool> ChangePasswordAsync(string userId, string oldPassword, string newPassword)
{
AdminUser adminUser = await GetUserByIdAsync(userId);
if (adminUser == null || !VerifyPassword(oldPassword, adminUser.PasswordHash))
{
return false;
}
adminUser.PasswordHash = HashPassword(newPassword);
await _mongoService.UpdateAsync("AdminUsers", userId, adminUser);
return true;
}
public async Task<bool> ResetPasswordAsync(string userId, string newPassword)
{
AdminUser adminUser = await GetUserByIdAsync(userId);
if (adminUser == null)
{
return false;
}
adminUser.PasswordHash = HashPassword(newPassword);
await _mongoService.UpdateAsync("AdminUsers", userId, adminUser);
return true;
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace SkyArtShop.Services;
public class MongoDBService
{
private readonly IMongoDatabase _database;
private readonly MongoDBSettings _settings;
public MongoDBService(IOptions<MongoDBSettings> settings)
{
_settings = settings.Value;
MongoClientSettings mongoClientSettings = MongoClientSettings.FromConnectionString(_settings.ConnectionString);
mongoClientSettings.MaxConnectionPoolSize = 500;
mongoClientSettings.MinConnectionPoolSize = 50;
mongoClientSettings.WaitQueueTimeout = TimeSpan.FromSeconds(30.0);
mongoClientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(10.0);
mongoClientSettings.ConnectTimeout = TimeSpan.FromSeconds(10.0);
mongoClientSettings.SocketTimeout = TimeSpan.FromSeconds(60.0);
MongoClient mongoClient = new MongoClient(mongoClientSettings);
_database = mongoClient.GetDatabase(_settings.DatabaseName);
}
public IMongoCollection<T> GetCollection<T>(string collectionName)
{
return _database.GetCollection<T>(collectionName);
}
public async Task<List<T>> GetAllAsync<T>(string collectionName)
{
IMongoCollection<T> collection = GetCollection<T>(collectionName);
return await collection.Find((T _) => true).ToListAsync();
}
public async Task<T> GetByIdAsync<T>(string collectionName, string id)
{
IMongoCollection<T> collection = GetCollection<T>(collectionName);
FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
return await collection.Find(filter).FirstOrDefaultAsync();
}
public async Task InsertAsync<T>(string collectionName, T document)
{
IMongoCollection<T> collection = GetCollection<T>(collectionName);
await collection.InsertOneAsync(document);
}
public async Task UpdateAsync<T>(string collectionName, string id, T document)
{
IMongoCollection<T> collection = GetCollection<T>(collectionName);
FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
await collection.ReplaceOneAsync(filter, document);
}
public async Task DeleteAsync<T>(string collectionName, string id)
{
IMongoCollection<T> collection = GetCollection<T>(collectionName);
FilterDefinition<T> filter = Builders<T>.Filter.Eq("_id", ObjectId.Parse(id));
await collection.DeleteOneAsync(filter);
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace SkyArtShop.Services;
public class MongoDBSettings
{
public string ConnectionString { get; set; } = string.Empty;
public string DatabaseName { get; set; } = string.Empty;
public Dictionary<string, string> Collections { get; set; } = new Dictionary<string, string>();
}

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using SkyArtShop.Models;
namespace SkyArtShop.Services;
public class PostgreAuthService
{
private readonly PostgreSQLService _pgService;
public PostgreAuthService(PostgreSQLService pgService)
{
_pgService = pgService;
}
public string HashPassword(string password)
{
using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create();
byte[] array = new byte[16];
randomNumberGenerator.GetBytes(array);
using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, array, 10000, HashAlgorithmName.SHA256);
byte[] bytes = rfc2898DeriveBytes.GetBytes(32);
byte[] array2 = new byte[48];
Array.Copy(array, 0, array2, 0, 16);
Array.Copy(bytes, 0, array2, 16, 32);
return Convert.ToBase64String(array2);
}
public bool VerifyPassword(string password, string hashedPassword)
{
try
{
if (hashedPassword.Contains(':'))
{
string[] array = hashedPassword.Split(':');
if (array.Length != 3)
{
return false;
}
int iterations = int.Parse(array[0]);
byte[] salt = Convert.FromBase64String(array[1]);
byte[] array2 = Convert.FromBase64String(array[2]);
byte[] array3 = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, iterations, HashAlgorithmName.SHA256, array2.Length);
return CryptographicOperations.FixedTimeEquals(array2, array3);
}
byte[] array4 = Convert.FromBase64String(hashedPassword);
byte[] array5 = new byte[16];
Array.Copy(array4, 0, array5, 0, 16);
using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, array5, 10000, HashAlgorithmName.SHA256);
byte[] bytes = rfc2898DeriveBytes.GetBytes(32);
for (int i = 0; i < 32; i++)
{
if (array4[i + 16] != bytes[i])
{
return false;
}
}
return true;
}
catch
{
return false;
}
}
public async Task<AdminUser?> AuthenticateAsync(string email, string password)
{
AdminUser user = await _pgService.GetUserByEmailAsync(email);
if (user == null || !VerifyPassword(password, user.PasswordHash))
{
return null;
}
await _pgService.UpdateUserLastLoginAsync(user.Id, DateTime.UtcNow);
user.LastLogin = DateTime.UtcNow;
return user;
}
public ClaimsPrincipal CreateClaimsPrincipal(AdminUser user)
{
List<Claim> list = new List<Claim>
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", user.Id),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", user.Email),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", user.Name),
new Claim("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", user.Role)
};
foreach (string permission in user.Permissions)
{
list.Add(new Claim("Permission", permission));
}
ClaimsIdentity identity = new ClaimsIdentity(list, "Cookies");
return new ClaimsPrincipal(identity);
}
public async Task<AdminUser?> GetUserByEmailAsync(string email)
{
return await _pgService.GetUserByEmailAsync(email);
}
public async Task<AdminUser> CreateUserAsync(string email, string password, string name, string role = "Admin")
{
AdminUser user = new AdminUser
{
Id = Guid.NewGuid().ToString(),
Email = email,
PasswordHash = HashPassword(password),
Name = name,
Role = role,
IsActive = true,
CreatedAt = DateTime.UtcNow,
Permissions = new List<string>()
};
await _pgService.CreateAdminUserAsync(user);
return user;
}
}

View File

@@ -0,0 +1,466 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
using Npgsql;
using NpgsqlTypes;
using SkyArtShop.Models;
namespace SkyArtShop.Services;
public class PostgreSQLService
{
private readonly string _connectionString;
public PostgreSQLService(string connectionString)
{
_connectionString = connectionString;
}
private async Task<NpgsqlConnection> GetConnectionAsync()
{
NpgsqlConnection conn = new NpgsqlConnection(_connectionString);
await conn.OpenAsync();
return conn;
}
public async Task<List<T>> GetAllAsync<T>(string tableName) where T : class, new()
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM " + tableName.ToLower();
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
List<T> results = new List<T>();
while (await reader.ReadAsync())
{
results.Add(MapToObject<T>(reader));
}
return results;
}
public async Task<T?> GetByIdAsync<T>(string tableName, string id) where T : class, new()
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM " + tableName.ToLower() + " WHERE id = @id";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", id);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapToObject<T>(reader);
}
return null;
}
public async Task<AdminUser?> GetUserByEmailAsync(string email)
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM adminusers WHERE LOWER(email) = LOWER(@email) AND isactive = true";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("email", email);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapToAdminUser(reader);
}
return null;
}
public async Task UpdateUserLastLoginAsync(string userId, DateTime lastLogin)
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "UPDATE adminusers SET lastlogin = @lastlogin WHERE id = @id";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", userId);
cmd.Parameters.AddWithValue("lastlogin", lastLogin);
await cmd.ExecuteNonQueryAsync();
}
public async Task CreateAdminUserAsync(AdminUser user)
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "INSERT INTO adminusers (id, email, passwordhash, name, role, permissions, isactive, createdby, createdat)\n VALUES (@id, @email, @passwordhash, @name, @role, @permissions::jsonb, @isactive, @createdby, @createdat)";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", Guid.NewGuid().ToString());
cmd.Parameters.AddWithValue("email", user.Email);
cmd.Parameters.AddWithValue("passwordhash", user.PasswordHash);
cmd.Parameters.AddWithValue("name", user.Name);
cmd.Parameters.AddWithValue("role", user.Role);
cmd.Parameters.AddWithValue("permissions", JsonSerializer.Serialize(user.Permissions));
cmd.Parameters.AddWithValue("isactive", user.IsActive);
cmd.Parameters.AddWithValue("createdby", user.CreatedBy);
cmd.Parameters.AddWithValue("createdat", user.CreatedAt);
await cmd.ExecuteNonQueryAsync();
}
public async Task<List<Product>> GetProductsAsync()
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM products WHERE isactive = true ORDER BY createdat DESC";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
List<Product> results = new List<Product>();
while (await reader.ReadAsync())
{
results.Add(MapToProduct(reader));
}
return results;
}
public async Task<Product?> GetProductBySlugAsync(string slug)
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM products WHERE slug = @slug AND isactive = true";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("slug", slug);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapToProduct(reader);
}
return null;
}
public async Task<Page?> GetPageBySlugAsync(string slug)
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM pages WHERE pageslug = @slug AND isactive = true";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("slug", slug);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapToPage(reader);
}
return null;
}
public async Task<SiteSettings?> GetSiteSettingsAsync()
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM sitesettings LIMIT 1";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return MapToSiteSettings(reader);
}
return null;
}
public async Task<List<MenuItem>> GetMenuItemsAsync()
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "SELECT * FROM menuitems WHERE isactive = true ORDER BY displayorder";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
using NpgsqlDataReader reader = await cmd.ExecuteReaderAsync();
List<MenuItem> results = new List<MenuItem>();
while (await reader.ReadAsync())
{
results.Add(MapToMenuItem(reader));
}
return results;
}
public async Task InsertAsync<T>(string tableName, T entity) where T : class
{
using NpgsqlConnection conn = await GetConnectionAsync();
Type typeFromHandle = typeof(T);
List<PropertyInfo> list = (from p in typeFromHandle.GetProperties()
where p.CanRead && p.Name != "Id"
select p).ToList();
PropertyInfo property = typeFromHandle.GetProperty("Id");
if (property != null && string.IsNullOrEmpty(property.GetValue(entity)?.ToString()))
{
property.SetValue(entity, Guid.NewGuid().ToString());
}
string value = string.Join(", ", list.Select((PropertyInfo p) => p.Name.ToLower()).Prepend("id"));
string value2 = string.Join(", ", list.Select((PropertyInfo p) => "@" + p.Name.ToLower()).Prepend("@id"));
string cmdText = $"INSERT INTO {tableName.ToLower()} ({value}) VALUES ({value2})";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", property?.GetValue(entity)?.ToString() ?? Guid.NewGuid().ToString());
foreach (PropertyInfo item in list)
{
object value3 = item.GetValue(entity);
string parameterName = item.Name.ToLower();
if (value3 == null)
{
cmd.Parameters.AddWithValue(parameterName, DBNull.Value);
}
else if (item.PropertyType == typeof(List<string>) || item.PropertyType == typeof(List<ProductVariant>) || item.PropertyType == typeof(List<TeamMember>) || item.PropertyType == typeof(Dictionary<string, string>))
{
cmd.Parameters.AddWithValue(parameterName, NpgsqlDbType.Jsonb, JsonSerializer.Serialize(value3));
}
else if (item.PropertyType == typeof(DateTime) || item.PropertyType == typeof(DateTime?))
{
cmd.Parameters.AddWithValue(parameterName, value3);
}
else if (item.PropertyType == typeof(bool) || item.PropertyType == typeof(bool?))
{
cmd.Parameters.AddWithValue(parameterName, value3);
}
else
{
cmd.Parameters.AddWithValue(parameterName, value3);
}
}
await cmd.ExecuteNonQueryAsync();
}
public async Task UpdateAsync<T>(string tableName, string id, T entity) where T : class
{
using NpgsqlConnection conn = await GetConnectionAsync();
Type typeFromHandle = typeof(T);
List<PropertyInfo> list = (from p in typeFromHandle.GetProperties()
where p.CanRead && p.Name != "Id"
select p).ToList();
string value = string.Join(", ", list.Select((PropertyInfo p) => p.Name.ToLower() + " = @" + p.Name.ToLower()));
string cmdText = $"UPDATE {tableName.ToLower()} SET {value} WHERE id = @id";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", id);
foreach (PropertyInfo item in list)
{
object value2 = item.GetValue(entity);
string parameterName = item.Name.ToLower();
if (value2 == null)
{
cmd.Parameters.AddWithValue(parameterName, DBNull.Value);
}
else if (item.PropertyType == typeof(List<string>) || item.PropertyType == typeof(List<ProductVariant>) || item.PropertyType == typeof(List<TeamMember>) || item.PropertyType == typeof(Dictionary<string, string>))
{
cmd.Parameters.AddWithValue(parameterName, NpgsqlDbType.Jsonb, JsonSerializer.Serialize(value2));
}
else if (item.PropertyType == typeof(DateTime) || item.PropertyType == typeof(DateTime?))
{
cmd.Parameters.AddWithValue(parameterName, value2);
}
else if (item.PropertyType == typeof(bool) || item.PropertyType == typeof(bool?))
{
cmd.Parameters.AddWithValue(parameterName, value2);
}
else
{
cmd.Parameters.AddWithValue(parameterName, value2);
}
}
await cmd.ExecuteNonQueryAsync();
}
public async Task DeleteAsync<T>(string tableName, string id) where T : class
{
using NpgsqlConnection conn = await GetConnectionAsync();
string cmdText = "DELETE FROM " + tableName.ToLower() + " WHERE id = @id";
using NpgsqlCommand cmd = new NpgsqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("id", id);
await cmd.ExecuteNonQueryAsync();
}
private T MapToObject<T>(NpgsqlDataReader reader) where T : class, new()
{
Type typeFromHandle = typeof(T);
if (typeFromHandle == typeof(AdminUser))
{
return (MapToAdminUser(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(Product))
{
return (MapToProduct(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(Page))
{
return (MapToPage(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(MenuItem))
{
return (MapToMenuItem(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(SiteSettings))
{
return (MapToSiteSettings(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(PortfolioCategory))
{
return (MapToPortfolioCategory(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(PortfolioProject))
{
return (MapToPortfolioProject(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(BlogPost))
{
return (MapToBlogPost(reader) as T) ?? new T();
}
if (typeFromHandle == typeof(HomepageSection))
{
return (MapToHomepageSection(reader) as T) ?? new T();
}
return new T();
}
private AdminUser MapToAdminUser(NpgsqlDataReader reader)
{
AdminUser adminUser = new AdminUser();
adminUser.Id = reader["id"].ToString();
adminUser.Email = reader["email"].ToString() ?? "";
adminUser.PasswordHash = reader["passwordhash"].ToString() ?? "";
adminUser.Name = reader["name"].ToString() ?? "";
adminUser.Role = reader["role"].ToString() ?? "Admin";
adminUser.Permissions = JsonSerializer.Deserialize<List<string>>(reader["permissions"].ToString() ?? "[]") ?? new List<string>();
adminUser.IsActive = (reader["isactive"] as bool?) ?? true;
adminUser.CreatedBy = reader["createdby"]?.ToString() ?? "";
adminUser.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
adminUser.LastLogin = (reader.IsDBNull(reader.GetOrdinal("lastlogin")) ? ((DateTime?)null) : new DateTime?(reader.GetDateTime(reader.GetOrdinal("lastlogin"))));
adminUser.Phone = reader["phone"]?.ToString() ?? "";
adminUser.Notes = reader["notes"]?.ToString() ?? "";
return adminUser;
}
private Product MapToProduct(NpgsqlDataReader reader)
{
Product product = new Product();
product.Id = reader["id"].ToString();
product.Name = reader["name"].ToString() ?? "";
product.Slug = reader["slug"].ToString() ?? "";
product.SKU = reader["sku"]?.ToString() ?? "";
product.ShortDescription = reader["shortdescription"]?.ToString() ?? "";
product.Description = reader["description"]?.ToString() ?? "";
product.Price = (reader["price"] as decimal?).GetValueOrDefault();
product.Category = reader["category"]?.ToString() ?? "";
product.Color = reader["color"]?.ToString() ?? "";
product.Colors = JsonSerializer.Deserialize<List<string>>(reader["colors"].ToString() ?? "[]") ?? new List<string>();
product.ImageUrl = reader["imageurl"]?.ToString() ?? "";
product.Images = JsonSerializer.Deserialize<List<string>>(reader["images"].ToString() ?? "[]") ?? new List<string>();
product.IsFeatured = reader["isfeatured"] as bool? == true;
product.IsTopSeller = reader["istopseller"] as bool? == true;
product.StockQuantity = (reader["stockquantity"] as int?).GetValueOrDefault();
product.IsActive = (reader["isactive"] as bool?) ?? true;
product.UnitsSold = (reader["unitssold"] as int?).GetValueOrDefault();
product.TotalRevenue = (reader["totalrevenue"] as decimal?).GetValueOrDefault();
product.AverageRating = (reader["averagerating"] as double?).GetValueOrDefault();
product.TotalReviews = (reader["totalreviews"] as int?).GetValueOrDefault();
product.CostPrice = (reader["costprice"] as decimal?).GetValueOrDefault();
product.Tags = JsonSerializer.Deserialize<List<string>>(reader["tags"].ToString() ?? "[]") ?? new List<string>();
product.MetaDescription = reader["metadescription"]?.ToString() ?? "";
product.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
product.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return product;
}
private Page MapToPage(NpgsqlDataReader reader)
{
Page page = new Page();
page.Id = reader["id"].ToString();
page.PageName = reader["pagename"].ToString() ?? "";
page.PageSlug = reader["pageslug"].ToString() ?? "";
page.Title = reader["title"]?.ToString() ?? "";
page.Subtitle = reader["subtitle"]?.ToString() ?? "";
page.HeroImage = reader["heroimage"]?.ToString() ?? "";
page.Content = reader["content"]?.ToString() ?? "";
page.MetaDescription = reader["metadescription"]?.ToString() ?? "";
page.ImageGallery = JsonSerializer.Deserialize<List<string>>(reader["imagegallery"].ToString() ?? "[]") ?? new List<string>();
page.IsActive = (reader["isactive"] as bool?) ?? true;
page.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
page.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return page;
}
private MenuItem MapToMenuItem(NpgsqlDataReader reader)
{
MenuItem menuItem = new MenuItem();
menuItem.Id = reader["id"].ToString();
menuItem.Label = reader["label"].ToString() ?? "";
menuItem.Url = reader["url"].ToString() ?? "";
menuItem.DisplayOrder = (reader["displayorder"] as int?).GetValueOrDefault();
menuItem.IsActive = (reader["isactive"] as bool?) ?? true;
menuItem.ShowInNavbar = (reader["showinnavbar"] as bool?) ?? true;
menuItem.ShowInDropdown = (reader["showindropdown"] as bool?) ?? true;
menuItem.OpenInNewTab = reader["openinnewtab"] as bool? == true;
menuItem.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
return menuItem;
}
private SiteSettings MapToSiteSettings(NpgsqlDataReader reader)
{
SiteSettings siteSettings = new SiteSettings();
siteSettings.Id = reader["id"].ToString();
siteSettings.SiteName = reader["sitename"]?.ToString() ?? "Sky Art Shop";
siteSettings.SiteTagline = reader["sitetagline"]?.ToString() ?? "";
siteSettings.ContactEmail = reader["contactemail"]?.ToString() ?? "";
siteSettings.ContactPhone = reader["contactphone"]?.ToString() ?? "";
siteSettings.InstagramUrl = reader["instagramurl"]?.ToString() ?? "#";
siteSettings.FooterText = reader["footertext"]?.ToString() ?? "";
siteSettings.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return siteSettings;
}
private PortfolioCategory MapToPortfolioCategory(NpgsqlDataReader reader)
{
PortfolioCategory portfolioCategory = new PortfolioCategory();
portfolioCategory.Id = reader["id"].ToString();
portfolioCategory.Name = reader["name"].ToString() ?? "";
portfolioCategory.Slug = reader["slug"].ToString() ?? "";
portfolioCategory.Description = reader["description"]?.ToString() ?? "";
portfolioCategory.ThumbnailImage = reader["thumbnailimage"]?.ToString() ?? "";
portfolioCategory.FeaturedImage = reader["featuredimage"]?.ToString() ?? "";
portfolioCategory.DisplayOrder = (reader["displayorder"] as int?).GetValueOrDefault();
portfolioCategory.IsActive = (reader["isactive"] as bool?) ?? true;
portfolioCategory.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
portfolioCategory.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return portfolioCategory;
}
private BlogPost MapToBlogPost(NpgsqlDataReader reader)
{
BlogPost blogPost = new BlogPost();
blogPost.Id = reader["id"].ToString();
blogPost.Title = reader["title"].ToString() ?? "";
blogPost.Slug = reader["slug"].ToString() ?? "";
blogPost.Content = reader["content"]?.ToString() ?? "";
blogPost.Excerpt = reader["excerpt"]?.ToString() ?? "";
blogPost.FeaturedImage = reader["featuredimage"]?.ToString() ?? "";
blogPost.Author = reader["author"]?.ToString() ?? "";
blogPost.Tags = JsonSerializer.Deserialize<List<string>>(reader["tags"].ToString() ?? "[]") ?? new List<string>();
blogPost.IsPublished = (reader["ispublished"] as bool?) ?? true;
blogPost.PublishedDate = (reader["publisheddate"] as DateTime?) ?? DateTime.UtcNow;
blogPost.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
blogPost.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return blogPost;
}
private HomepageSection MapToHomepageSection(NpgsqlDataReader reader)
{
HomepageSection homepageSection = new HomepageSection();
homepageSection.Id = reader["id"].ToString();
homepageSection.Title = reader["title"]?.ToString() ?? "";
homepageSection.Subtitle = reader["subtitle"]?.ToString() ?? "";
homepageSection.Content = reader["content"]?.ToString() ?? "";
homepageSection.SectionType = reader["sectiontype"]?.ToString() ?? "";
homepageSection.ImageUrl = reader["imageurl"]?.ToString() ?? "";
homepageSection.ButtonText = reader["buttontext"]?.ToString() ?? "";
homepageSection.ButtonUrl = reader["buttonurl"]?.ToString() ?? "";
homepageSection.IsActive = (reader["isactive"] as bool?) ?? true;
homepageSection.DisplayOrder = (reader["displayorder"] as int?).GetValueOrDefault();
homepageSection.AdditionalData = JsonSerializer.Deserialize<Dictionary<string, string>>(reader["additionaldata"]?.ToString() ?? "{}") ?? new Dictionary<string, string>();
homepageSection.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
homepageSection.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return homepageSection;
}
private PortfolioProject MapToPortfolioProject(NpgsqlDataReader reader)
{
PortfolioProject portfolioProject = new PortfolioProject();
portfolioProject.Id = reader["id"].ToString();
portfolioProject.CategoryId = reader["categoryid"]?.ToString() ?? "";
portfolioProject.Title = reader["title"]?.ToString() ?? "";
portfolioProject.Description = reader["description"]?.ToString() ?? "";
portfolioProject.FeaturedImage = reader["featuredimage"]?.ToString() ?? "";
portfolioProject.Images = JsonSerializer.Deserialize<List<string>>(reader["images"].ToString() ?? "[]") ?? new List<string>();
portfolioProject.DisplayOrder = (reader["displayorder"] as int?).GetValueOrDefault();
portfolioProject.IsActive = (reader["isactive"] as bool?) ?? true;
portfolioProject.CreatedAt = (reader["createdat"] as DateTime?) ?? DateTime.UtcNow;
portfolioProject.UpdatedAt = (reader["updatedat"] as DateTime?) ?? DateTime.UtcNow;
return portfolioProject;
}
}

20
Services/SlugService.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.Text.RegularExpressions;
namespace SkyArtShop.Services;
public class SlugService
{
public string GenerateSlug(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return string.Empty;
}
string text2 = text.ToLowerInvariant();
text2 = text2.Replace(" ", "-");
text2 = text2.Replace("&", "and");
text2 = Regex.Replace(text2, "[^a-z0-9\\-]", "");
text2 = Regex.Replace(text2, "-+", "-");
return text2.Trim('-');
}
}

View File

@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SkyArtShop.Data;
using SkyArtShop.Models;
namespace SkyArtShop.Services;
public class SqlDataService
{
private readonly SkyArtShopDbContext _context;
public SqlDataService(SkyArtShopDbContext context)
{
_context = context;
}
public async Task<List<T>> GetAllAsync<T>() where T : class
{
return await _context.Set<T>().ToListAsync();
}
public async Task<T?> GetByIdAsync<T>(string id) where T : class
{
return await _context.Set<T>().FindAsync(id);
}
public async Task<T> InsertAsync<T>(T entity) where T : class
{
_context.Set<T>().Add(entity);
await _context.SaveChangesAsync();
return entity;
}
public async Task<T> UpdateAsync<T>(T entity) where T : class
{
_context.Set<T>().Update(entity);
await _context.SaveChangesAsync();
return entity;
}
public async Task DeleteAsync<T>(string id) where T : class
{
T val = await GetByIdAsync<T>(id);
if (val != null)
{
_context.Set<T>().Remove(val);
await _context.SaveChangesAsync();
}
}
public async Task<AdminUser?> GetUserByEmailAsync(string email)
{
return await _context.AdminUsers.FirstOrDefaultAsync((AdminUser u) => u.Email.ToLower() == email.ToLower());
}
public async Task<List<Product>> GetFeaturedProductsAsync()
{
return await _context.Products.Where((Product p) => p.IsFeatured && p.IsActive).ToListAsync();
}
public async Task<List<Product>> GetTopSellersAsync(int count = 10)
{
return await (from p in _context.Products
where p.IsTopSeller && p.IsActive
orderby p.UnitsSold descending
select p).Take(count).ToListAsync();
}
public async Task<Product?> GetProductBySlugAsync(string slug)
{
return await _context.Products.FirstOrDefaultAsync((Product p) => p.Slug == slug && p.IsActive);
}
public async Task<BlogPost?> GetBlogPostBySlugAsync(string slug)
{
return await _context.BlogPosts.FirstOrDefaultAsync((BlogPost b) => b.Slug == slug && b.IsPublished);
}
public async Task<Page?> GetPageBySlugAsync(string slug)
{
return await _context.Pages.FirstOrDefaultAsync((Page p) => p.PageSlug == slug && p.IsActive);
}
public async Task<SiteSettings?> GetSiteSettingsAsync()
{
return await _context.SiteSettings.FirstOrDefaultAsync();
}
public async Task<List<MenuItem>> GetActiveMenuItemsAsync()
{
return await (from m in _context.MenuItems
where m.IsActive
orderby m.DisplayOrder
select m).ToListAsync();
}
}