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:
134
Services/AuthService.cs
Normal file
134
Services/AuthService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
67
Services/MongoDBService.cs
Normal file
67
Services/MongoDBService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
Services/MongoDBSettings.cs
Normal file
12
Services/MongoDBSettings.cs
Normal 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>();
|
||||
}
|
||||
120
Services/PostgreAuthService.cs
Normal file
120
Services/PostgreAuthService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
466
Services/PostgreSQLService.cs
Normal file
466
Services/PostgreSQLService.cs
Normal 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
20
Services/SlugService.cs
Normal 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('-');
|
||||
}
|
||||
}
|
||||
98
Services/SqlDataService.cs
Normal file
98
Services/SqlDataService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user