chore: clean publish artifacts and add sources
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
bin/
|
||||
obj/
|
||||
publish/
|
||||
publish_old/
|
||||
*.log
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
wwwroot/uploads/
|
||||
56
change-admin-password.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Change Admin Password Script
|
||||
# Usage: ./change-admin-password.sh
|
||||
|
||||
echo "=========================================="
|
||||
echo "SkyArt Shop - Change Admin Password"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Get new password
|
||||
read -p "Enter new password (min 6 characters): " -s NEW_PASSWORD
|
||||
echo ""
|
||||
read -p "Confirm new password: " -s CONFIRM_PASSWORD
|
||||
echo ""
|
||||
echo ""
|
||||
|
||||
if [ "$NEW_PASSWORD" != "$CONFIRM_PASSWORD" ]; then
|
||||
echo "✗ Passwords do not match!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ${#NEW_PASSWORD} -lt 6 ]; then
|
||||
echo "✗ Password must be at least 6 characters!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call the emergency reset endpoint with the secret
|
||||
ADMIN_EMAIL="admin@skyartshop.com"
|
||||
SECRET="skyart-emergency-2025"
|
||||
|
||||
echo "Changing password for: $ADMIN_EMAIL"
|
||||
echo ""
|
||||
|
||||
# First, update the emergency reset endpoint to accept custom password
|
||||
# For now, we'll use the web endpoint
|
||||
RESULT=$(curl -k -s "https://localhost/admin/reset-password-emergency?confirm=yes-reset-now&secret=$SECRET")
|
||||
|
||||
if echo "$RESULT" | grep -q "Password Reset Successful"; then
|
||||
echo "✓ Password changed successfully!"
|
||||
echo ""
|
||||
echo "New credentials:"
|
||||
echo " Email: $ADMIN_EMAIL"
|
||||
echo " Password: Admin123!"
|
||||
echo ""
|
||||
echo "Note: The emergency reset sets password to 'Admin123!'"
|
||||
echo "After logging in, change it via:"
|
||||
echo " Admin Panel → Change Password"
|
||||
else
|
||||
echo "✗ Failed to reset password"
|
||||
echo "Try manually:"
|
||||
echo " curl -k 'https://localhost/admin/reset-password-emergency?confirm=yes-reset-now&secret=skyart-emergency-2025'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
78
check-admin-login.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Admin Login Test Script
|
||||
echo "=========================================="
|
||||
echo "Sky Art Shop - Admin Login Information"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
echo "Admin Login Credentials:"
|
||||
echo "------------------------"
|
||||
echo "Email: admin@skyartshop.com"
|
||||
echo "Password: ChangeThisPassword123!"
|
||||
echo ""
|
||||
|
||||
echo "Login URLs:"
|
||||
echo "-----------"
|
||||
echo "Local: http://192.168.10.130/admin/login"
|
||||
echo "Domain: http://skyarts.ddns.net/admin/login"
|
||||
echo ""
|
||||
|
||||
echo "Verifying Admin User..."
|
||||
echo "-----------------------"
|
||||
|
||||
# Check if Identity database exists
|
||||
if [ -f "/var/www/SkyArtShop/publish/identity.db" ]; then
|
||||
echo "✓ Identity database exists"
|
||||
|
||||
# Check if admin user exists
|
||||
USER_COUNT=$(sqlite3 /var/www/SkyArtShop/publish/identity.db "SELECT COUNT(*) FROM AspNetUsers WHERE Email='admin@skyartshop.com';" 2>/dev/null)
|
||||
|
||||
if [ "$USER_COUNT" = "1" ]; then
|
||||
echo "✓ Admin user exists in database"
|
||||
|
||||
# Get user details
|
||||
USER_INFO=$(sqlite3 /var/www/SkyArtShop/publish/identity.db "SELECT UserName, Email, DisplayName FROM AspNetUsers WHERE Email='admin@skyartshop.com';" 2>/dev/null)
|
||||
echo " User: $USER_INFO"
|
||||
|
||||
# Check if user has admin role
|
||||
ROLE_COUNT=$(sqlite3 /var/www/SkyArtShop/publish/identity.db "SELECT COUNT(*) FROM AspNetUserRoles ur JOIN AspNetRoles r ON ur.RoleId = r.Id JOIN AspNetUsers u ON ur.UserId = u.Id WHERE u.Email='admin@skyartshop.com' AND r.Name='Admin';" 2>/dev/null)
|
||||
|
||||
if [ "$ROLE_COUNT" = "1" ]; then
|
||||
echo "✓ Admin role assigned"
|
||||
else
|
||||
echo "✗ Admin role NOT assigned"
|
||||
fi
|
||||
else
|
||||
echo "✗ Admin user NOT found in database"
|
||||
fi
|
||||
else
|
||||
echo "✗ Identity database NOT found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Service Status:"
|
||||
echo "---------------"
|
||||
if systemctl is-active --quiet skyartshop.service; then
|
||||
echo "✓ SkyArtShop service is running"
|
||||
else
|
||||
echo "✗ SkyArtShop service is NOT running"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Testing Login Page:"
|
||||
echo "-------------------"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/admin/login)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "✓ Login page is accessible (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo "✗ Login page returned HTTP $HTTP_CODE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "If you still can't login, try:"
|
||||
echo "1. Clear browser cache/cookies"
|
||||
echo "2. Try incognito/private browsing mode"
|
||||
echo "3. Check browser console for errors (F12)"
|
||||
echo "=========================================="
|
||||
47
check-https-status.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# HTTPS & SSL Status Check for skyarts.ddns.net
|
||||
# Last Updated: December 6, 2025
|
||||
|
||||
echo "=========================================="
|
||||
echo "Sky Art Shop - HTTPS Status"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check certificate expiry
|
||||
echo "📜 SSL Certificate Status:"
|
||||
sudo certbot certificates 2>&1 | grep -A5 "skyarts.ddns.net" | grep -E "(Expiry|VALID)"
|
||||
|
||||
echo ""
|
||||
echo "🔒 Security Configuration:"
|
||||
echo " ✓ TLS 1.3 enabled"
|
||||
echo " ✓ Strong cipher: TLS_AES_256_GCM_SHA384"
|
||||
echo " ✓ HSTS enabled (max-age: 31536000)"
|
||||
echo " ✓ Let's Encrypt CA"
|
||||
echo " ✓ ECDSA certificate"
|
||||
|
||||
echo ""
|
||||
echo "🌐 Access URLs:"
|
||||
echo " Public: https://skyarts.ddns.net"
|
||||
echo " Admin: https://skyarts.ddns.net/admin/login"
|
||||
echo " Shop: https://skyarts.ddns.net/shop"
|
||||
echo " Blog: https://skyarts.ddns.net/blog"
|
||||
|
||||
echo ""
|
||||
echo "🔄 Auto-Renewal Status:"
|
||||
systemctl is-active certbot.timer >/dev/null 2>&1 && echo " ✓ Certbot timer active" || echo " ✗ Certbot timer inactive"
|
||||
echo " Next check: $(systemctl status certbot.timer --no-pager | grep Trigger | awk '{print $3, $4, $5}')"
|
||||
|
||||
echo ""
|
||||
echo "🛡️ Security Headers:"
|
||||
curl -s -I https://skyarts.ddns.net 2>&1 | grep -E "(Strict-Transport|X-Frame|X-Content|X-XSS)" | sed 's/^/ /'
|
||||
|
||||
echo ""
|
||||
echo "⚡ Services Status:"
|
||||
systemctl is-active nginx >/dev/null 2>&1 && echo " ✓ Nginx: Running" || echo " ✗ Nginx: Stopped"
|
||||
systemctl is-active skyartshop >/dev/null 2>&1 && echo " ✓ SkyArtShop: Running" || echo " ✗ SkyArtShop: Stopped"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ HTTPS fully operational!"
|
||||
echo "=========================================="
|
||||
78
check-users.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# User Management System Quick Reference
|
||||
echo "==========================================="
|
||||
echo "Sky Art Shop - User Management System"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
|
||||
echo "System Status:"
|
||||
echo "-------------"
|
||||
if systemctl is-active --quiet skyartshop.service; then
|
||||
echo "✓ Application is running"
|
||||
else
|
||||
echo "✗ Application is NOT running"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet mongod; then
|
||||
echo "✓ MongoDB is running"
|
||||
else
|
||||
echo "✗ MongoDB is NOT running"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Master Admin Credentials:"
|
||||
echo "------------------------"
|
||||
echo "Email: admin@skyartshop.com"
|
||||
echo "Password: ChangeThisPassword123!"
|
||||
echo ""
|
||||
|
||||
echo "User Management:"
|
||||
echo "---------------"
|
||||
echo "URL: http://192.168.10.130/admin/users"
|
||||
echo " (or http://skyarts.ddns.net/admin/users after port forwarding)"
|
||||
echo ""
|
||||
|
||||
echo "MongoDB - AdminUsers Collection:"
|
||||
echo "-------------------------------"
|
||||
USER_COUNT=$(mongosh SkyArtShopDB --eval "db.AdminUsers.countDocuments()" --quiet 2>/dev/null)
|
||||
echo "Total Users: $USER_COUNT"
|
||||
|
||||
if [ "$USER_COUNT" -gt "0" ]; then
|
||||
echo ""
|
||||
echo "Current Users:"
|
||||
mongosh SkyArtShopDB --eval "
|
||||
db.AdminUsers.find({}, {Name: 1, Email: 1, Role: 1, IsActive: 1, _id: 0}).forEach(function(user) {
|
||||
print(' - ' + user.Name + ' (' + user.Email + ') - Role: ' + user.Role + ' - Active: ' + user.IsActive);
|
||||
});
|
||||
" --quiet 2>/dev/null
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Available Roles:"
|
||||
echo "---------------"
|
||||
echo "1. MasterAdmin - Full system access, manage all users"
|
||||
echo "2. Admin - Manage products, orders, content, reports"
|
||||
echo "3. Cashier - Process orders and payments"
|
||||
echo "4. Accountant - View reports, manage finances"
|
||||
echo ""
|
||||
|
||||
echo "Quick Actions:"
|
||||
echo "-------------"
|
||||
echo "• Access User Management: http://192.168.10.130/admin/users"
|
||||
echo "• Create New User: http://192.168.10.130/admin/users/create"
|
||||
echo "• View Admin Dashboard: http://192.168.10.130/admin/dashboard"
|
||||
echo ""
|
||||
|
||||
echo "MongoDB Collections:"
|
||||
echo "-------------------"
|
||||
mongosh SkyArtShopDB --eval "
|
||||
print('Products: ' + db.Products.countDocuments());
|
||||
print('Pages: ' + db.Pages.countDocuments());
|
||||
print('MenuItems: ' + db.MenuItems.countDocuments());
|
||||
print('AdminUsers: ' + db.AdminUsers.countDocuments());
|
||||
print('SiteSettings: ' + db.SiteSettings.countDocuments());
|
||||
" --quiet 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "==========================================="
|
||||
157
cleanup-and-verify.sh
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SkyArtShop Cleanup and Verification Script
|
||||
# This script removes temporary files, verifies system health, and displays performance metrics
|
||||
|
||||
echo "========================================"
|
||||
echo " SkyArtShop Cleanup & Verification"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Step 1: Clean temporary files
|
||||
echo -e "${YELLOW}[1/7] Cleaning temporary files...${NC}"
|
||||
cd /var/www/SkyArtShop
|
||||
|
||||
# Remove backup files
|
||||
find . -type f \( -name "*.bak" -o -name "*.old" -o -name "*.broken" -o -name "*.tmp" \) -delete 2>/dev/null
|
||||
echo -e "${GREEN}✓ Temporary files removed${NC}"
|
||||
|
||||
# Remove empty directories
|
||||
find . -type d -empty -delete 2>/dev/null
|
||||
echo -e "${GREEN}✓ Empty directories removed${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 2: Clean build artifacts
|
||||
echo -e "${YELLOW}[2/7] Cleaning build artifacts...${NC}"
|
||||
rm -rf ./obj/Release/* 2>/dev/null
|
||||
rm -rf ./bin/Release/* 2>/dev/null
|
||||
echo -e "${GREEN}✓ Build artifacts cleaned${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 3: Check services
|
||||
echo -e "${YELLOW}[3/7] Checking services status...${NC}"
|
||||
SERVICES=("nginx" "mongod" "skyartshop")
|
||||
ALL_RUNNING=true
|
||||
|
||||
for service in "${SERVICES[@]}"; do
|
||||
if systemctl is-active --quiet "$service"; then
|
||||
echo -e "${GREEN}✓ $service is running${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ $service is not running${NC}"
|
||||
ALL_RUNNING=false
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Step 4: Test website connectivity
|
||||
echo -e "${YELLOW}[4/7] Testing website connectivity...${NC}"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost 2>/dev/null)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ Website responding (HTTP $HTTP_CODE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Website not responding (HTTP $HTTP_CODE)${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 5: Check MongoDB
|
||||
echo -e "${YELLOW}[5/7] Checking MongoDB connection...${NC}"
|
||||
if mongosh --quiet --eval "db.adminCommand('ping').ok" SkyArtShopDB >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ MongoDB connected${NC}"
|
||||
|
||||
# Get collection counts
|
||||
PRODUCTS=$(mongosh --quiet --eval "db.Products.countDocuments()" SkyArtShopDB 2>/dev/null)
|
||||
PAGES=$(mongosh --quiet --eval "db.Pages.countDocuments()" SkyArtShopDB 2>/dev/null)
|
||||
MENU_ITEMS=$(mongosh --quiet --eval "db.MenuItems.countDocuments()" SkyArtShopDB 2>/dev/null)
|
||||
ADMIN_USERS=$(mongosh --quiet --eval "db.AdminUsers.countDocuments()" SkyArtShopDB 2>/dev/null)
|
||||
|
||||
echo " - Products: $PRODUCTS"
|
||||
echo " - Pages: $PAGES"
|
||||
echo " - Menu Items: $MENU_ITEMS"
|
||||
echo " - Admin Users: $ADMIN_USERS"
|
||||
else
|
||||
echo -e "${RED}✗ MongoDB connection failed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 6: System resources
|
||||
echo -e "${YELLOW}[6/7] System resources:${NC}"
|
||||
echo "--- Memory ---"
|
||||
free -h | grep "^Mem:" | awk '{print " Used: " $3 " / " $2 " (" int($3/$2*100) "%)"}'
|
||||
echo ""
|
||||
|
||||
echo "--- CPU ---"
|
||||
CPU_COUNT=$(nproc)
|
||||
CPU_LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1)
|
||||
echo " Cores: $CPU_COUNT"
|
||||
echo " Load: $CPU_LOAD"
|
||||
echo ""
|
||||
|
||||
echo "--- Disk ---"
|
||||
df -h /var/www | tail -1 | awk '{print " Used: " $3 " / " $2 " (" $5 ")"}'
|
||||
echo ""
|
||||
|
||||
echo "--- Network Connections ---"
|
||||
CONNECTIONS=$(ss -s | grep TCP: | awk '{print $4}')
|
||||
echo " Active TCP: $CONNECTIONS"
|
||||
echo ""
|
||||
|
||||
# Step 7: Configuration verification
|
||||
echo -e "${YELLOW}[7/7] Configuration verification:${NC}"
|
||||
|
||||
# Check Nginx config
|
||||
if sudo nginx -t >/dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Nginx configuration valid${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Nginx configuration has errors${NC}"
|
||||
fi
|
||||
|
||||
# Check file permissions
|
||||
if [ -x "/var/www/SkyArtShop/publish/SkyArtShop.dll" ]; then
|
||||
echo -e "${GREEN}✓ Application files have correct permissions${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Application files may have permission issues${NC}"
|
||||
fi
|
||||
|
||||
# Check .NET runtime
|
||||
if command -v dotnet &> /dev/null; then
|
||||
DOTNET_VERSION=$(dotnet --version)
|
||||
echo -e "${GREEN}✓ .NET runtime installed ($DOTNET_VERSION)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ .NET runtime not found${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " Verification Complete"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Final summary
|
||||
if [ "$ALL_RUNNING" = true ] && [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ System is healthy and running optimally${NC}"
|
||||
echo ""
|
||||
echo "Access your website at:"
|
||||
echo " - Local: http://localhost"
|
||||
echo " - Domain: http://skyarts.ddns.net"
|
||||
echo ""
|
||||
echo "Admin Panel:"
|
||||
echo " - URL: http://skyarts.ddns.net/admin"
|
||||
echo " - User: admin@skyartshop.com"
|
||||
echo ""
|
||||
echo "For detailed performance info, see:"
|
||||
echo " /var/www/SkyArtShop/PERFORMANCE_OPTIMIZATION.md"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}⚠ System has issues that need attention${NC}"
|
||||
echo ""
|
||||
echo "Check logs with:"
|
||||
echo " sudo journalctl -u skyartshop.service -n 50"
|
||||
echo " sudo tail -f /var/log/nginx/error.log"
|
||||
exit 1
|
||||
fi
|
||||
214
deploy.ps1
Executable file
@@ -0,0 +1,214 @@
|
||||
# Sky Art Shop - Deployment Script
|
||||
# Run this script as Administrator
|
||||
|
||||
param(
|
||||
[string]$DeployPath = "C:\inetpub\wwwroot\skyartshop",
|
||||
[string]$SiteName = "SkyArtShop",
|
||||
[switch]$InstallIIS,
|
||||
[switch]$CreateSite,
|
||||
[switch]$UpdateOnly
|
||||
)
|
||||
|
||||
Write-Host "==================================" -ForegroundColor Cyan
|
||||
Write-Host "Sky Art Shop Deployment Script" -ForegroundColor Cyan
|
||||
Write-Host "==================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Check if running as Administrator
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
Write-Host "ERROR: This script must be run as Administrator!" -ForegroundColor Red
|
||||
Write-Host "Right-click PowerShell and select 'Run as Administrator'" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to install IIS
|
||||
function Install-IIS {
|
||||
Write-Host "Installing IIS features..." -ForegroundColor Yellow
|
||||
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServer -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-CommonHttpFeatures -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpErrors -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-ApplicationDevelopment -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-NetFxExtensibility45 -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HealthAndDiagnostics -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpLogging -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-Security -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-RequestFiltering -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-Performance -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerManagementTools -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-ManagementConsole -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-StaticContent -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-DefaultDocument -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-DirectoryBrowsing -NoRestart
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName IIS-HttpCompressionStatic -NoRestart
|
||||
|
||||
Write-Host "IIS features installed successfully!" -ForegroundColor Green
|
||||
Write-Host "NOTE: You may need to restart your computer." -ForegroundColor Yellow
|
||||
Write-Host "After restart, install .NET 8.0 Hosting Bundle from:" -ForegroundColor Yellow
|
||||
Write-Host "https://dotnet.microsoft.com/download/dotnet/8.0" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
# Function to publish application
|
||||
function Publish-Application {
|
||||
Write-Host "Publishing application..." -ForegroundColor Yellow
|
||||
|
||||
$projectPath = $PSScriptRoot
|
||||
|
||||
# Build and publish
|
||||
Set-Location $projectPath
|
||||
dotnet publish SkyArtShop.csproj -c Release -o $DeployPath
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Application published successfully to: $DeployPath" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "ERROR: Publishing failed!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Function to set permissions
|
||||
function Set-Permissions {
|
||||
Write-Host "Setting folder permissions..." -ForegroundColor Yellow
|
||||
|
||||
# Grant IIS permissions
|
||||
icacls $DeployPath /grant "IIS_IUSRS:(OI)(CI)F" /T
|
||||
icacls $DeployPath /grant "IUSR:(OI)(CI)F" /T
|
||||
|
||||
# Ensure uploads folder exists and has permissions
|
||||
$uploadsPath = Join-Path $DeployPath "wwwroot\uploads\images"
|
||||
if (-not (Test-Path $uploadsPath)) {
|
||||
New-Item -Path $uploadsPath -ItemType Directory -Force
|
||||
}
|
||||
icacls $uploadsPath /grant "IIS_IUSRS:(OI)(CI)F" /T
|
||||
|
||||
Write-Host "Permissions set successfully!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Function to create IIS site
|
||||
function Create-IISSite {
|
||||
Write-Host "Creating IIS site..." -ForegroundColor Yellow
|
||||
|
||||
Import-Module WebAdministration
|
||||
|
||||
# Check if site exists
|
||||
$existingSite = Get-Website -Name $SiteName -ErrorAction SilentlyContinue
|
||||
if ($existingSite) {
|
||||
Write-Host "Site '$SiteName' already exists. Updating..." -ForegroundColor Yellow
|
||||
Stop-Website -Name $SiteName -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
Remove-Website -Name $SiteName -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
# Check if default site is running on port 80
|
||||
$defaultSite = Get-Website | Where-Object { $_.Bindings.Collection.bindingInformation -like "*:80:*" -and $_.State -eq "Started" }
|
||||
if ($defaultSite -and $defaultSite.Name -ne $SiteName) {
|
||||
Write-Host "Stopping '$($defaultSite.Name)' which is using port 80..." -ForegroundColor Yellow
|
||||
Stop-Website -Name $defaultSite.Name -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Create new site
|
||||
New-Website -Name $SiteName -PhysicalPath $DeployPath -Port 80 -Force
|
||||
|
||||
# Configure application pool
|
||||
$appPool = Get-Item "IIS:\AppPools\$SiteName" -ErrorAction SilentlyContinue
|
||||
if ($appPool) {
|
||||
$appPool.managedRuntimeVersion = ""
|
||||
$appPool.startMode = "AlwaysRunning"
|
||||
$appPool | Set-Item
|
||||
}
|
||||
|
||||
# Wait a moment before starting
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Start site
|
||||
Start-Website -Name $SiteName -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Host "IIS site '$SiteName' created and started!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Function to configure firewall
|
||||
function Configure-Firewall {
|
||||
Write-Host "Configuring Windows Firewall..." -ForegroundColor Yellow
|
||||
|
||||
# Remove existing rules if they exist
|
||||
Remove-NetFirewallRule -DisplayName "SkyArtShop-HTTP" -ErrorAction SilentlyContinue
|
||||
|
||||
# Add HTTP rule
|
||||
New-NetFirewallRule -DisplayName "SkyArtShop-HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow
|
||||
|
||||
Write-Host "Firewall rules configured!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Main deployment flow
|
||||
try {
|
||||
if ($InstallIIS) {
|
||||
Install-IIS
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($UpdateOnly) {
|
||||
Write-Host "Performing update only..." -ForegroundColor Cyan
|
||||
|
||||
# Stop site
|
||||
Import-Module WebAdministration
|
||||
Stop-Website -Name $SiteName -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Publish
|
||||
Publish-Application
|
||||
|
||||
# Start site
|
||||
Start-Website -Name $SiteName
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Update completed successfully!" -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Full deployment
|
||||
Write-Host "Starting full deployment..." -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Step 1: Publish
|
||||
Publish-Application
|
||||
|
||||
# Step 2: Set permissions
|
||||
Set-Permissions
|
||||
|
||||
# Step 3: Create IIS site (if requested)
|
||||
if ($CreateSite) {
|
||||
Create-IISSite
|
||||
}
|
||||
|
||||
# Step 4: Configure firewall
|
||||
Configure-Firewall
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "==================================" -ForegroundColor Green
|
||||
Write-Host "Deployment completed successfully!" -ForegroundColor Green
|
||||
Write-Host "==================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host "1. Ensure MongoDB is running: net start MongoDB" -ForegroundColor White
|
||||
Write-Host "2. Test locally: http://localhost" -ForegroundColor White
|
||||
Write-Host "3. Configure your router port forwarding (port 80)" -ForegroundColor White
|
||||
Write-Host "4. Update No-IP DUC client" -ForegroundColor White
|
||||
Write-Host "5. Test from internet: http://your-noip-hostname.ddns.net" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Site location: $DeployPath" -ForegroundColor Cyan
|
||||
|
||||
if ($CreateSite) {
|
||||
Write-Host "IIS Site name: $SiteName" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
}
|
||||
catch {
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Deployment failed!" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
5
fail2ban-filter-admin.conf
Executable file
@@ -0,0 +1,5 @@
|
||||
# Fail2ban filter for SkyArtShop admin login attempts
|
||||
[Definition]
|
||||
failregex = ^<HOST> -.*"POST /admin/login HTTP.*" (401|403|400) .*$
|
||||
^<HOST> -.*"POST /admin/login.*Invalid.*$
|
||||
ignoreregex =
|
||||
6
fail2ban-filter-skyartshop.conf
Executable file
@@ -0,0 +1,6 @@
|
||||
# Fail2ban filter for SkyArtShop general attacks
|
||||
[Definition]
|
||||
failregex = ^<HOST> -.*"(GET|POST|HEAD).*HTTP.*" (404|444|403|400) .*$
|
||||
^<HOST> -.*"(GET|POST).*\.(php|asp|aspx|cgi|jsp).*HTTP.*" .*$
|
||||
^<HOST>.*"(union|select|insert|cast|set|declare|drop|update|md5|benchmark).*HTTP.*".*$
|
||||
ignoreregex =
|
||||
4
fail2ban-filter-sql.conf
Executable file
@@ -0,0 +1,4 @@
|
||||
# Fail2ban filter for SQL injection attempts
|
||||
[Definition]
|
||||
failregex = ^<HOST>.*"(union|select|insert|cast|set|declare|drop|update|delete|exec|script|alert|eval|base64|decode).*HTTP.*".*$
|
||||
ignoreregex =
|
||||
36
fail2ban-skyartshop.conf
Executable file
@@ -0,0 +1,36 @@
|
||||
# Fail2ban jail for SkyArtShop
|
||||
# Protects against brute force attacks and malicious access
|
||||
|
||||
[DEFAULT]
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
maxretry = 5
|
||||
banaction = iptables-multiport
|
||||
|
||||
[nginx-skyartshop]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-skyartshop
|
||||
logpath = /var/log/nginx/skyartshop_access.log
|
||||
/var/log/nginx/skyartshop_error.log
|
||||
maxretry = 10
|
||||
findtime = 300
|
||||
bantime = 3600
|
||||
|
||||
[nginx-skyartshop-admin]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-skyartshop-admin
|
||||
logpath = /var/log/nginx/skyartshop_access.log
|
||||
maxretry = 3
|
||||
findtime = 600
|
||||
bantime = 7200
|
||||
|
||||
[nginx-skyartshop-sql-injection]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-sql-injection
|
||||
logpath = /var/log/nginx/skyartshop_access.log
|
||||
maxretry = 2
|
||||
findtime = 300
|
||||
bantime = 86400
|
||||
59
fix-cookies.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fix Cookie/Authentication Issues
|
||||
# Run this if you experience login problems or "Service Unavailable" errors
|
||||
|
||||
echo "=========================================="
|
||||
echo "SkyArt Shop - Cookie Issue Fix"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Stop the service
|
||||
echo "1. Stopping SkyArtShop service..."
|
||||
sudo systemctl stop skyartshop
|
||||
|
||||
# Fix DataProtection keys directory
|
||||
echo "2. Fixing DataProtection keys directory..."
|
||||
KEYS_DIR="/var/www/SkyArtShop/publish/DataProtection-Keys"
|
||||
sudo mkdir -p "$KEYS_DIR"
|
||||
sudo chown -R www-data:www-data "$KEYS_DIR"
|
||||
sudo chmod 755 "$KEYS_DIR"
|
||||
echo " ✓ Keys directory fixed: $KEYS_DIR"
|
||||
|
||||
# Fix publish directory permissions
|
||||
echo "3. Fixing publish directory permissions..."
|
||||
sudo chown -R www-data:www-data /var/www/SkyArtShop/publish/
|
||||
echo " ✓ Permissions fixed"
|
||||
|
||||
# Restart the service
|
||||
echo "4. Starting SkyArtShop service..."
|
||||
sudo systemctl start skyartshop
|
||||
sleep 3
|
||||
|
||||
# Check service status
|
||||
if sudo systemctl is-active --quiet skyartshop; then
|
||||
echo " ✓ Service started successfully"
|
||||
else
|
||||
echo " ✗ Service failed to start. Check logs:"
|
||||
echo " sudo journalctl -u skyartshop -n 50"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Cookie Issue Fixed!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "What was fixed:"
|
||||
echo " • DataProtection keys directory created"
|
||||
echo " • Proper permissions set (www-data)"
|
||||
echo " • Service restarted"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Clear browser cookies/cache"
|
||||
echo " 2. Go to: https://skyarts.ddns.net/admin/login"
|
||||
echo " 3. Login with your credentials"
|
||||
echo ""
|
||||
echo "If you still have issues, reset password with:"
|
||||
echo " curl -k 'https://localhost/admin/reset-password-emergency?confirm=yes-reset-now&secret=skyart-emergency-2025'"
|
||||
echo ""
|
||||
20
fix-image-urls.js
Executable file
@@ -0,0 +1,20 @@
|
||||
// Fix Product Image URLs - Replace absolute URLs with relative paths
|
||||
// Run this in browser console on admin page: http://skyarts.ddns.net/admin
|
||||
|
||||
(async function () {
|
||||
console.log("🔧 Fixing product image URLs...");
|
||||
|
||||
// This would need to be run server-side to fix MongoDB
|
||||
// For now, manually edit the product with broken image through admin interface
|
||||
|
||||
console.log("⚠️ Manual fix required:");
|
||||
console.log("1. Go to Admin → Products");
|
||||
console.log(
|
||||
'2. Find product with image: "http://localhost:5001/uploads/images/de3ee948-a476-40a6-b31b-d226549b762d.jpg"'
|
||||
);
|
||||
console.log("3. Edit the product");
|
||||
console.log(
|
||||
'4. In ImageUrl field, change to: "/uploads/images/de3ee948-a476-40a6-b31b-d226549b762d.jpg"'
|
||||
);
|
||||
console.log("5. Save");
|
||||
})();
|
||||
250
initialize-database.csx
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env dotnet-script
|
||||
#r "nuget: MongoDB.Driver, 2.23.1"
|
||||
|
||||
using System;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Bson;
|
||||
|
||||
// Configuration
|
||||
var connectionString = "mongodb://localhost:27017";
|
||||
var databaseName = "SkyArtShopDB";
|
||||
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine("SkyArt Shop - Database Initialization");
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine();
|
||||
|
||||
try
|
||||
{
|
||||
var client = new MongoClient(connectionString);
|
||||
var database = client.GetDatabase(databaseName);
|
||||
|
||||
// 1. Add Sample Products
|
||||
Console.WriteLine("[1/4] Adding sample products...");
|
||||
var productsCollection = database.GetCollection<BsonDocument>("Products");
|
||||
var productCount = await productsCollection.CountDocumentsAsync(new BsonDocument());
|
||||
|
||||
if (productCount == 0)
|
||||
{
|
||||
var sampleProducts = new[]
|
||||
{
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Vintage Scrapbook Paper Set" },
|
||||
{ "Description", "Beautiful vintage-inspired scrapbook paper collection with 50 unique designs" },
|
||||
{ "Price", 24.99 },
|
||||
{ "Category", "Scrapbooking" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 50 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", true },
|
||||
{ "IsTopSeller", true },
|
||||
{ "Tags", new BsonArray { "vintage", "paper", "scrapbooking" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Journal Sticker Pack" },
|
||||
{ "Description", "Adorable stickers perfect for journaling and planning" },
|
||||
{ "Price", 12.99 },
|
||||
{ "Category", "Journaling" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 100 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", true },
|
||||
{ "IsTopSeller", true },
|
||||
{ "Tags", new BsonArray { "stickers", "journal", "cute" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Washi Tape Collection" },
|
||||
{ "Description", "Set of 20 colorful washi tapes for all your crafting needs" },
|
||||
{ "Price", 18.99 },
|
||||
{ "Category", "Supplies" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 75 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", true },
|
||||
{ "IsTopSeller", true },
|
||||
{ "Tags", new BsonArray { "washi", "tape", "colorful" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Card Making Kit" },
|
||||
{ "Description", "Complete kit for creating beautiful handmade cards" },
|
||||
{ "Price", 34.99 },
|
||||
{ "Category", "Cardmaking" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 30 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", true },
|
||||
{ "IsTopSeller", true },
|
||||
{ "Tags", new BsonArray { "cards", "kit", "crafts" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Premium Journaling Pens" },
|
||||
{ "Description", "Set of fine-tip pens perfect for detailed journaling" },
|
||||
{ "Price", 15.99 },
|
||||
{ "Category", "Journaling" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 60 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", false },
|
||||
{ "IsTopSeller", false },
|
||||
{ "Tags", new BsonArray { "pens", "journal", "writing" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "Name", "Collage Paper Bundle" },
|
||||
{ "Description", "Mixed media papers perfect for collage projects" },
|
||||
{ "Price", 22.99 },
|
||||
{ "Category", "Collaging" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "StockQuantity", 40 },
|
||||
{ "IsActive", true },
|
||||
{ "IsFeatured", false },
|
||||
{ "IsTopSeller", false },
|
||||
{ "Tags", new BsonArray { "collage", "paper", "mixed media" } },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
}
|
||||
};
|
||||
|
||||
await productsCollection.InsertManyAsync(sampleProducts);
|
||||
Console.WriteLine($" ✓ Added {sampleProducts.Length} sample products");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" ⚠ Products already exist ({productCount} products found)");
|
||||
}
|
||||
|
||||
// 2. Add Homepage Sections
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("[2/4] Adding homepage sections...");
|
||||
var sectionsCollection = database.GetCollection<BsonDocument>("HomepageSections");
|
||||
var sectionCount = await sectionsCollection.CountDocumentsAsync(new BsonDocument());
|
||||
|
||||
if (sectionCount == 0)
|
||||
{
|
||||
var sampleSections = new[]
|
||||
{
|
||||
new BsonDocument
|
||||
{
|
||||
{ "SectionType", "hero" },
|
||||
{ "Title", "Welcome to Sky Art Shop" },
|
||||
{ "Subtitle", "Your One-Stop Shop for Creative Supplies" },
|
||||
{ "Content", "Discover beautiful scrapbooking, journaling, and crafting materials" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "ButtonText", "Shop Now" },
|
||||
{ "ButtonUrl", "/Shop" },
|
||||
{ "DisplayOrder", 1 },
|
||||
{ "IsActive", true },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "SectionType", "collection" },
|
||||
{ "Title", "Featured Collections" },
|
||||
{ "Subtitle", "Explore Our Curated Collections" },
|
||||
{ "Content", "Hand-picked items for your creative projects" },
|
||||
{ "ImageUrl", "" },
|
||||
{ "ButtonText", "View All" },
|
||||
{ "ButtonUrl", "/Shop" },
|
||||
{ "DisplayOrder", 2 },
|
||||
{ "IsActive", true },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
},
|
||||
new BsonDocument
|
||||
{
|
||||
{ "SectionType", "promotion" },
|
||||
{ "Title", "Special Offers" },
|
||||
{ "Subtitle", "Don't Miss Our Latest Deals" },
|
||||
{ "Content", "Save up to 30% on selected items" },
|
||||
{ "ImageUrl", "/uploads/images/8ba675b9-c4e6-41e6-8f14-382b9ee1d019.jpg" },
|
||||
{ "ButtonText", "Shop Sale" },
|
||||
{ "ButtonUrl", "/Shop" },
|
||||
{ "DisplayOrder", 3 },
|
||||
{ "IsActive", true },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
}
|
||||
};
|
||||
|
||||
await sectionsCollection.InsertManyAsync(sampleSections);
|
||||
Console.WriteLine($" ✓ Added {sampleSections.Length} homepage sections");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" ⚠ Homepage sections already exist ({sectionCount} sections found)");
|
||||
}
|
||||
|
||||
// 3. Verify Site Settings
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("[3/4] Checking site settings...");
|
||||
var settingsCollection = database.GetCollection<BsonDocument>("SiteSettings");
|
||||
var settingsCount = await settingsCollection.CountDocumentsAsync(new BsonDocument());
|
||||
|
||||
if (settingsCount == 0)
|
||||
{
|
||||
var siteSettings = new BsonDocument
|
||||
{
|
||||
{ "SiteName", "Sky Art Shop" },
|
||||
{ "SiteTagline", "Scrapbooking, Journaling & Crafting Supplies" },
|
||||
{ "ContactEmail", "info@skyartshop.com" },
|
||||
{ "ContactPhone", "+501 608-0409" },
|
||||
{ "InstagramUrl", "https://instagram.com/skyartshop" },
|
||||
{ "FooterText", "© 2025 by Sky Art Shop. All rights reserved." },
|
||||
{ "UpdatedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
await settingsCollection.InsertOneAsync(siteSettings);
|
||||
Console.WriteLine(" ✓ Added site settings");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($" ✓ Site settings exist");
|
||||
}
|
||||
|
||||
// 4. Verify Menu Items
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("[4/4] Checking navigation menu...");
|
||||
var menuCollection = database.GetCollection<BsonDocument>("MenuItems");
|
||||
var menuCount = await menuCollection.CountDocumentsAsync(new BsonDocument());
|
||||
Console.WriteLine($" ✓ {menuCount} menu items found");
|
||||
|
||||
// Summary
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine("✓ Database Initialization Complete!");
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine();
|
||||
|
||||
var finalProductCount = await productsCollection.CountDocumentsAsync(new BsonDocument());
|
||||
var finalSectionCount = await sectionsCollection.CountDocumentsAsync(new BsonDocument());
|
||||
|
||||
Console.WriteLine($"Products: {finalProductCount}");
|
||||
Console.WriteLine($"Homepage Sections: {finalSectionCount}");
|
||||
Console.WriteLine($"Menu Items: {menuCount}");
|
||||
Console.WriteLine($"Site Settings: {settingsCount}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Website should now display properly!");
|
||||
Console.WriteLine();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"✗ Error: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
119
install-prerequisites.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# Prerequisites Installation Script for New Server
|
||||
|
||||
echo "========================================="
|
||||
echo " Installing SkyArtShop Prerequisites"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${YELLOW}This script needs sudo privileges.${NC}"
|
||||
echo "It will prompt for password when needed."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Update system
|
||||
echo -e "${YELLOW}[1/6] Updating system packages...${NC}"
|
||||
sudo apt update
|
||||
echo -e "${GREEN}✓ System updated${NC}"
|
||||
echo ""
|
||||
|
||||
# Install .NET 8.0
|
||||
echo -e "${YELLOW}[2/6] Installing .NET 8.0 SDK...${NC}"
|
||||
if command -v dotnet &> /dev/null; then
|
||||
echo -e "${GREEN}✓ .NET already installed: $(dotnet --version)${NC}"
|
||||
else
|
||||
wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh
|
||||
chmod +x /tmp/dotnet-install.sh
|
||||
/tmp/dotnet-install.sh --channel 8.0
|
||||
|
||||
# Add to PATH
|
||||
export DOTNET_ROOT=$HOME/.dotnet
|
||||
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
|
||||
|
||||
# Add to bashrc for persistence
|
||||
if ! grep -q "DOTNET_ROOT" ~/.bashrc; then
|
||||
echo 'export DOTNET_ROOT=$HOME/.dotnet' >> ~/.bashrc
|
||||
echo 'export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools' >> ~/.bashrc
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ .NET 8.0 installed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Install MongoDB
|
||||
echo -e "${YELLOW}[3/6] Installing MongoDB...${NC}"
|
||||
if command -v mongod &> /dev/null; then
|
||||
echo -e "${GREEN}✓ MongoDB already installed${NC}"
|
||||
else
|
||||
# Import MongoDB public key
|
||||
curl -fsSL https://pgp.mongodb.com/server-7.0.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg
|
||||
|
||||
# Add MongoDB repository
|
||||
echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
|
||||
|
||||
# Install MongoDB
|
||||
sudo apt update
|
||||
sudo apt install -y mongodb-org
|
||||
|
||||
# Start and enable MongoDB
|
||||
sudo systemctl start mongod
|
||||
sudo systemctl enable mongod
|
||||
|
||||
echo -e "${GREEN}✓ MongoDB installed and started${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Install Nginx
|
||||
echo -e "${YELLOW}[4/6] Installing Nginx...${NC}"
|
||||
if command -v nginx &> /dev/null; then
|
||||
echo -e "${GREEN}✓ Nginx already installed${NC}"
|
||||
else
|
||||
sudo apt install -y nginx
|
||||
sudo systemctl enable nginx
|
||||
echo -e "${GREEN}✓ Nginx installed${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Install utilities
|
||||
echo -e "${YELLOW}[5/6] Installing utilities...${NC}"
|
||||
sudo apt install -y git curl wget unzip tar
|
||||
echo -e "${GREEN}✓ Utilities installed${NC}"
|
||||
echo ""
|
||||
|
||||
# Configure firewall
|
||||
echo -e "${YELLOW}[6/6] Configuring firewall...${NC}"
|
||||
if command -v ufw &> /dev/null; then
|
||||
sudo ufw allow 'Nginx Full'
|
||||
sudo ufw allow OpenSSH
|
||||
sudo ufw --force enable
|
||||
echo -e "${GREEN}✓ Firewall configured${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ UFW not available, skipping firewall configuration${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Verify installations
|
||||
echo "========================================="
|
||||
echo " ✅ INSTALLATION COMPLETE"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Installed versions:"
|
||||
echo " .NET SDK: $(dotnet --version 2>/dev/null || echo 'Not found')"
|
||||
echo " MongoDB: $(mongod --version 2>/dev/null | head -1 | awk '{print $3}' || echo 'Not found')"
|
||||
echo " Nginx: $(nginx -v 2>&1 | cut -d'/' -f2 || echo 'Not found')"
|
||||
echo ""
|
||||
echo "Service status:"
|
||||
systemctl is-active --quiet mongod && echo -e " MongoDB: ${GREEN}Running${NC}" || echo -e " MongoDB: ${RED}Stopped${NC}"
|
||||
systemctl is-active --quiet nginx && echo -e " Nginx: ${GREEN}Running${NC}" || echo -e " Nginx: ${RED}Stopped${NC}"
|
||||
echo ""
|
||||
echo "You can now run the restoration script:"
|
||||
echo -e " ${YELLOW}bash migrate-restore.sh${NC}"
|
||||
echo ""
|
||||
159
install-sqlserver-docker.sh
Executable file
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SQL Server Docker Installation Script
|
||||
# This is a more reliable method for running SQL Server on Ubuntu 24.04
|
||||
|
||||
set -e
|
||||
|
||||
echo "=================================="
|
||||
echo "SQL Server 2022 Docker Installation"
|
||||
echo "=================================="
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install Docker if not already installed
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Installing Docker..."
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl gnupg lsb-release
|
||||
|
||||
# Add Docker's official GPG key
|
||||
install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# Add Docker repository
|
||||
echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
# Install Docker Engine
|
||||
apt-get update
|
||||
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
|
||||
# Start Docker
|
||||
systemctl start docker
|
||||
systemctl enable docker
|
||||
|
||||
echo "✅ Docker installed successfully"
|
||||
else
|
||||
echo "✅ Docker is already installed"
|
||||
fi
|
||||
|
||||
# Set SA password
|
||||
echo ""
|
||||
read -sp "Enter SA password (min 8 chars, uppercase, lowercase, digits, symbols): " SA_PASSWORD
|
||||
echo ""
|
||||
read -sp "Confirm SA password: " SA_PASSWORD_CONFIRM
|
||||
echo ""
|
||||
|
||||
if [ "$SA_PASSWORD" != "$SA_PASSWORD_CONFIRM" ]; then
|
||||
echo "❌ Passwords do not match!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate password strength
|
||||
if [ ${#SA_PASSWORD} -lt 8 ]; then
|
||||
echo "❌ Password must be at least 8 characters!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stop and remove existing container if it exists
|
||||
if [ "$(docker ps -aq -f name=sqlserver)" ]; then
|
||||
echo "Removing existing SQL Server container..."
|
||||
docker stop sqlserver 2>/dev/null || true
|
||||
docker rm sqlserver 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Create data directory
|
||||
mkdir -p /var/opt/mssql/data
|
||||
mkdir -p /var/opt/mssql/log
|
||||
mkdir -p /var/opt/mssql/backup
|
||||
|
||||
# Run SQL Server container
|
||||
echo ""
|
||||
echo "Starting SQL Server 2022 container..."
|
||||
docker run -e "ACCEPT_EULA=Y" \
|
||||
-e "MSSQL_SA_PASSWORD=$SA_PASSWORD" \
|
||||
-e "MSSQL_PID=Express" \
|
||||
-p 1433:1433 \
|
||||
--name sqlserver \
|
||||
--hostname sqlserver \
|
||||
-v /var/opt/mssql/data:/var/opt/mssql/data \
|
||||
-v /var/opt/mssql/log:/var/opt/mssql/log \
|
||||
-v /var/opt/mssql/backup:/var/opt/mssql/backup \
|
||||
--restart unless-stopped \
|
||||
-d \
|
||||
mcr.microsoft.com/mssql/server:2022-latest
|
||||
|
||||
# Wait for SQL Server to start
|
||||
echo ""
|
||||
echo "Waiting for SQL Server to start..."
|
||||
sleep 15
|
||||
|
||||
# Check container status
|
||||
if docker ps | grep -q sqlserver; then
|
||||
echo "✅ SQL Server container is running"
|
||||
else
|
||||
echo "❌ SQL Server container failed to start"
|
||||
echo "Check logs with: docker logs sqlserver"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test connection
|
||||
echo ""
|
||||
echo "Testing connection..."
|
||||
docker exec sqlserver /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U sa -P "$SA_PASSWORD" -C \
|
||||
-Q "SELECT @@VERSION" &>/dev/null
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Connection successful!"
|
||||
else
|
||||
echo "⚠️ Connection test failed, but container is running"
|
||||
echo "You may need to wait a bit longer for SQL Server to fully initialize"
|
||||
fi
|
||||
|
||||
# Configure firewall
|
||||
if command -v ufw &> /dev/null; then
|
||||
echo ""
|
||||
echo "Configuring firewall..."
|
||||
ufw allow 1433/tcp
|
||||
echo "✅ Firewall configured"
|
||||
fi
|
||||
|
||||
# Set container to start on boot
|
||||
systemctl enable docker
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Installation Complete!"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "SQL Server 2022 is running in Docker"
|
||||
echo ""
|
||||
echo "Connection Information:"
|
||||
echo " Server: localhost,1433"
|
||||
echo " Username: sa"
|
||||
echo " Password: (the one you just set)"
|
||||
echo ""
|
||||
echo "Connection String for appsettings.json:"
|
||||
echo "Server=localhost,1433;Database=SkyArtShopDB;User Id=sa;Password=YOUR_PASSWORD;TrustServerCertificate=True;MultipleActiveResultSets=True"
|
||||
echo ""
|
||||
echo "Useful Docker commands:"
|
||||
echo " docker ps - List running containers"
|
||||
echo " docker logs sqlserver - View SQL Server logs"
|
||||
echo " docker stop sqlserver - Stop SQL Server"
|
||||
echo " docker start sqlserver - Start SQL Server"
|
||||
echo " docker restart sqlserver - Restart SQL Server"
|
||||
echo " docker exec -it sqlserver bash - Access container shell"
|
||||
echo ""
|
||||
echo "Data persistence:"
|
||||
echo " Database files: /var/opt/mssql/data"
|
||||
echo " Log files: /var/opt/mssql/log"
|
||||
echo " Backups: /var/opt/mssql/backup"
|
||||
echo ""
|
||||
118
install-sqlserver.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SQL Server 2022 Installation Script for Ubuntu
|
||||
# This script installs Microsoft SQL Server 2022 on Ubuntu
|
||||
|
||||
set -e
|
||||
|
||||
echo "=================================="
|
||||
echo "SQL Server 2022 Installation"
|
||||
echo "=================================="
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get Ubuntu version
|
||||
UBUNTU_VERSION=$(lsb_release -rs)
|
||||
echo "Detected Ubuntu version: $UBUNTU_VERSION"
|
||||
|
||||
# Import the public repository GPG keys
|
||||
echo "Adding Microsoft GPG key..."
|
||||
curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
|
||||
|
||||
# Register the SQL Server Ubuntu repository
|
||||
echo "Adding SQL Server repository..."
|
||||
if [[ "$UBUNTU_VERSION" == "22.04" ]] || [[ "$UBUNTU_VERSION" == "24.04" ]]; then
|
||||
add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list)"
|
||||
else
|
||||
add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/20.04/mssql-server-2022.list)"
|
||||
fi
|
||||
|
||||
# Update package list
|
||||
echo "Updating package list..."
|
||||
apt-get update
|
||||
|
||||
# Install SQL Server
|
||||
echo "Installing SQL Server 2022..."
|
||||
apt-get install -y mssql-server
|
||||
|
||||
# Run SQL Server setup
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "SQL Server Configuration"
|
||||
echo "=================================="
|
||||
echo "Please choose edition and set SA password"
|
||||
echo ""
|
||||
|
||||
/opt/mssql/bin/mssql-conf setup
|
||||
|
||||
# Check SQL Server status
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Checking SQL Server Status"
|
||||
echo "=================================="
|
||||
systemctl status mssql-server --no-pager
|
||||
|
||||
# Install SQL Server command-line tools
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Installing SQL Server Tools"
|
||||
echo "=================================="
|
||||
|
||||
# Add repository for SQL tools
|
||||
curl https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | tee /etc/apt/sources.list.d/msprod.list
|
||||
|
||||
# Update and install tools
|
||||
apt-get update
|
||||
ACCEPT_EULA=Y apt-get install -y mssql-tools unixodbc-dev
|
||||
|
||||
# Add tools to PATH
|
||||
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Configure firewall
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Configuring Firewall"
|
||||
echo "=================================="
|
||||
|
||||
if command -v ufw &> /dev/null; then
|
||||
echo "Allowing SQL Server port 1433..."
|
||||
ufw allow 1433/tcp
|
||||
echo "Firewall configured."
|
||||
else
|
||||
echo "UFW not installed. Skipping firewall configuration."
|
||||
fi
|
||||
|
||||
# Enable SQL Server to start on boot
|
||||
systemctl enable mssql-server
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "Installation Complete!"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "SQL Server 2022 has been successfully installed."
|
||||
echo ""
|
||||
echo "Connection Information:"
|
||||
echo " Server: localhost,1433"
|
||||
echo " Username: sa"
|
||||
echo " Password: (the one you just set)"
|
||||
echo ""
|
||||
echo "Command-line tools installed:"
|
||||
echo " sqlcmd - SQL Server command-line tool"
|
||||
echo " bcp - Bulk copy program"
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " systemctl status mssql-server - Check status"
|
||||
echo " systemctl restart mssql-server - Restart SQL Server"
|
||||
echo " sqlcmd -S localhost -U sa - Connect to SQL Server"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Test connection: sqlcmd -S localhost -U sa -P 'YourPassword'"
|
||||
echo " 2. Create database for SkyArtShop"
|
||||
echo " 3. Configure application connection string"
|
||||
echo ""
|
||||
167
migrate-mongo-to-sql.csx
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env dotnet-script
|
||||
#r "nuget: MongoDB.Driver, 2.23.1"
|
||||
#r "nuget: Microsoft.EntityFrameworkCore.SqlServer, 8.0.0"
|
||||
#r "nuget: Microsoft.EntityFrameworkCore, 8.0.0"
|
||||
#r "nuget: System.Text.Json, 8.0.0"
|
||||
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Bson;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
Console.WriteLine("==============================================");
|
||||
Console.WriteLine("MongoDB to SQL Server Migration Script");
|
||||
Console.WriteLine("==============================================\n");
|
||||
|
||||
// Connection strings
|
||||
var mongoConnectionString = "mongodb://localhost:27017";
|
||||
var mongoDatabaseName = "SkyArtShopDB";
|
||||
var sqlConnectionString = "Server=localhost,1433;Database=SkyArtShop;User Id=sa;Password=Str0ngP@ssw0rd!2025;TrustServerCertificate=True;";
|
||||
|
||||
Console.WriteLine($"MongoDB: {mongoConnectionString}/{mongoDatabaseName}");
|
||||
Console.WriteLine($"SQL Server: Server=localhost,1433;Database=SkyArtShop\n");
|
||||
|
||||
// Connect to MongoDB
|
||||
var mongoClient = new MongoClient(mongoConnectionString);
|
||||
var mongoDatabase = mongoClient.GetDatabase(mongoDatabaseName);
|
||||
|
||||
Console.WriteLine("✓ Connected to MongoDB");
|
||||
|
||||
// Test SQL connection
|
||||
try
|
||||
{
|
||||
using (var connection = new Microsoft.Data.SqlClient.SqlConnection(sqlConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
Console.WriteLine("✓ Connected to SQL Server\n");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"✗ Failed to connect to SQL Server: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Starting data migration...\n");
|
||||
|
||||
// Migration statistics
|
||||
int totalCollections = 0;
|
||||
int totalDocuments = 0;
|
||||
var errors = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
// Get all collections
|
||||
var collections = new[]
|
||||
{
|
||||
"Pages", "PortfolioCategories", "PortfolioProjects",
|
||||
"Products", "BlogPosts", "SiteSettings", "MenuItems",
|
||||
"AdminUsers", "UserRoles", "Orders", "ProductViews",
|
||||
"HomepageSections"
|
||||
};
|
||||
|
||||
foreach (var collectionName in collections)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Migrating {collectionName}...");
|
||||
var collection = mongoDatabase.GetCollection<BsonDocument>(collectionName);
|
||||
var documents = await collection.Find(new BsonDocument()).ToListAsync();
|
||||
|
||||
if (documents.Count == 0)
|
||||
{
|
||||
Console.WriteLine($" → 0 documents (skipped)\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Export to JSON file for SQL import
|
||||
var jsonData = new List<string>();
|
||||
foreach (var doc in documents)
|
||||
{
|
||||
// Convert ObjectId to string
|
||||
if (doc.Contains("_id"))
|
||||
{
|
||||
doc["_id"] = doc["_id"].ToString();
|
||||
}
|
||||
|
||||
// Handle nested ObjectIds
|
||||
ConvertObjectIdsToStrings(doc);
|
||||
|
||||
jsonData.Add(doc.ToJson(new MongoDB.Bson.IO.JsonWriterSettings { OutputMode = MongoDB.Bson.IO.JsonOutputMode.Strict }));
|
||||
}
|
||||
|
||||
// Save to file
|
||||
var outputPath = $"/tmp/migration_{collectionName}.json";
|
||||
await File.WriteAllTextAsync(outputPath, $"[{string.Join(",", jsonData)}]");
|
||||
|
||||
Console.WriteLine($" ✓ Exported {documents.Count} documents to {outputPath}");
|
||||
totalDocuments += documents.Count;
|
||||
totalCollections++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"{collectionName}: {ex.Message}");
|
||||
Console.WriteLine($" ✗ Error: {ex.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("==============================================");
|
||||
Console.WriteLine("Migration Export Complete");
|
||||
Console.WriteLine("==============================================");
|
||||
Console.WriteLine($"Collections exported: {totalCollections}");
|
||||
Console.WriteLine($"Total documents: {totalDocuments}");
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"\nErrors ({errors.Count}):");
|
||||
foreach (var error in errors)
|
||||
{
|
||||
Console.WriteLine($" - {error}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n✓ JSON files created in /tmp/migration_*.json");
|
||||
Console.WriteLine("✓ Next step: Run the C# import program to load into SQL Server");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n✗ Fatal error: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ConvertObjectIdsToStrings(BsonDocument doc)
|
||||
{
|
||||
var keysToConvert = new List<string>();
|
||||
|
||||
foreach (var element in doc)
|
||||
{
|
||||
if (element.Value.IsObjectId)
|
||||
{
|
||||
keysToConvert.Add(element.Name);
|
||||
}
|
||||
else if (element.Value.IsBsonDocument)
|
||||
{
|
||||
ConvertObjectIdsToStrings(element.Value.AsBsonDocument);
|
||||
}
|
||||
else if (element.Value.IsBsonArray)
|
||||
{
|
||||
foreach (var item in element.Value.AsBsonArray)
|
||||
{
|
||||
if (item.IsBsonDocument)
|
||||
{
|
||||
ConvertObjectIdsToStrings(item.AsBsonDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var key in keysToConvert)
|
||||
{
|
||||
doc[key] = doc[key].ToString();
|
||||
}
|
||||
}
|
||||
164
migrate-prepare.sh
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/bin/bash
|
||||
# SkyArtShop Migration Preparation Script
|
||||
# Run this on the SOURCE server
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================="
|
||||
echo " SkyArtShop Migration - Preparation"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR=~/skyartshop-backup
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
APP_DIR=/var/www/SkyArtShop
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Create backup directory
|
||||
echo -e "${YELLOW}[1/6] Creating backup directory...${NC}"
|
||||
mkdir -p $BACKUP_DIR/configs
|
||||
echo -e "${GREEN}✓ Backup directory created: $BACKUP_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
# Export MongoDB
|
||||
echo -e "${YELLOW}[2/6] Exporting MongoDB database...${NC}"
|
||||
if command -v mongodump &> /dev/null; then
|
||||
mongodump --db=SkyArtShopDB --out=$BACKUP_DIR/mongodb-dump
|
||||
cd $BACKUP_DIR
|
||||
tar -czf mongodb-backup-$DATE.tar.gz mongodb-dump/
|
||||
rm -rf mongodb-dump/
|
||||
|
||||
# Get database stats
|
||||
DB_SIZE=$(du -sh mongodb-backup-$DATE.tar.gz | cut -f1)
|
||||
echo -e "${GREEN}✓ MongoDB backup created: mongodb-backup-$DATE.tar.gz ($DB_SIZE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ MongoDB not found! Skipping database backup.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Package application files
|
||||
echo -e "${YELLOW}[3/6] Packaging application files...${NC}"
|
||||
if [ -d "$APP_DIR" ]; then
|
||||
cd /var/www
|
||||
tar --exclude='SkyArtShop/bin' \
|
||||
--exclude='SkyArtShop/obj' \
|
||||
--exclude='SkyArtShop/publish/runtimes' \
|
||||
--exclude='SkyArtShop/.vs' \
|
||||
--exclude='SkyArtShop/.git' \
|
||||
--exclude='SkyArtShop/node_modules' \
|
||||
-czf $BACKUP_DIR/skyartshop-app-$DATE.tar.gz SkyArtShop/
|
||||
|
||||
APP_SIZE=$(du -sh $BACKUP_DIR/skyartshop-app-$DATE.tar.gz | cut -f1)
|
||||
echo -e "${GREEN}✓ Application packaged: skyartshop-app-$DATE.tar.gz ($APP_SIZE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Application directory not found: $APP_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Save configuration files
|
||||
echo -e "${YELLOW}[4/6] Backing up configuration files...${NC}"
|
||||
|
||||
# Nginx config
|
||||
if [ -f /etc/nginx/sites-available/skyartshop ]; then
|
||||
cp /etc/nginx/sites-available/skyartshop $BACKUP_DIR/configs/nginx-skyartshop.conf
|
||||
echo -e "${GREEN}✓ Nginx config backed up${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ No Nginx config found${NC}"
|
||||
fi
|
||||
|
||||
# Systemd service
|
||||
if [ -f /etc/systemd/system/skyartshop.service ]; then
|
||||
cp /etc/systemd/system/skyartshop.service $BACKUP_DIR/configs/skyartshop.service
|
||||
echo -e "${GREEN}✓ Systemd service backed up${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ No systemd service found${NC}"
|
||||
fi
|
||||
|
||||
# Save environment info
|
||||
cat > $BACKUP_DIR/configs/environment-info.txt << EOF
|
||||
Migration Package Information
|
||||
==============================
|
||||
Created: $(date)
|
||||
Source Server: $(hostname)
|
||||
IP Address: $(hostname -I | awk '{print $1}')
|
||||
|
||||
.NET Version:
|
||||
$(dotnet --version 2>/dev/null || echo "Not installed")
|
||||
|
||||
MongoDB Version:
|
||||
$(mongod --version 2>/dev/null | head -1 || echo "Not installed")
|
||||
|
||||
OS Information:
|
||||
$(cat /etc/os-release | grep PRETTY_NAME)
|
||||
|
||||
Application Path: $APP_DIR
|
||||
Backup Date: $DATE
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Environment info saved${NC}"
|
||||
echo ""
|
||||
|
||||
# Create manifest
|
||||
echo -e "${YELLOW}[5/6] Creating migration manifest...${NC}"
|
||||
cat > $BACKUP_DIR/MANIFEST.txt << EOF
|
||||
========================================
|
||||
SkyArtShop Migration Package
|
||||
========================================
|
||||
|
||||
Created: $(date)
|
||||
Source Server: $(hostname)
|
||||
Package ID: $DATE
|
||||
|
||||
CONTENTS:
|
||||
---------
|
||||
$(ls -lh $BACKUP_DIR/*.tar.gz 2>/dev/null | awk '{print " " $9 " (" $5 ")"}')
|
||||
|
||||
Configuration Files:
|
||||
$(ls -1 $BACKUP_DIR/configs/ | sed 's/^/ - /')
|
||||
|
||||
Total Package Size: $(du -sh $BACKUP_DIR | cut -f1)
|
||||
|
||||
NEXT STEPS:
|
||||
-----------
|
||||
1. Transfer this entire directory to the new server:
|
||||
scp -r ~/skyartshop-backup username@new-server-ip:/home/username/
|
||||
|
||||
2. On the new server, run:
|
||||
cd /var/www/SkyArtShop
|
||||
bash migrate-restore.sh
|
||||
|
||||
For detailed instructions, see:
|
||||
MIGRATION_PACKAGE.md
|
||||
|
||||
========================================
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✓ Manifest created${NC}"
|
||||
echo ""
|
||||
|
||||
# Display summary
|
||||
echo -e "${YELLOW}[6/6] Migration package ready!${NC}"
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " 📦 MIGRATION PACKAGE SUMMARY"
|
||||
echo "========================================="
|
||||
cat $BACKUP_DIR/MANIFEST.txt
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ All files backed up successfully!${NC}"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Transfer the backup to new server:"
|
||||
echo -e " ${YELLOW}scp -r $BACKUP_DIR username@new-server-ip:/home/username/${NC}"
|
||||
echo ""
|
||||
echo "2. Or create a single archive:"
|
||||
echo -e " ${YELLOW}cd ~ && tar -czf skyartshop-migration-$DATE.tar.gz skyartshop-backup/${NC}"
|
||||
echo ""
|
||||
echo "3. On the new server, extract and run migrate-restore.sh"
|
||||
echo ""
|
||||
186
migrate-restore.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
# SkyArtShop Migration Restoration Script
|
||||
# Run this on the TARGET server
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================="
|
||||
echo " SkyArtShop Migration - Restoration"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
APP_DIR=/var/www/SkyArtShop
|
||||
BACKUP_DIR=~/skyartshop-backup
|
||||
|
||||
# Check if backup directory exists
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
echo -e "${RED}✗ Backup directory not found: $BACKUP_DIR${NC}"
|
||||
echo "Please transfer the backup files first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}Backup directory found: $BACKUP_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
# Check for required files
|
||||
echo -e "${YELLOW}[1/8] Checking backup files...${NC}"
|
||||
MONGO_BACKUP=$(ls $BACKUP_DIR/mongodb-backup-*.tar.gz 2>/dev/null | head -1)
|
||||
APP_BACKUP=$(ls $BACKUP_DIR/skyartshop-app-*.tar.gz 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$MONGO_BACKUP" ]; then
|
||||
echo -e "${RED}✗ MongoDB backup not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$APP_BACKUP" ]; then
|
||||
echo -e "${RED}✗ Application backup not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Found MongoDB backup: $(basename $MONGO_BACKUP)${NC}"
|
||||
echo -e "${GREEN}✓ Found Application backup: $(basename $APP_BACKUP)${NC}"
|
||||
echo ""
|
||||
|
||||
# Check prerequisites
|
||||
echo -e "${YELLOW}[2/8] Checking prerequisites...${NC}"
|
||||
|
||||
if ! command -v dotnet &> /dev/null; then
|
||||
echo -e "${RED}✗ .NET SDK not installed${NC}"
|
||||
echo "Install .NET 8.0 first:"
|
||||
echo " wget https://dot.net/v1/dotnet-install.sh"
|
||||
echo " bash dotnet-install.sh --channel 8.0"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}✓ .NET SDK found: $(dotnet --version)${NC}"
|
||||
fi
|
||||
|
||||
if ! command -v mongod &> /dev/null; then
|
||||
echo -e "${RED}✗ MongoDB not installed${NC}"
|
||||
echo "Install MongoDB first:"
|
||||
echo " See MIGRATION_PACKAGE.md for instructions"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}✓ MongoDB found${NC}"
|
||||
fi
|
||||
|
||||
# Check if MongoDB is running
|
||||
if ! pgrep -x mongod > /dev/null; then
|
||||
echo -e "${YELLOW}⚠ MongoDB not running, starting...${NC}"
|
||||
sudo systemctl start mongod
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ MongoDB is running${NC}"
|
||||
echo ""
|
||||
|
||||
# Restore MongoDB
|
||||
echo -e "${YELLOW}[3/8] Restoring MongoDB database...${NC}"
|
||||
cd $BACKUP_DIR
|
||||
tar -xzf $MONGO_BACKUP
|
||||
mongorestore --db=SkyArtShopDB mongodb-dump/SkyArtShopDB/ --drop
|
||||
rm -rf mongodb-dump/
|
||||
|
||||
# Verify restoration
|
||||
PRODUCT_COUNT=$(mongosh SkyArtShopDB --quiet --eval "db.Products.countDocuments()" 2>/dev/null || echo "0")
|
||||
MENU_COUNT=$(mongosh SkyArtShopDB --quiet --eval "db.MenuItems.countDocuments()" 2>/dev/null || echo "0")
|
||||
|
||||
echo -e "${GREEN}✓ MongoDB restored${NC}"
|
||||
echo -e " - Products: $PRODUCT_COUNT"
|
||||
echo -e " - Menu Items: $MENU_COUNT"
|
||||
echo ""
|
||||
|
||||
# Restore application files
|
||||
echo -e "${YELLOW}[4/8] Restoring application files...${NC}"
|
||||
sudo mkdir -p /var/www
|
||||
cd /var/www
|
||||
sudo tar -xzf $APP_BACKUP
|
||||
|
||||
# Set ownership
|
||||
sudo chown -R $USER:$USER $APP_DIR
|
||||
|
||||
echo -e "${GREEN}✓ Application files restored to $APP_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
# Restore NuGet packages
|
||||
echo -e "${YELLOW}[5/8] Restoring NuGet packages...${NC}"
|
||||
cd $APP_DIR
|
||||
dotnet restore
|
||||
echo -e "${GREEN}✓ NuGet packages restored${NC}"
|
||||
echo ""
|
||||
|
||||
# Build application
|
||||
echo -e "${YELLOW}[6/8] Building application...${NC}"
|
||||
dotnet build -c Release
|
||||
echo -e "${GREEN}✓ Application built${NC}"
|
||||
echo ""
|
||||
|
||||
# Publish application
|
||||
echo -e "${YELLOW}[7/8] Publishing application...${NC}"
|
||||
dotnet publish SkyArtShop.csproj -c Release -o publish
|
||||
echo -e "${GREEN}✓ Application published to $APP_DIR/publish${NC}"
|
||||
echo ""
|
||||
|
||||
# Set permissions
|
||||
echo -e "${YELLOW}[8/8] Setting permissions...${NC}"
|
||||
chmod -R 755 $APP_DIR
|
||||
chmod -R 777 $APP_DIR/wwwroot/uploads
|
||||
echo -e "${GREEN}✓ Permissions set${NC}"
|
||||
echo ""
|
||||
|
||||
# Test application
|
||||
echo -e "${YELLOW}Testing application startup...${NC}"
|
||||
cd $APP_DIR/publish
|
||||
|
||||
# Kill any existing instance
|
||||
pkill -f "dotnet.*SkyArtShop.dll" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Start application
|
||||
nohup dotnet SkyArtShop.dll --urls "http://0.0.0.0:5001" > /tmp/skyartshop.log 2>&1 &
|
||||
sleep 5
|
||||
|
||||
# Test if running
|
||||
if curl -s http://localhost:5001 > /dev/null; then
|
||||
echo -e "${GREEN}✓ Application is running on port 5001${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Application failed to start. Check logs:${NC}"
|
||||
echo " tail -f /tmp/skyartshop.log"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Display summary
|
||||
echo "========================================="
|
||||
echo " ✅ MIGRATION COMPLETE"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Application Details:"
|
||||
echo " Location: $APP_DIR"
|
||||
echo " Status: Running on http://localhost:5001"
|
||||
echo " Database: SkyArtShopDB ($PRODUCT_COUNT products)"
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Test the application:"
|
||||
echo -e " ${YELLOW}curl http://localhost:5001${NC}"
|
||||
echo ""
|
||||
echo "2. Configure Nginx (optional):"
|
||||
echo -e " ${YELLOW}sudo nano /etc/nginx/sites-available/skyartshop${NC}"
|
||||
echo " (See MIGRATION_PACKAGE.md for config)"
|
||||
echo ""
|
||||
echo "3. Create systemd service (optional):"
|
||||
echo -e " ${YELLOW}sudo nano /etc/systemd/system/skyartshop.service${NC}"
|
||||
echo " (See MIGRATION_PACKAGE.md for config)"
|
||||
echo ""
|
||||
echo "4. Check application logs:"
|
||||
echo -e " ${YELLOW}tail -f /tmp/skyartshop.log${NC}"
|
||||
echo ""
|
||||
echo "Configuration files backed up in:"
|
||||
echo " $BACKUP_DIR/configs/"
|
||||
echo ""
|
||||
99
migrate-transfer.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/bin/bash
|
||||
# Quick Transfer Script - Use this to send backup to new server
|
||||
|
||||
echo "========================================="
|
||||
echo " SkyArtShop - Quick Transfer"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check if backup exists
|
||||
BACKUP_DIR=~/skyartshop-backup
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
echo -e "${YELLOW}Backup not found. Running preparation script...${NC}"
|
||||
bash /var/www/SkyArtShop/migrate-prepare.sh
|
||||
fi
|
||||
|
||||
# Get new server details
|
||||
echo -e "${BLUE}Enter new server details:${NC}"
|
||||
read -p "Username: " NEW_USER
|
||||
read -p "IP Address: " NEW_IP
|
||||
echo ""
|
||||
|
||||
# Create single archive
|
||||
echo -e "${YELLOW}Creating single archive for easy transfer...${NC}"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
cd ~
|
||||
tar -czf skyartshop-complete-$DATE.tar.gz skyartshop-backup/
|
||||
ARCHIVE_SIZE=$(du -sh skyartshop-complete-$DATE.tar.gz | cut -f1)
|
||||
echo -e "${GREEN}✓ Archive created: skyartshop-complete-$DATE.tar.gz ($ARCHIVE_SIZE)${NC}"
|
||||
echo ""
|
||||
|
||||
# Test connection
|
||||
echo -e "${YELLOW}Testing connection to new server...${NC}"
|
||||
if ssh -o ConnectTimeout=5 $NEW_USER@$NEW_IP "echo 'Connection successful'" 2>/dev/null; then
|
||||
echo -e "${GREEN}✓ Connection successful${NC}"
|
||||
echo ""
|
||||
|
||||
# Transfer file
|
||||
echo -e "${YELLOW}Transferring archive to new server...${NC}"
|
||||
echo "This may take several minutes depending on file size and network speed..."
|
||||
|
||||
scp ~/skyartshop-complete-$DATE.tar.gz $NEW_USER@$NEW_IP:/tmp/
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Transfer complete!${NC}"
|
||||
echo ""
|
||||
|
||||
# Extract on remote server
|
||||
echo -e "${YELLOW}Extracting on new server...${NC}"
|
||||
ssh $NEW_USER@$NEW_IP "cd ~ && tar -xzf /tmp/skyartshop-complete-$DATE.tar.gz && rm /tmp/skyartshop-complete-$DATE.tar.gz"
|
||||
|
||||
echo -e "${GREEN}✓ Files extracted on new server${NC}"
|
||||
echo ""
|
||||
|
||||
# Display next steps
|
||||
echo "========================================="
|
||||
echo " ✅ TRANSFER COMPLETE"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Next Steps - Run on NEW SERVER:"
|
||||
echo ""
|
||||
echo "1. SSH to the new server:"
|
||||
echo -e " ${YELLOW}ssh $NEW_USER@$NEW_IP${NC}"
|
||||
echo ""
|
||||
echo "2. Install prerequisites (if not already installed):"
|
||||
echo -e " ${YELLOW}bash ~/skyartshop-backup/install-prerequisites.sh${NC}"
|
||||
echo ""
|
||||
echo "3. Run restoration script:"
|
||||
echo -e " ${YELLOW}bash ~/skyartshop-backup/migrate-restore.sh${NC}"
|
||||
echo ""
|
||||
echo "4. Follow the on-screen instructions"
|
||||
echo ""
|
||||
|
||||
else
|
||||
echo -e "${RED}✗ Transfer failed${NC}"
|
||||
echo "Try manual transfer:"
|
||||
echo -e " ${YELLOW}scp ~/skyartshop-complete-$DATE.tar.gz $NEW_USER@$NEW_IP:/tmp/${NC}"
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "${RED}✗ Cannot connect to new server${NC}"
|
||||
echo ""
|
||||
echo "Manual transfer options:"
|
||||
echo ""
|
||||
echo "1. Using SCP:"
|
||||
echo -e " ${YELLOW}scp ~/skyartshop-complete-$DATE.tar.gz $NEW_USER@$NEW_IP:/tmp/${NC}"
|
||||
echo ""
|
||||
echo "2. Using USB drive:"
|
||||
echo -e " ${YELLOW}cp ~/skyartshop-complete-$DATE.tar.gz /media/usb-drive/${NC}"
|
||||
echo ""
|
||||
echo "3. Using cloud storage:"
|
||||
echo " Upload ~/skyartshop-complete-$DATE.tar.gz to your cloud provider"
|
||||
echo ""
|
||||
fi
|
||||
159
nginx-skyartshop-secured.conf
Executable file
@@ -0,0 +1,159 @@
|
||||
# Upstream configuration
|
||||
upstream skyartshop_backend {
|
||||
server localhost:5000;
|
||||
keepalive 64;
|
||||
}
|
||||
|
||||
# Rate limiting zones - Enhanced for security
|
||||
limit_req_zone $binary_remote_addr zone=general:10m rate=100r/s;
|
||||
limit_req_zone $binary_remote_addr zone=admin:10m rate=10r/s; # Reduced from 20 to 10
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # Only 5 login attempts per minute
|
||||
limit_conn_zone $binary_remote_addr zone=addr:10m;
|
||||
|
||||
# Block common attack patterns
|
||||
map $request_uri $is_blocked {
|
||||
default 0;
|
||||
"~*\.(asp|aspx|php|jsp|cgi)$" 1;
|
||||
# Block common SQL injection keywords except allow legitimate admin endpoints
|
||||
"~*(eval|base64|decode|union|select|insert|update|drop)" 1;
|
||||
"~*(/\.|\.\./)" 1;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name skyarts.ddns.net localhost;
|
||||
|
||||
# Block malicious requests
|
||||
if ($is_blocked = 1) {
|
||||
return 444;
|
||||
}
|
||||
|
||||
# Enhanced Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
|
||||
|
||||
# Content Security Policy - Allow only trusted CDNs
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://cdn.ckeditor.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; img-src 'self' data: https:; connect-src 'self' https://cdn.jsdelivr.net https://cdn.ckeditor.com; frame-src 'none';" always;
|
||||
|
||||
# Prevent clickjacking
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
|
||||
# HSTS - Force HTTPS
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
|
||||
# Connection limits
|
||||
limit_conn addr 10;
|
||||
|
||||
# General rate limiting
|
||||
limit_req zone=general burst=200 nodelay;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/skyartshop_access.log combined buffer=32k;
|
||||
error_log /var/log/nginx/skyartshop_error.log warn;
|
||||
|
||||
# Block access to sensitive files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
# Block access to config files
|
||||
location ~* \.(conf|config|json|lock|git|env)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# Admin login with strict rate limiting
|
||||
location = /admin/login {
|
||||
limit_req zone=login burst=2 nodelay;
|
||||
limit_req zone=admin burst=5 nodelay;
|
||||
|
||||
proxy_pass http://skyartshop_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
client_max_body_size 1M;
|
||||
}
|
||||
|
||||
# Admin area with stricter rate limiting
|
||||
location /admin {
|
||||
limit_req zone=admin burst=10 nodelay;
|
||||
|
||||
proxy_pass http://skyartshop_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
|
||||
# Main application - catch all
|
||||
location / {
|
||||
proxy_pass http://skyartshop_backend;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Connection headers
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection keep-alive;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# Forwarding headers
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# Buffer settings for performance
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 4k;
|
||||
proxy_buffers 24 4k;
|
||||
proxy_busy_buffers_size 8k;
|
||||
proxy_max_temp_file_size 2048m;
|
||||
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
|
||||
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/skyarts.ddns.net/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/skyarts.ddns.net/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
}
|
||||
|
||||
server {
|
||||
if ($host = skyarts.ddns.net) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name skyarts.ddns.net localhost;
|
||||
return 404; # managed by Certbot
|
||||
}
|
||||
22
quick-restart.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Quick restart without rebuild (for configuration changes)
|
||||
|
||||
echo "Restarting SkyArtShop services..."
|
||||
|
||||
sudo systemctl restart skyartshop
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet skyartshop; then
|
||||
echo "✓ Application restarted successfully"
|
||||
|
||||
# Quick test
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost | grep -q "200"; then
|
||||
echo "✓ Website is responding"
|
||||
else
|
||||
echo "⚠ Website may have issues"
|
||||
fi
|
||||
else
|
||||
echo "✗ Application failed to start"
|
||||
echo "View logs: sudo journalctl -u skyartshop.service -n 50"
|
||||
fi
|
||||
57
quick-update.ps1
Executable file
@@ -0,0 +1,57 @@
|
||||
# Quick View Update - Copy changed view files without restarting site
|
||||
# Run this after editing views to update live site without downtime
|
||||
|
||||
param(
|
||||
[string]$ViewFile = ""
|
||||
)
|
||||
|
||||
$devPath = "E:\Documents\Website Projects\Sky_Art_Shop"
|
||||
$prodPath = "C:\inetpub\wwwroot\skyartshop"
|
||||
|
||||
if ($ViewFile) {
|
||||
# Copy specific file
|
||||
$sourceFile = Join-Path $devPath $ViewFile
|
||||
$destFile = Join-Path $prodPath $ViewFile
|
||||
|
||||
if (Test-Path $sourceFile) {
|
||||
$destDir = Split-Path $destFile -Parent
|
||||
if (-not (Test-Path $destDir)) {
|
||||
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
|
||||
}
|
||||
Copy-Item $sourceFile $destFile -Force
|
||||
Write-Host "✅ Updated: $ViewFile" -ForegroundColor Green
|
||||
|
||||
# Touch web.config to trigger reload without restart
|
||||
$webConfig = Join-Path $prodPath "web.config"
|
||||
if (Test-Path $webConfig) {
|
||||
(Get-Item $webConfig).LastWriteTime = Get-Date
|
||||
Write-Host "✅ Site reloaded automatically (no downtime)" -ForegroundColor Cyan
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "❌ File not found: $sourceFile" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Sync all Views
|
||||
Write-Host "🔄 Syncing all views..." -ForegroundColor Cyan
|
||||
|
||||
$viewsSource = Join-Path $devPath "Views"
|
||||
$viewsDest = Join-Path $prodPath "Views"
|
||||
|
||||
if (Test-Path $viewsSource) {
|
||||
Copy-Item -Path $viewsSource -Destination $prodPath -Recurse -Force
|
||||
Write-Host "✅ All views synced" -ForegroundColor Green
|
||||
|
||||
# Touch web.config
|
||||
$webConfig = Join-Path $prodPath "web.config"
|
||||
if (Test-Path $webConfig) {
|
||||
(Get-Item $webConfig).LastWriteTime = Get-Date
|
||||
Write-Host "✅ Site reloaded automatically" -ForegroundColor Cyan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n💡 Usage examples:" -ForegroundColor Yellow
|
||||
Write-Host " .\quick-update.ps1 # Sync all views"
|
||||
Write-Host " .\quick-update.ps1 'Views\Shared\_AdminLayout.cshtml' # Update specific view"
|
||||
119
rebuild-and-deploy.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SkyArtShop - Rebuild and Deploy Script
|
||||
# Run this script after making changes to your code
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "========================================"
|
||||
echo " SkyArtShop - Rebuild & Deploy"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Change to project directory
|
||||
cd /var/www/SkyArtShop
|
||||
|
||||
# Step 1: Build
|
||||
echo -e "${YELLOW}[1/5] Building application...${NC}"
|
||||
dotnet build SkyArtShop.csproj -c Release
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Build successful${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Build failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 2: Clean and Publish
|
||||
echo -e "${YELLOW}[2/5] Cleaning old publish directory...${NC}"
|
||||
rm -rf ./publish_temp
|
||||
mkdir -p ./publish_temp
|
||||
|
||||
echo -e "${YELLOW}[2/5] Publishing to ./publish_temp...${NC}"
|
||||
dotnet publish SkyArtShop.csproj -c Release -o ./publish_temp --no-build
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Publish successful${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Publish failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 3: Stop service and swap directories
|
||||
echo -e "${YELLOW}[3/5] Stopping application...${NC}"
|
||||
sudo systemctl stop skyartshop
|
||||
sleep 1
|
||||
|
||||
echo -e "${YELLOW}[3/5] Swapping publish directories...${NC}"
|
||||
if [ -d "./publish" ]; then
|
||||
sudo rm -rf ./publish_old
|
||||
sudo mv ./publish ./publish_old
|
||||
fi
|
||||
mv ./publish_temp ./publish
|
||||
|
||||
echo -e "${YELLOW}[3/5] Setting permissions...${NC}"
|
||||
sudo chown -R www-data:www-data ./publish
|
||||
sudo chmod +x ./publish/SkyArtShop.dll
|
||||
echo -e "${GREEN}✓ Permissions set${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 4: Restart services
|
||||
echo -e "${YELLOW}[4/5] Restarting services...${NC}"
|
||||
sudo systemctl restart skyartshop
|
||||
echo " Waiting for application to start..."
|
||||
sleep 5
|
||||
|
||||
if systemctl is-active --quiet skyartshop; then
|
||||
echo -e "${GREEN}✓ Application restarted${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Application failed to start${NC}"
|
||||
echo "Check logs: sudo journalctl -u skyartshop.service -n 50"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Step 5: Verify
|
||||
echo -e "${YELLOW}[5/5] Verifying deployment...${NC}"
|
||||
echo " Waiting for application to be ready..."
|
||||
sleep 3
|
||||
|
||||
# Test website
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost 2>/dev/null)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ Website responding (HTTP $HTTP_CODE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Website not responding (HTTP $HTTP_CODE)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test CSS
|
||||
CSS_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/assets/css/main.css 2>/dev/null)
|
||||
if [ "$CSS_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ CSS loading (HTTP $CSS_CODE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ CSS not loading (HTTP $CSS_CODE)${NC}"
|
||||
fi
|
||||
|
||||
# Test JS
|
||||
JS_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/assets/js/main.js 2>/dev/null)
|
||||
if [ "$JS_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ JavaScript loading (HTTP $JS_CODE)${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ JavaScript not loading (HTTP $JS_CODE)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo -e "${GREEN}✓ Deployment Complete!${NC}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Website: http://localhost"
|
||||
echo "Domain: http://skyarts.ddns.net"
|
||||
echo ""
|
||||
echo "To view logs: sudo journalctl -u skyartshop.service -f"
|
||||
44
reset-admin-password-mongo.sh
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# MongoDB-Based Admin Password Reset Script
|
||||
# No more SQLite issues!
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <new-password>"
|
||||
echo "Example: $0 MyNewPassword123!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_PASSWORD="$1"
|
||||
EMAIL="admin@skyartshop.com"
|
||||
|
||||
echo "Resetting admin password in MongoDB..."
|
||||
|
||||
# Stop the service
|
||||
echo "Stopping service..."
|
||||
sudo systemctl stop skyartshop.service
|
||||
|
||||
# Update the password in appsettings.Production.json
|
||||
echo "Updating password in configuration..."
|
||||
cd /var/www/SkyArtShop
|
||||
sudo sed -i "s|\"Password\": \".*\"|\"Password\": \"$NEW_PASSWORD\"|g" publish/appsettings.Production.json
|
||||
|
||||
# Delete the admin user from MongoDB to force recreation
|
||||
echo "Removing old admin user from MongoDB..."
|
||||
mongosh SkyArtShopDB --eval "db.AdminUsers.deleteMany({Email: '$EMAIL'})" --quiet
|
||||
|
||||
# Start the service (it will recreate the admin with new password)
|
||||
echo "Starting service..."
|
||||
sudo systemctl start skyartshop.service
|
||||
|
||||
sleep 3
|
||||
|
||||
# Check status
|
||||
systemctl status skyartshop.service --no-pager | head -15
|
||||
|
||||
echo ""
|
||||
echo "✅ Admin password has been reset in MongoDB!"
|
||||
echo "Email: $EMAIL"
|
||||
echo "Password: $NEW_PASSWORD"
|
||||
echo ""
|
||||
echo "You can now login at: https://skyarts.ddns.net/admin/login"
|
||||
78
reset-admin-password.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SkyArt Shop - Admin Password Reset Tool
|
||||
# Usage: ./reset-admin-password.sh [new_password]
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
RESET_SCRIPT="$SCRIPT_DIR/reset-password.csx"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " SkyArt Shop - Password Reset Tool"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if dotnet-script is installed
|
||||
if ! command -v dotnet-script &> /dev/null; then
|
||||
echo -e "${RED}✗ Error: dotnet-script is not installed${NC}"
|
||||
echo ""
|
||||
echo "To install, run:"
|
||||
echo " /home/pts/.dotnet/dotnet tool install -g dotnet-script"
|
||||
echo " export PATH=\"\$PATH:/home/pts/.dotnet/tools\""
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if reset script exists
|
||||
if [ ! -f "$RESET_SCRIPT" ]; then
|
||||
echo -e "${RED}✗ Error: Reset script not found at $RESET_SCRIPT${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get password from argument or prompt
|
||||
if [ -n "$1" ]; then
|
||||
NEW_PASSWORD="$1"
|
||||
else
|
||||
echo -e "${YELLOW}Enter new password (or press Enter for default 'Admin123!'):${NC}"
|
||||
read -s NEW_PASSWORD
|
||||
echo ""
|
||||
|
||||
if [ -z "$NEW_PASSWORD" ]; then
|
||||
NEW_PASSWORD="Admin123!"
|
||||
echo "Using default password: Admin123!"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Resetting password for: admin@skyartshop.com"
|
||||
echo ""
|
||||
|
||||
# Add dotnet-script to PATH if needed
|
||||
export PATH="$PATH:/home/pts/.dotnet/tools"
|
||||
|
||||
# Run the reset script
|
||||
if dotnet-script "$RESET_SCRIPT" "$NEW_PASSWORD"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}=========================================="
|
||||
echo "✓ Password reset complete!"
|
||||
echo "==========================================${NC}"
|
||||
echo ""
|
||||
echo "You can now login at:"
|
||||
echo " https://skyarts.ddns.net/admin/login"
|
||||
echo ""
|
||||
echo "With credentials:"
|
||||
echo " Email: admin@skyartshop.com"
|
||||
echo " Password: $NEW_PASSWORD"
|
||||
echo ""
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}✗ Password reset failed${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
63
reset-admin-simple.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Simple Admin Password Reset Script
|
||||
# This creates a new admin with a simple known password
|
||||
|
||||
ADMIN_EMAIL="admin@skyartshop.com"
|
||||
NEW_PASSWORD="Admin123!"
|
||||
|
||||
echo "=========================================="
|
||||
echo "SkyArt Shop - Admin Password Reset"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Resetting password for: $ADMIN_EMAIL"
|
||||
echo "New password will be: $NEW_PASSWORD"
|
||||
echo ""
|
||||
|
||||
# Delete existing admin
|
||||
mongosh --quiet SkyArtShopDB <<EOF
|
||||
db.AdminUsers.deleteMany({Email: '$ADMIN_EMAIL'});
|
||||
print('Deleted existing admin user');
|
||||
EOF
|
||||
|
||||
# Create password hash using OpenSSL (simple method)
|
||||
SALT=$(openssl rand -base64 16)
|
||||
HASH=$(echo -n "${NEW_PASSWORD}${SALT}" | openssl dgst -sha256 -binary | base64)
|
||||
COMBINED=$(echo -n "${SALT}:${HASH}" | base64)
|
||||
|
||||
# Insert new admin
|
||||
mongosh --quiet SkyArtShopDB <<EOF
|
||||
db.AdminUsers.insertOne({
|
||||
Email: '$ADMIN_EMAIL',
|
||||
PasswordHash: '$COMBINED',
|
||||
Name: 'System Administrator',
|
||||
Role: 'MasterAdmin',
|
||||
Permissions: [
|
||||
'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,
|
||||
CreatedAt: new Date(),
|
||||
LastLogin: null,
|
||||
CreatedBy: 'System Reset',
|
||||
Phone: null,
|
||||
Notes: 'Password reset on ' + new Date().toISOString()
|
||||
});
|
||||
print('');
|
||||
print('✓ Admin user created successfully!');
|
||||
print('');
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Password Reset Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Login Credentials:"
|
||||
echo " Email: $ADMIN_EMAIL"
|
||||
echo " Password: $NEW_PASSWORD"
|
||||
echo ""
|
||||
echo "Login URL: https://skyarts.ddns.net/admin/login"
|
||||
echo ""
|
||||
82
reset-password-direct.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Direct MongoDB Password Reset
|
||||
# This bypasses the web interface and sets password directly
|
||||
|
||||
ADMIN_EMAIL="admin@skyartshop.com"
|
||||
NEW_PASSWORD="Admin123!"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Direct MongoDB Password Reset"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Resetting password for: $ADMIN_EMAIL"
|
||||
echo "New password: $NEW_PASSWORD"
|
||||
echo ""
|
||||
|
||||
# Create a C# script to generate the correct PBKDF2 hash
|
||||
cat > /tmp/hash_password.cs << 'CSHARP'
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
var password = args.Length > 0 ? args[0] : "Admin123!";
|
||||
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
var salt = new byte[16];
|
||||
rng.GetBytes(salt);
|
||||
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
|
||||
var hash = pbkdf2.GetBytes(32);
|
||||
|
||||
var hashBytes = new byte[48];
|
||||
Array.Copy(salt, 0, hashBytes, 0, 16);
|
||||
Array.Copy(hash, 0, hashBytes, 16, 32);
|
||||
|
||||
Console.WriteLine(Convert.ToBase64String(hashBytes));
|
||||
CSHARP
|
||||
|
||||
# Generate the hash
|
||||
HASH=$(dotnet script /tmp/hash_password.cs "$NEW_PASSWORD" 2>/dev/null)
|
||||
|
||||
if [ -z "$HASH" ]; then
|
||||
echo "✗ Failed to generate password hash"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Generated hash: ${HASH:0:40}..."
|
||||
echo ""
|
||||
|
||||
# Update MongoDB
|
||||
mongosh --quiet SkyArtShopDB <<EOF
|
||||
var result = db.AdminUsers.updateOne(
|
||||
{ Email: '$ADMIN_EMAIL' },
|
||||
{
|
||||
\$set: {
|
||||
PasswordHash: '$HASH',
|
||||
LastLogin: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (result.modifiedCount > 0) {
|
||||
print('✓ Password updated successfully');
|
||||
} else {
|
||||
print('✗ Failed to update password');
|
||||
}
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Password Reset Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Login credentials:"
|
||||
echo " Email: $ADMIN_EMAIL"
|
||||
echo " Password: $NEW_PASSWORD"
|
||||
echo ""
|
||||
echo "Test login:"
|
||||
echo " https://skyarts.ddns.net/admin/login"
|
||||
echo ""
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/hash_password.cs
|
||||
118
reset-password.csx
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env dotnet-script
|
||||
#r "nuget: MongoDB.Driver, 2.23.1"
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Bson;
|
||||
|
||||
// Configuration
|
||||
var connectionString = "mongodb://localhost:27017";
|
||||
var databaseName = "SkyArtShopDB";
|
||||
var collectionName = "AdminUsers";
|
||||
var adminEmail = "admin@skyartshop.com";
|
||||
|
||||
// Get password from command line or use default
|
||||
var newPassword = Args.Count > 0 ? Args[0] : "Admin123!";
|
||||
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine("SkyArt Shop - Direct Password Reset");
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Email: {adminEmail}");
|
||||
Console.WriteLine($"New Password: {newPassword}");
|
||||
Console.WriteLine();
|
||||
|
||||
// Hash password using PBKDF2 (same as AuthService)
|
||||
string HashPassword(string password)
|
||||
{
|
||||
using var rng = RandomNumberGenerator.Create();
|
||||
var salt = new byte[16];
|
||||
rng.GetBytes(salt);
|
||||
|
||||
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256);
|
||||
var hash = pbkdf2.GetBytes(32);
|
||||
|
||||
var hashBytes = new byte[48];
|
||||
Array.Copy(salt, 0, hashBytes, 0, 16);
|
||||
Array.Copy(hash, 0, hashBytes, 16, 32);
|
||||
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var client = new MongoClient(connectionString);
|
||||
var database = client.GetDatabase(databaseName);
|
||||
var collection = database.GetCollection<BsonDocument>(collectionName);
|
||||
|
||||
// Find existing admin
|
||||
var filter = Builders<BsonDocument>.Filter.Eq("Email", adminEmail);
|
||||
var existingUser = await collection.Find(filter).FirstOrDefaultAsync();
|
||||
|
||||
var newPasswordHash = HashPassword(newPassword);
|
||||
|
||||
if (existingUser != null)
|
||||
{
|
||||
// Update existing user
|
||||
var update = Builders<BsonDocument>.Update
|
||||
.Set("PasswordHash", newPasswordHash)
|
||||
.Set("LastLogin", DateTime.UtcNow);
|
||||
|
||||
var result = await collection.UpdateOneAsync(filter, update);
|
||||
|
||||
if (result.ModifiedCount > 0)
|
||||
{
|
||||
Console.WriteLine("✓ Password updated successfully!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("⚠️ User found but not updated");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new admin user
|
||||
var newUser = new BsonDocument
|
||||
{
|
||||
{ "Email", adminEmail },
|
||||
{ "PasswordHash", newPasswordHash },
|
||||
{ "Name", "System Administrator" },
|
||||
{ "Role", "MasterAdmin" },
|
||||
{ "Permissions", new BsonArray
|
||||
{
|
||||
"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 },
|
||||
{ "CreatedAt", DateTime.UtcNow },
|
||||
{ "LastLogin", BsonNull.Value },
|
||||
{ "CreatedBy", "Direct Reset Script" },
|
||||
{ "Phone", BsonNull.Value },
|
||||
{ "Notes", $"Created via direct reset on {DateTime.UtcNow:yyyy-MM-dd HH:mm:ss}" }
|
||||
};
|
||||
|
||||
await collection.InsertOneAsync(newUser);
|
||||
Console.WriteLine("✓ New admin user created!");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine("✓ Success!");
|
||||
Console.WriteLine("========================================");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Login credentials:");
|
||||
Console.WriteLine($" Email: {adminEmail}");
|
||||
Console.WriteLine($" Password: {newPassword}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Login URL: https://skyarts.ddns.net/admin/login");
|
||||
Console.WriteLine();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"✗ Error: {ex.Message}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
37
setup-database.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SQL Server Database Setup Script
|
||||
# Creates the SkyArtShop database and required tables
|
||||
|
||||
echo "=================================="
|
||||
echo "SkyArtShop Database Setup"
|
||||
echo "=================================="
|
||||
|
||||
# Prompt for SA password
|
||||
read -sp "Enter SA password: " SA_PASSWORD
|
||||
echo ""
|
||||
|
||||
# Database name
|
||||
DB_NAME="SkyArtShopDB"
|
||||
|
||||
# Create database
|
||||
echo "Creating database $DB_NAME..."
|
||||
|
||||
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SA_PASSWORD" -Q "
|
||||
IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = N'$DB_NAME')
|
||||
BEGIN
|
||||
CREATE DATABASE $DB_NAME;
|
||||
PRINT 'Database $DB_NAME created successfully.';
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
PRINT 'Database $DB_NAME already exists.';
|
||||
END
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "Database setup complete!"
|
||||
echo ""
|
||||
echo "Connection String for appsettings.json:"
|
||||
echo "Server=localhost,1433;Database=$DB_NAME;User Id=sa;Password=YOUR_PASSWORD;TrustServerCertificate=True;MultipleActiveResultSets=True"
|
||||
echo ""
|
||||
49
sync-images.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Image Sync Script for SkyArtShop
|
||||
# Keeps uploads directory in sync between main and publish folders
|
||||
|
||||
echo "==================================="
|
||||
echo "SkyArtShop Image Sync Script"
|
||||
echo "==================================="
|
||||
|
||||
SOURCE_DIR="/var/www/SkyArtShop/wwwroot/uploads"
|
||||
PUBLISH_DIR="/var/www/SkyArtShop/publish/wwwroot/uploads"
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$SOURCE_DIR/images"
|
||||
mkdir -p "$PUBLISH_DIR/images"
|
||||
|
||||
echo ""
|
||||
echo "Source: $SOURCE_DIR"
|
||||
echo "Publish: $PUBLISH_DIR"
|
||||
echo ""
|
||||
|
||||
# Sync from source to publish (main direction)
|
||||
echo "Syncing from main wwwroot to publish..."
|
||||
rsync -av --progress "$SOURCE_DIR/" "$PUBLISH_DIR/"
|
||||
|
||||
echo ""
|
||||
echo "Checking for files in publish not in source..."
|
||||
# Also sync back any files that might be in publish but not in source
|
||||
rsync -av --ignore-existing "$PUBLISH_DIR/" "$SOURCE_DIR/"
|
||||
|
||||
echo ""
|
||||
echo "==================================="
|
||||
echo "Image Sync Complete!"
|
||||
echo "==================================="
|
||||
|
||||
# Show statistics
|
||||
SOURCE_COUNT=$(find "$SOURCE_DIR/images" -type f | wc -l)
|
||||
PUBLISH_COUNT=$(find "$PUBLISH_DIR/images" -type f | wc -l)
|
||||
|
||||
echo ""
|
||||
echo "Files in source: $SOURCE_COUNT"
|
||||
echo "Files in publish: $PUBLISH_COUNT"
|
||||
echo ""
|
||||
|
||||
# Set proper permissions
|
||||
echo "Setting permissions..."
|
||||
chmod -R 755 "$SOURCE_DIR"
|
||||
chmod -R 755 "$PUBLISH_DIR"
|
||||
|
||||
echo "Done!"
|
||||
29
sync-uploads.ps1
Executable file
@@ -0,0 +1,29 @@
|
||||
# Sync uploaded images between live site and localhost
|
||||
# Run this script to sync images uploaded on the live site to your development environment
|
||||
|
||||
$liveUploads = "C:\inetpub\wwwroot\skyartshop\wwwroot\uploads\images"
|
||||
$devUploads = "E:\Documents\Website Projects\Sky_Art_Shop\wwwroot\uploads\images"
|
||||
|
||||
Write-Host "🔄 Syncing uploads from live site to localhost..." -ForegroundColor Cyan
|
||||
|
||||
# Copy new files from live to dev
|
||||
Get-ChildItem -Path $liveUploads -File | ForEach-Object {
|
||||
$devFile = Join-Path $devUploads $_.Name
|
||||
if (-not (Test-Path $devFile) -or $_.LastWriteTime -gt (Get-Item $devFile).LastWriteTime) {
|
||||
Copy-Item $_.FullName $devFile -Force
|
||||
Write-Host " ✅ Copied: $($_.Name)" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
# Copy new files from dev to live (if you upload locally)
|
||||
Get-ChildItem -Path $devUploads -File | ForEach-Object {
|
||||
$liveFile = Join-Path $liveUploads $_.Name
|
||||
if (-not (Test-Path $liveFile) -or $_.LastWriteTime -gt (Get-Item $liveFile).LastWriteTime) {
|
||||
Copy-Item $_.FullName $liveFile -Force
|
||||
Write-Host " ✅ Copied to live: $($_.Name)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n✨ Sync complete!" -ForegroundColor Green
|
||||
Write-Host "📊 Total images in dev: $((Get-ChildItem $devUploads).Count)" -ForegroundColor Cyan
|
||||
Write-Host "📊 Total images in live: $((Get-ChildItem $liveUploads).Count)" -ForegroundColor Cyan
|
||||
87
test-homepage-editor.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test Homepage Editor Functionality
|
||||
|
||||
echo "=========================================="
|
||||
echo "Testing Homepage Editor"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check if user can access homepage editor
|
||||
echo "1. Testing Homepage Editor Access..."
|
||||
curl -s -c /tmp/homepage_test.txt -X POST http://localhost:5000/admin/login \
|
||||
-d "email=admin@skyartshop.com&password=Admin123!" -L > /dev/null 2>&1
|
||||
|
||||
curl -s -b /tmp/homepage_test.txt http://localhost:5000/admin/homepage | grep -q "Homepage Editor" && \
|
||||
echo " ✓ Homepage Editor accessible" || echo " ✗ Homepage Editor not accessible"
|
||||
|
||||
echo ""
|
||||
echo "2. Checking Database Structure..."
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -t -c \
|
||||
"SELECT column_name FROM information_schema.columns WHERE table_name = 'homepagesections' ORDER BY ordinal_position;" | \
|
||||
tr '\n' ', ' && echo ""
|
||||
|
||||
echo ""
|
||||
echo "3. Current Homepage Sections:"
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -c \
|
||||
"SELECT id, sectiontype, title, isactive, displayorder FROM homepagesections ORDER BY displayorder;"
|
||||
|
||||
echo ""
|
||||
echo "4. Required Fields for Create:"
|
||||
echo " ✓ SectionType (required) - hero, inspiration, collection, promotion, custom"
|
||||
echo " ✓ Title (required)"
|
||||
echo " - Subtitle (optional)"
|
||||
echo " - Content (optional, uses CKEditor)"
|
||||
echo " - ImageUrl (optional, file upload)"
|
||||
echo " - ButtonText (optional)"
|
||||
echo " - ButtonUrl (optional)"
|
||||
echo " ✓ IsActive (checkbox, defaults to true)"
|
||||
echo " ✓ DisplayOrder (auto-calculated)"
|
||||
echo " ✓ AdditionalData (auto-initialized as empty dictionary)"
|
||||
|
||||
echo ""
|
||||
echo "5. Access URLs:"
|
||||
echo " Admin Editor: https://skyarts.ddns.net/admin/homepage"
|
||||
echo " Create Section: https://skyarts.ddns.net/admin/homepage/section/create"
|
||||
echo " Frontend: https://skyarts.ddns.net/"
|
||||
|
||||
echo ""
|
||||
echo "6. Testing Section Creation (SQL INSERT)..."
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop << 'EOF'
|
||||
INSERT INTO homepagesections (
|
||||
id, sectiontype, title, subtitle, content, imageurl,
|
||||
buttontext, buttonurl, isactive, displayorder,
|
||||
additionaldata, createdat, updatedat
|
||||
) VALUES (
|
||||
gen_random_uuid()::text,
|
||||
'hero',
|
||||
'Welcome to Sky Art Shop',
|
||||
'Scrapbooking and Journaling Fun',
|
||||
'<p>Explore the world of creativity and self-expression</p>',
|
||||
'/uploads/images/hero-banner.jpg',
|
||||
'Shop Now',
|
||||
'/shop',
|
||||
true,
|
||||
0,
|
||||
'{}'::jsonb,
|
||||
now(),
|
||||
now()
|
||||
) ON CONFLICT (id) DO NOTHING;
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "7. Verifying Section Created:"
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -c \
|
||||
"SELECT id, title, sectiontype, isactive FROM homepagesections WHERE sectiontype = 'hero';"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ Homepage Editor Ready!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Next Steps:"
|
||||
echo "1. Visit: https://skyarts.ddns.net/admin/homepage"
|
||||
echo "2. Click 'Create New Section'"
|
||||
echo "3. Fill in required fields (Section Type, Title)"
|
||||
echo "4. Click 'Create Section'"
|
||||
echo "5. View on homepage: https://skyarts.ddns.net/"
|
||||
57
test-image-picker.html
Executable file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Picker Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Image Picker Debugging Instructions</h1>
|
||||
|
||||
<h2>Test Steps:</h2>
|
||||
<ol>
|
||||
<li>Go to: <a href="https://skyarts.ddns.net/admin/homepage" target="_blank">Homepage Editor</a></li>
|
||||
<li>Click on any existing section's "Edit" button</li>
|
||||
<li>Scroll down to "Section Image"</li>
|
||||
<li>Click "Select/Upload Image" button</li>
|
||||
<li>Open Browser Developer Console (F12)</li>
|
||||
<li>Watch for console logs that will show:
|
||||
<ul>
|
||||
<li>"Opening image picker. Mode: single Callback: function"</li>
|
||||
<li>"Modal opened successfully"</li>
|
||||
<li>When you click an image: "Toggle selection for: [URL] Mode: single"</li>
|
||||
<li>When you click "Select Images": "Confirm selection clicked. Selected images: [array]"</li>
|
||||
<li>After callback: "Image selection successful!"</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<h2>What Was Fixed:</h2>
|
||||
<ul>
|
||||
<li>✅ Fixed ID attribute on hidden ImageUrl field</li>
|
||||
<li>✅ Fixed case mismatch in SelectedImageUrl field</li>
|
||||
<li>✅ Changed onclick inline handlers to event listeners (prevents URL escaping issues)</li>
|
||||
<li>✅ Added comprehensive console logging for debugging</li>
|
||||
<li>✅ Added error handling with try-catch blocks</li>
|
||||
<li>✅ Added validation checks for all DOM elements</li>
|
||||
<li>✅ Added user-friendly error alerts</li>
|
||||
</ul>
|
||||
|
||||
<h2>Expected Behavior:</h2>
|
||||
<p>When you select an image and click "Select Images (1)":</p>
|
||||
<ul>
|
||||
<li>The modal should close</li>
|
||||
<li>The image preview should appear below the button</li>
|
||||
<li>The SelectedImageUrl hidden field should contain the image URL</li>
|
||||
<li>When you submit the form, the image should be saved to the section</li>
|
||||
</ul>
|
||||
|
||||
<h2>If Issues Persist:</h2>
|
||||
<p>Check the browser console for error messages. Common issues:</p>
|
||||
<ul>
|
||||
<li>Red error about "callback function not found" - refresh the page</li>
|
||||
<li>No images loading - check /admin/upload/list endpoint</li>
|
||||
<li>Modal not opening - Bootstrap JS not loaded properly</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
53
test-live-dashboard.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test Live Dashboard System Status
|
||||
|
||||
echo "=========================================="
|
||||
echo "Testing Live Dashboard System Status"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Test endpoint accessibility
|
||||
echo "1. Testing system-status endpoint..."
|
||||
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/admin/system-status)
|
||||
if [ "$STATUS_CODE" = "302" ] || [ "$STATUS_CODE" = "401" ]; then
|
||||
echo " ✓ Endpoint exists (redirecting to login - expected)"
|
||||
else
|
||||
echo " Status code: $STATUS_CODE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Dashboard Features:"
|
||||
echo " ✓ Live server time (updates every second)"
|
||||
echo " ✓ PostgreSQL connection status (checks every 10 seconds)"
|
||||
echo " ✓ Database information (host, name, version)"
|
||||
echo " ✓ System online indicator"
|
||||
echo " ✓ Admin user display"
|
||||
echo " ✓ Site name display"
|
||||
|
||||
echo ""
|
||||
echo "3. System Status Response Format:"
|
||||
echo " {
|
||||
\"databaseConnected\": true,
|
||||
\"dbType\": \"PostgreSQL\",
|
||||
\"dbHost\": \"localhost\",
|
||||
\"dbName\": \"skyartshop\",
|
||||
\"dbVersion\": \"16\",
|
||||
\"userCount\": 1,
|
||||
\"timestamp\": \"2025-12-06 14:20:00 UTC\"
|
||||
}"
|
||||
|
||||
echo ""
|
||||
echo "4. Accessing Dashboard:"
|
||||
echo " URL: http://SERVER_IP:5000/admin/dashboard"
|
||||
echo " Login: admin@skyartshop.com / Admin123!"
|
||||
echo ""
|
||||
echo " Once logged in, you will see:"
|
||||
echo " - Live clock updating in real-time"
|
||||
echo " - Green PostgreSQL Connected badge"
|
||||
echo " - Database info: localhost | skyartshop"
|
||||
echo " - System: Online (green badge)"
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✅ Live dashboard system is ready!"
|
||||
echo "=========================================="
|
||||
40
test-product-save.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# Test script to verify product save functionality
|
||||
|
||||
echo "Testing Product Save Functionality"
|
||||
echo "===================================="
|
||||
echo ""
|
||||
|
||||
# Test 1: Check if create page loads
|
||||
echo "Test 1: Checking if product create page loads..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/admin/products/create)
|
||||
if [ "$HTTP_CODE" == "200" ] || [ "$HTTP_CODE" == "302" ]; then
|
||||
echo "✅ Create page accessible (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo "❌ Create page failed (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 2: Check if form has antiforgery token
|
||||
echo "Test 2: Checking if form includes antiforgery token..."
|
||||
if curl -s http://localhost:5000/admin/products/create | grep -q "AntiForgeryToken\|__RequestVerificationToken"; then
|
||||
echo "✅ Antiforgery token found in form"
|
||||
else
|
||||
echo "⚠️ Antiforgery token not found (may require authentication)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test 3: Monitor logs for errors
|
||||
echo "Test 3: Monitoring application logs for recent errors..."
|
||||
echo "Recent application logs:"
|
||||
journalctl -u skyartshop.service -n 10 --no-pager --since "1 minute ago" | grep -i "error\|exception" || echo "✅ No errors in recent logs"
|
||||
echo ""
|
||||
|
||||
echo "===================================="
|
||||
echo "Test complete!"
|
||||
echo ""
|
||||
echo "To test product save manually:"
|
||||
echo "1. Navigate to https://skyarts.ddns.net/admin/products/create"
|
||||
echo "2. Fill in product details (Name and Price are required)"
|
||||
echo "3. Click 'Save Product'"
|
||||
echo "4. Check logs with: journalctl -u skyartshop.service -f"
|
||||
82
test-user-management.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test User Management System
|
||||
# This script tests the complete user management functionality
|
||||
|
||||
BASE_URL="http://localhost:5000"
|
||||
COOKIE_FILE="/tmp/admin_cookies.txt"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Sky Art Shop - User Management Test"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Step 1: Login as Master Admin
|
||||
echo "1. Testing Master Admin Login..."
|
||||
curl -s -c "$COOKIE_FILE" -X POST "$BASE_URL/admin/login" \
|
||||
-d "email=admin@skyartshop.com" \
|
||||
-d "password=Admin123!" \
|
||||
-L | grep -q "Dashboard" && echo "✓ Master Admin login successful" || echo "✗ Master Admin login failed"
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 2: Access User Management Page
|
||||
echo "2. Testing User Management Access..."
|
||||
curl -s -b "$COOKIE_FILE" "$BASE_URL/admin/users" | grep -q "User Management" && echo "✓ User Management page accessible" || echo "✗ User Management page access failed"
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 3: Test Create User Page
|
||||
echo "3. Testing Create User Page..."
|
||||
curl -s -b "$COOKIE_FILE" "$BASE_URL/admin/users/create" | grep -q "Create New User" && echo "✓ Create User page accessible" || echo "✗ Create User page access failed"
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 4: Test Role Permissions
|
||||
echo "4. Checking Role Permissions..."
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -t -c "SELECT DISTINCT role FROM adminusers;" | while read role; do
|
||||
if [ -n "$role" ]; then
|
||||
echo " - Role: $role"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 5: Verify Database Structure
|
||||
echo "5. Verifying Database Structure..."
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -t -c "SELECT column_name FROM information_schema.columns WHERE table_name = 'adminusers' AND column_name IN ('passwordneverexpires', 'passwordexpiresat');" | grep -q "passwordneverexpires" && echo "✓ Password expiration fields exist" || echo "✗ Password expiration fields missing"
|
||||
|
||||
echo ""
|
||||
|
||||
# Step 6: Display Current Users
|
||||
echo "6. Current Users in System:"
|
||||
PGPASSWORD='SkyArt2025Pass!' psql -h localhost -U skyartapp -d skyartshop -c "SELECT email, name, role, isactive, passwordneverexpires FROM adminusers;"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Test Summary"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Master Admin Credentials:"
|
||||
echo " Email: admin@skyartshop.com"
|
||||
echo " Password: Admin123!"
|
||||
echo ""
|
||||
echo "Access URLs:"
|
||||
echo " Admin Panel: $BASE_URL/admin"
|
||||
echo " User Management: $BASE_URL/admin/users"
|
||||
echo " Create User: $BASE_URL/admin/users/create"
|
||||
echo ""
|
||||
echo "Available Roles:"
|
||||
echo " - MasterAdmin (Full system access + user management)"
|
||||
echo " - Admin (Full access except user management)"
|
||||
echo " - Cashier (Orders, payments, customers)"
|
||||
echo " - Accountant (Reports, finances, view-only)"
|
||||
echo ""
|
||||
echo "Features:"
|
||||
echo " ✓ Role-based permissions"
|
||||
echo " ✓ Password never expires option"
|
||||
echo " ✓ User activation/deactivation"
|
||||
echo " ✓ Secure PBKDF2 password hashing"
|
||||
echo " ✓ Last login tracking"
|
||||
echo " ✓ Master Admin protection (cannot be deleted)"
|
||||
echo ""
|
||||
64
test-user-system.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "============================================"
|
||||
echo "Testing User Management System"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
echo "1. Master Admin Login Test"
|
||||
echo "-------------------------"
|
||||
echo "Email: admin@skyartshop.com"
|
||||
echo "Password: ChangeThisPassword123!"
|
||||
echo ""
|
||||
|
||||
echo "2. Checking Identity Database"
|
||||
echo "-----------------------------"
|
||||
sqlite3 /var/www/SkyArtShop/publish/identity.db << 'EOF'
|
||||
.mode column
|
||||
.headers on
|
||||
SELECT UserName, Email, DisplayName FROM AspNetUsers;
|
||||
EOF
|
||||
echo ""
|
||||
|
||||
echo "3. Checking MongoDB AdminUsers"
|
||||
echo "------------------------------"
|
||||
mongosh SkyArtShopDB --eval "db.AdminUsers.find({}, {Name: 1, Email: 1, Role: 1, IsActive: 1}).forEach(function(u){ print('Name: ' + u.Name + ', Email: ' + u.Email + ', Role: ' + u.Role + ', Active: ' + u.IsActive); });" --quiet
|
||||
echo ""
|
||||
|
||||
echo "4. User Management URLs"
|
||||
echo "----------------------"
|
||||
echo "List Users: http://192.168.10.130/admin/users"
|
||||
echo "Create User: http://192.168.10.130/admin/users/create"
|
||||
echo "Admin Login: http://192.168.10.130/admin/login"
|
||||
echo ""
|
||||
|
||||
echo "============================================"
|
||||
echo "How to Create a New User:"
|
||||
echo "============================================"
|
||||
echo "1. Login as Master Admin"
|
||||
echo "2. Go to User Management in sidebar"
|
||||
echo "3. Click 'Add New User'"
|
||||
echo "4. Fill in all required fields:"
|
||||
echo " - Name, Email, Password (min 6 chars)"
|
||||
echo " - Select Role (MasterAdmin/Admin/Cashier/Accountant)"
|
||||
echo " - Set Status (Active/Inactive)"
|
||||
echo "5. Click 'Create User'"
|
||||
echo "6. User can immediately login with their credentials"
|
||||
echo ""
|
||||
|
||||
echo "Password Change:"
|
||||
echo "----------------"
|
||||
echo "1. Go to User Management"
|
||||
echo "2. Click Edit button for the user"
|
||||
echo "3. Enter new password in 'New Password' field"
|
||||
echo "4. Leave blank to keep current password"
|
||||
echo "5. Click 'Update User'"
|
||||
echo "6. Password is immediately updated in BOTH databases"
|
||||
echo ""
|
||||
|
||||
echo "Both Systems Synchronized:"
|
||||
echo "--------------------------"
|
||||
echo "✓ ASP.NET Identity (SQLite) - For authentication/login"
|
||||
echo "✓ MongoDB AdminUsers - For profile, roles, permissions"
|
||||
echo "✓ Changes in admin panel update BOTH systems"
|
||||
echo "============================================"
|
||||
118
verify-auth.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SkyArt Shop - System Status & Verification Tool
|
||||
# Checks authentication, database, and service health
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " SkyArt Shop - System Verification"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check service status
|
||||
echo -e "${BLUE}[1/6] Service Status${NC}"
|
||||
if systemctl is-active --quiet skyartshop.service; then
|
||||
echo -e " ${GREEN}✓${NC} Service is running"
|
||||
UPTIME=$(systemctl show skyartshop.service --property=ActiveEnterTimestamp --value)
|
||||
echo " Uptime: $UPTIME"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} Service is NOT running"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check MongoDB
|
||||
echo -e "${BLUE}[2/6] MongoDB Connection${NC}"
|
||||
if mongosh --quiet --eval "db.version()" SkyArtShopDB >/dev/null 2>&1; then
|
||||
MONGO_VERSION=$(mongosh --quiet --eval "db.version()" SkyArtShopDB 2>/dev/null)
|
||||
echo -e " ${GREEN}✓${NC} MongoDB connected (version $MONGO_VERSION)"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} MongoDB connection failed"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check admin user
|
||||
echo -e "${BLUE}[3/6] Admin User${NC}"
|
||||
ADMIN_EXISTS=$(mongosh SkyArtShopDB --quiet --eval 'db.AdminUsers.countDocuments({Email: "admin@skyartshop.com"})' 2>/dev/null)
|
||||
if [ "$ADMIN_EXISTS" = "1" ]; then
|
||||
echo -e " ${GREEN}✓${NC} Admin user exists"
|
||||
ADMIN_ROLE=$(mongosh SkyArtShopDB --quiet --eval 'db.AdminUsers.findOne({Email: "admin@skyartshop.com"}, {Role: 1, _id: 0}).Role' 2>/dev/null)
|
||||
echo " Role: $ADMIN_ROLE"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} Admin user NOT found"
|
||||
echo " Run: ./reset-admin-password.sh to create"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check DataProtection keys
|
||||
echo -e "${BLUE}[4/6] DataProtection Keys${NC}"
|
||||
KEYS_DIR="/var/www/SkyArtShop/publish/DataProtection-Keys"
|
||||
if [ -d "$KEYS_DIR" ]; then
|
||||
KEY_COUNT=$(ls -1 "$KEYS_DIR"/*.xml 2>/dev/null | wc -l)
|
||||
if [ "$KEY_COUNT" -gt 0 ]; then
|
||||
echo -e " ${GREEN}✓${NC} $KEY_COUNT key(s) found"
|
||||
LATEST_KEY=$(ls -t "$KEYS_DIR"/*.xml 2>/dev/null | head -1 | xargs basename)
|
||||
echo " Latest: $LATEST_KEY"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} No keys found (will be generated)"
|
||||
fi
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} Keys directory doesn't exist"
|
||||
echo " Run: ./fix-cookies.sh to create"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test web endpoint
|
||||
echo -e "${BLUE}[5/6] Web Endpoint${NC}"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://skyarts.ddns.net/admin/login -k)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e " ${GREEN}✓${NC} Login page accessible (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} Login page returned HTTP $HTTP_CODE"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Test authentication
|
||||
echo -e "${BLUE}[6/6] Authentication Test${NC}"
|
||||
rm -f /tmp/test-cookies.txt
|
||||
curl -c /tmp/test-cookies.txt -b /tmp/test-cookies.txt \
|
||||
-X POST "https://skyarts.ddns.net/admin/login" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "email=admin@skyartshop.com&password=Admin123!" \
|
||||
-k -s -o /dev/null
|
||||
|
||||
DASHBOARD_TITLE=$(curl -b /tmp/test-cookies.txt "https://skyarts.ddns.net/admin/dashboard" -k -s 2>/dev/null | grep -oP '<title>.*?</title>')
|
||||
rm -f /tmp/test-cookies.txt
|
||||
|
||||
if [[ "$DASHBOARD_TITLE" == *"Dashboard"* ]]; then
|
||||
echo -e " ${GREEN}✓${NC} Login successful"
|
||||
echo " Dashboard accessible: $DASHBOARD_TITLE"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} Login failed or dashboard inaccessible"
|
||||
echo " Try: ./reset-admin-password.sh Admin123!"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "=========================================="
|
||||
echo -e "${GREEN}System Status: Operational${NC}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Quick Access:"
|
||||
echo " • Admin Panel: https://skyarts.ddns.net/admin/login"
|
||||
echo " • Default Login: admin@skyartshop.com / Admin123!"
|
||||
echo ""
|
||||
echo "Management Commands:"
|
||||
echo " • Reset password: ./reset-admin-password.sh [password]"
|
||||
echo " • Fix cookies: ./fix-cookies.sh"
|
||||
echo " • Service logs: sudo journalctl -u skyartshop.service -f"
|
||||
echo " • Restart service: sudo systemctl restart skyartshop.service"
|
||||
echo ""
|
||||
175
verify-deployment.sh
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Sky Art Shop Deployment Verification Script
|
||||
# This script verifies that the website is running correctly
|
||||
|
||||
echo "============================================"
|
||||
echo "Sky Art Shop - Deployment Verification"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to check service status
|
||||
check_service() {
|
||||
local service=$1
|
||||
if systemctl is-active --quiet $service; then
|
||||
echo -e "${GREEN}✓${NC} $service is running"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗${NC} $service is NOT running"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check port
|
||||
check_port() {
|
||||
local port=$1
|
||||
local service=$2
|
||||
if ss -tlnp | grep -q ":$port "; then
|
||||
echo -e "${GREEN}✓${NC} Port $port is listening ($service)"
|
||||
return 0
|
||||
else
|
||||
echo -e "${RED}✗${NC} Port $port is NOT listening ($service)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 1. Check Services
|
||||
echo "1. Checking Services Status..."
|
||||
echo "-----------------------------------"
|
||||
check_service mongod
|
||||
check_service nginx
|
||||
check_service skyartshop.service
|
||||
echo ""
|
||||
|
||||
# 2. Check Ports
|
||||
echo "2. Checking Ports..."
|
||||
echo "-----------------------------------"
|
||||
check_port 27017 "MongoDB"
|
||||
check_port 80 "Nginx"
|
||||
check_port 5000 "SkyArtShop App"
|
||||
echo ""
|
||||
|
||||
# 3. Check MongoDB Connection
|
||||
echo "3. Checking MongoDB Connection..."
|
||||
echo "-----------------------------------"
|
||||
if mongosh --eval "db.adminCommand('ping')" --quiet > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓${NC} MongoDB is accessible"
|
||||
|
||||
# Check SkyArtShopDB
|
||||
if mongosh --eval "use SkyArtShopDB; db.stats()" --quiet > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓${NC} SkyArtShopDB database exists"
|
||||
|
||||
# Count collections
|
||||
COLLECTIONS=$(mongosh SkyArtShopDB --eval "db.getCollectionNames().length" --quiet)
|
||||
echo -e "${GREEN}✓${NC} Found $COLLECTIONS collections in database"
|
||||
|
||||
# Show collection counts
|
||||
echo ""
|
||||
echo " Collection Data:"
|
||||
mongosh SkyArtShopDB --eval "
|
||||
print(' - Products: ' + db.Products.countDocuments());
|
||||
print(' - Pages: ' + db.Pages.countDocuments());
|
||||
print(' - MenuItems: ' + db.MenuItems.countDocuments());
|
||||
print(' - Users: ' + db.Users.countDocuments());
|
||||
print(' - SiteSettings: ' + db.SiteSettings.countDocuments());
|
||||
print(' - HomepageSections: ' + db.HomepageSections.countDocuments());
|
||||
print(' - PortfolioCategories: ' + db.PortfolioCategories.countDocuments());
|
||||
" --quiet 2>/dev/null
|
||||
else
|
||||
echo -e "${RED}✗${NC} SkyArtShopDB database not found"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} Cannot connect to MongoDB"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Check Web Application
|
||||
echo "4. Checking Web Application..."
|
||||
echo "-----------------------------------"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓${NC} Website is responding (HTTP $HTTP_CODE)"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Website returned HTTP $HTTP_CODE"
|
||||
fi
|
||||
|
||||
# Test admin login page
|
||||
ADMIN_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/admin)
|
||||
if [ "$ADMIN_CODE" = "200" ] || [ "$ADMIN_CODE" = "302" ]; then
|
||||
echo -e "${GREEN}✓${NC} Admin panel is accessible"
|
||||
else
|
||||
echo -e "${YELLOW}!${NC} Admin panel returned HTTP $ADMIN_CODE"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. Check Network Configuration
|
||||
echo "5. Network Configuration..."
|
||||
echo "-----------------------------------"
|
||||
IP_ADDRESS=$(hostname -I | awk '{print $1}')
|
||||
echo -e "${GREEN}✓${NC} Server IP: $IP_ADDRESS"
|
||||
|
||||
# Check nginx configuration
|
||||
NGINX_SERVER_NAME=$(grep -E "^\s*server_name" /etc/nginx/sites-enabled/skyartshop 2>/dev/null | awk '{print $2}' | tr -d ';')
|
||||
if [ -z "$NGINX_SERVER_NAME" ] || [ "$NGINX_SERVER_NAME" = "_" ]; then
|
||||
echo -e "${YELLOW}!${NC} Nginx is configured for ANY domain (server_name _)"
|
||||
echo " Access via: http://$IP_ADDRESS"
|
||||
else
|
||||
echo -e "${GREEN}✓${NC} Nginx configured for: $NGINX_SERVER_NAME"
|
||||
echo " Access via: http://$NGINX_SERVER_NAME"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 6. Check Application Files
|
||||
echo "6. Application Files..."
|
||||
echo "-----------------------------------"
|
||||
if [ -f "/var/www/SkyArtShop/publish/SkyArtShop.dll" ]; then
|
||||
echo -e "${GREEN}✓${NC} Application binary exists"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Application binary NOT found"
|
||||
fi
|
||||
|
||||
if [ -f "/var/www/SkyArtShop/appsettings.Production.json" ]; then
|
||||
echo -e "${GREEN}✓${NC} Production settings exist"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Production settings NOT found"
|
||||
fi
|
||||
|
||||
UPLOAD_DIR="/var/www/SkyArtShop/wwwroot/uploads"
|
||||
if [ -d "$UPLOAD_DIR" ]; then
|
||||
UPLOAD_COUNT=$(find $UPLOAD_DIR -type f 2>/dev/null | wc -l)
|
||||
echo -e "${GREEN}✓${NC} Uploads directory exists ($UPLOAD_COUNT files)"
|
||||
else
|
||||
echo -e "${YELLOW}!${NC} Uploads directory not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 7. Recent Application Logs
|
||||
echo "7. Recent Application Logs..."
|
||||
echo "-----------------------------------"
|
||||
if journalctl -u skyartshop.service -n 5 --no-pager 2>/dev/null | tail -5; then
|
||||
echo ""
|
||||
else
|
||||
echo -e "${YELLOW}!${NC} Unable to read application logs"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "Verification Complete!"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Access your website at:"
|
||||
echo " - http://$IP_ADDRESS"
|
||||
if [ ! -z "$NGINX_SERVER_NAME" ] && [ "$NGINX_SERVER_NAME" != "_" ]; then
|
||||
echo " - http://$NGINX_SERVER_NAME"
|
||||
fi
|
||||
echo ""
|
||||
echo "Admin Panel:"
|
||||
echo " - http://$IP_ADDRESS/admin"
|
||||
echo ""
|
||||
103
verify-mongodb-connection.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ MongoDB Connection Verification ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# 1. Check MongoDB Service
|
||||
echo "[1/5] MongoDB Service Status"
|
||||
if systemctl is-active --quiet mongod; then
|
||||
echo " ✅ MongoDB service is running"
|
||||
UPTIME=$(systemctl show mongod --property=ActiveEnterTimestamp --value)
|
||||
echo " 📅 Uptime: $UPTIME"
|
||||
else
|
||||
echo " ❌ MongoDB service is NOT running"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Check MongoDB Port
|
||||
echo "[2/5] MongoDB Network Binding"
|
||||
if sudo ss -tulpn | grep -q 27017; then
|
||||
echo " ✅ MongoDB listening on port 27017"
|
||||
BIND=$(sudo ss -tulpn | grep 27017 | awk '{print $5}')
|
||||
echo " 🔌 Binding: $BIND"
|
||||
else
|
||||
echo " ❌ MongoDB NOT listening on port 27017"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. Test Direct Connection
|
||||
echo "[3/5] Direct MongoDB Connection Test"
|
||||
if mongosh --quiet --eval "db.version()" SkyArtShopDB >/dev/null 2>&1; then
|
||||
VERSION=$(mongosh --quiet --eval "db.version()" SkyArtShopDB 2>/dev/null)
|
||||
echo " ✅ Can connect to MongoDB"
|
||||
echo " 📦 Version: $VERSION"
|
||||
|
||||
COLLECTIONS=$(mongosh --quiet --eval "db.getCollectionNames().length" SkyArtShopDB 2>/dev/null)
|
||||
echo " 📚 Collections: $COLLECTIONS"
|
||||
|
||||
PRODUCTS=$(mongosh --quiet --eval "db.Products.countDocuments()" SkyArtShopDB 2>/dev/null)
|
||||
echo " 🛍️ Products: $PRODUCTS"
|
||||
else
|
||||
echo " ❌ Cannot connect to MongoDB"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Check Application Configuration
|
||||
echo "[4/5] Application MongoDB Configuration"
|
||||
if [ -f /var/www/SkyArtShop/publish/appsettings.Production.json ]; then
|
||||
CONN_STRING=$(grep -A 1 "ConnectionString" /var/www/SkyArtShop/publish/appsettings.Production.json | grep -oP '(?<=": ")[^"]+')
|
||||
DB_NAME=$(grep -A 2 "MongoDB" /var/www/SkyArtShop/publish/appsettings.Production.json | grep "DatabaseName" | grep -oP '(?<=": ")[^"]+')
|
||||
|
||||
echo " ✅ Configuration file found"
|
||||
echo " 🔗 Connection: $CONN_STRING"
|
||||
echo " 💾 Database: $DB_NAME"
|
||||
else
|
||||
echo " ❌ Configuration file NOT found"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. Test Website Data Retrieval
|
||||
echo "[5/5] Website Data Retrieval Test"
|
||||
PRODUCTS_ON_PAGE=$(curl -s https://skyarts.ddns.net/Shop 2>&1 | grep -c "product-card")
|
||||
if [ "$PRODUCTS_ON_PAGE" -gt 0 ]; then
|
||||
echo " ✅ Website retrieving data from MongoDB"
|
||||
echo " 📦 Products displayed: $PRODUCTS_ON_PAGE"
|
||||
|
||||
# Test admin dashboard
|
||||
curl -c /tmp/mongo-test.txt -b /tmp/mongo-test.txt -X POST "https://skyarts.ddns.net/admin/login" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "email=admin@skyartshop.com&password=Admin123!" \
|
||||
-k -s -o /dev/null 2>&1
|
||||
|
||||
if curl -b /tmp/mongo-test.txt "https://skyarts.ddns.net/admin/products" -k -s 2>&1 | grep -q "product"; then
|
||||
echo " ✅ Admin backend accessing MongoDB"
|
||||
fi
|
||||
rm -f /tmp/mongo-test.txt
|
||||
else
|
||||
echo " ❌ Website NOT retrieving data"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Summary
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ✅ CONNECTION VERIFIED ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "Architecture:"
|
||||
echo " 🖥️ Linux Server: $(hostname)"
|
||||
echo " 🌐 Web Application: ASP.NET Core 8.0 (localhost:5000)"
|
||||
echo " 💾 MongoDB: Local (127.0.0.1:27017)"
|
||||
echo " 🔒 Nginx Proxy: Public (skyarts.ddns.net:443)"
|
||||
echo ""
|
||||
echo "Data Flow:"
|
||||
echo " Browser → Nginx (SSL) → ASP.NET App → MongoDB"
|
||||
echo " (All running on the same Linux server)"
|
||||
echo ""
|
||||
echo "✅ Website is properly connected to MongoDB on this Linux server"
|
||||
echo ""
|
||||
49
verify-system.bat
Executable file
@@ -0,0 +1,49 @@
|
||||
@echo off
|
||||
echo ============================================
|
||||
echo Sky Art Shop - Full System Verification
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
echo [1/5] Checking if backend is running...
|
||||
powershell -Command "$proc = Get-Process | Where-Object {$_.ProcessName -like '*SkyArtShop*'}; if ($proc) { Write-Host ' ✓ Backend is running (PID: ' $proc.Id ')' -ForegroundColor Green } else { Write-Host ' ✗ Backend is NOT running' -ForegroundColor Red; Write-Host ' Start with: cd Admin; dotnet run --launch-profile https' -ForegroundColor Yellow }"
|
||||
echo.
|
||||
|
||||
echo [2/5] Testing API endpoint...
|
||||
powershell -Command "try { $products = Invoke-RestMethod -Uri 'http://localhost:5000/api/products' -ErrorAction Stop; Write-Host ' ✓ API working - Found' $products.Count 'products' -ForegroundColor Green } catch { Write-Host ' ✗ API not responding: ' $_.Exception.Message -ForegroundColor Red }"
|
||||
echo.
|
||||
|
||||
echo [3/5] Checking product images...
|
||||
powershell -Command "$products = Invoke-RestMethod -Uri 'http://localhost:5000/api/products' 2>$null; foreach ($p in $products) { if ($p.images.Count -gt 0) { Write-Host ' ✓' $p.name '- HAS' $p.images.Count 'images' -ForegroundColor Green } else { Write-Host ' ✗' $p.name '- NO images (upload needed)' -ForegroundColor Yellow } }"
|
||||
echo.
|
||||
|
||||
echo [4/5] Testing image serving...
|
||||
powershell -Command "try { $img = Invoke-WebRequest -Uri 'http://localhost:5000/uploads/products/2dbdad6c-c4a6-4f60-a1ce-3ff3b88a13ae.jpg' -Method Head -ErrorAction Stop; Write-Host ' ✓ Images are accessible via HTTP' -ForegroundColor Green } catch { Write-Host ' ⚠ Test image not found (might be OK if you have different images)' -ForegroundColor Yellow }"
|
||||
echo.
|
||||
|
||||
echo [5/5] Checking demo files...
|
||||
if exist "shop-demo.html" (
|
||||
echo ✓ shop-demo.html exists
|
||||
) else (
|
||||
echo ✗ shop-demo.html missing
|
||||
)
|
||||
if exist "js\api-integration.js" (
|
||||
echo ✓ api-integration.js exists
|
||||
) else (
|
||||
echo ✗ api-integration.js missing
|
||||
)
|
||||
if exist "css\api-styles.css" (
|
||||
echo ✓ api-styles.css exists
|
||||
) else (
|
||||
echo ✗ api-styles.css missing
|
||||
)
|
||||
echo.
|
||||
|
||||
echo ============================================
|
||||
echo NEXT STEPS:
|
||||
echo ============================================
|
||||
echo 1. Open demo: shop-demo.html
|
||||
echo 2. Add images: https://localhost:5001/admin/products
|
||||
echo 3. Integrate: See SHOP_HTML_INTEGRATION.html
|
||||
echo ============================================
|
||||
echo.
|
||||
pause
|
||||
171
verify-system.sh
Executable file
@@ -0,0 +1,171 @@
|
||||
#!/bin/bash
|
||||
# SkyArtShop System Verification Script
|
||||
# Verifies MongoDB, images, and application status
|
||||
|
||||
echo "========================================="
|
||||
echo "SkyArtShop System Verification"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# Color codes
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check MongoDB
|
||||
echo "1. MongoDB Service Status"
|
||||
echo "-----------------------------------------"
|
||||
if systemctl is-active --quiet mongod; then
|
||||
echo -e "${GREEN}✓ MongoDB is running${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ MongoDB is not running${NC}"
|
||||
fi
|
||||
|
||||
# Check MongoDB connection
|
||||
echo ""
|
||||
echo "2. MongoDB Database Check"
|
||||
echo "-----------------------------------------"
|
||||
DB_CHECK=$(mongosh --quiet --eval "db.adminCommand('ping').ok" 2>&1)
|
||||
if [ "$DB_CHECK" = "1" ]; then
|
||||
echo -e "${GREEN}✓ MongoDB connection successful${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Cannot connect to MongoDB${NC}"
|
||||
fi
|
||||
|
||||
# Check collections
|
||||
echo ""
|
||||
echo "3. MongoDB Collections"
|
||||
echo "-----------------------------------------"
|
||||
mongosh --quiet SkyArtShopDB --eval '
|
||||
db.getCollectionNames().forEach(function(col) {
|
||||
var count = db.getCollection(col).countDocuments();
|
||||
print(col + ": " + count + " documents");
|
||||
})'
|
||||
|
||||
# Check products and their images
|
||||
echo ""
|
||||
echo "4. Product Data Integrity"
|
||||
echo "-----------------------------------------"
|
||||
MISSING_IMAGES=0
|
||||
mongosh --quiet SkyArtShopDB --eval '
|
||||
var products = db.Products.find({}).toArray();
|
||||
products.forEach(function(p) {
|
||||
print("Product: " + p.Name);
|
||||
print(" ID: " + p._id);
|
||||
print(" Price: $" + p.Price);
|
||||
print(" ImageUrl: " + (p.ImageUrl || "MISSING"));
|
||||
print(" Images: " + (p.Images ? p.Images.length : 0) + " images");
|
||||
print(" Colors: " + (p.Colors ? p.Colors.join(", ") : "none"));
|
||||
print(" Active: " + p.IsActive);
|
||||
print("");
|
||||
});
|
||||
'
|
||||
|
||||
# Check if images exist on filesystem
|
||||
echo ""
|
||||
echo "5. Image File Verification"
|
||||
echo "-----------------------------------------"
|
||||
IMAGES_DIR="/var/www/SkyArtShop/wwwroot/uploads/images"
|
||||
if [ -d "$IMAGES_DIR" ]; then
|
||||
IMAGE_COUNT=$(find "$IMAGES_DIR" -type f | wc -l)
|
||||
echo -e "${GREEN}✓ Images directory exists${NC}"
|
||||
echo " Total images: $IMAGE_COUNT"
|
||||
|
||||
# Check product images
|
||||
mongosh --quiet SkyArtShopDB --eval '
|
||||
var products = db.Products.find({}).toArray();
|
||||
products.forEach(function(p) {
|
||||
if (p.ImageUrl) {
|
||||
var filename = p.ImageUrl.split("/").pop();
|
||||
print(filename);
|
||||
}
|
||||
if (p.Images) {
|
||||
p.Images.forEach(function(img) {
|
||||
var filename = img.split("/").pop();
|
||||
print(filename);
|
||||
});
|
||||
}
|
||||
});
|
||||
' | while read -r img; do
|
||||
if [ -n "$img" ] && [ ! -f "$IMAGES_DIR/$img" ]; then
|
||||
echo -e "${RED} ✗ Missing: $img${NC}"
|
||||
MISSING_IMAGES=$((MISSING_IMAGES + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $MISSING_IMAGES -eq 0 ]; then
|
||||
echo -e "${GREEN} ✓ All product images exist${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Images directory not found${NC}"
|
||||
fi
|
||||
|
||||
# Check application status
|
||||
echo ""
|
||||
echo "6. Application Status"
|
||||
echo "-----------------------------------------"
|
||||
if pgrep -f "SkyArtShop.dll" > /dev/null; then
|
||||
echo -e "${GREEN}✓ Application is running${NC}"
|
||||
APP_PID=$(pgrep -f "SkyArtShop.dll")
|
||||
echo " Process ID: $APP_PID"
|
||||
|
||||
# Check if port is listening
|
||||
if ss -tln | grep -q ":5001"; then
|
||||
echo -e "${GREEN}✓ Application listening on port 5001${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Application not listening on expected port${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Application is not running${NC}"
|
||||
fi
|
||||
|
||||
# Check Nginx
|
||||
echo ""
|
||||
echo "7. Web Server Status"
|
||||
echo "-----------------------------------------"
|
||||
if systemctl is-active --quiet nginx; then
|
||||
echo -e "${GREEN}✓ Nginx is running${NC}"
|
||||
if ss -tln | grep -q ":80"; then
|
||||
echo -e "${GREEN}✓ Nginx listening on port 80${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Nginx is not running${NC}"
|
||||
fi
|
||||
|
||||
# Test website response
|
||||
echo ""
|
||||
echo "8. Website Response Test"
|
||||
echo "-----------------------------------------"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5001/ 2>&1)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✓ Website responding correctly (HTTP 200)${NC}"
|
||||
elif [ "$HTTP_CODE" = "000" ]; then
|
||||
echo -e "${RED}✗ Cannot connect to website${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Website returned HTTP $HTTP_CODE${NC}"
|
||||
fi
|
||||
|
||||
# Test shop page
|
||||
SHOP_TEST=$(curl -s http://localhost:5001/shop 2>&1 | grep -c "product-card")
|
||||
if [ "$SHOP_TEST" -gt 0 ]; then
|
||||
echo -e "${GREEN}✓ Shop page displaying $SHOP_TEST products${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ Shop page may have issues${NC}"
|
||||
fi
|
||||
|
||||
# Disk space check
|
||||
echo ""
|
||||
echo "9. Disk Space"
|
||||
echo "-----------------------------------------"
|
||||
df -h /var/www/SkyArtShop | tail -1 | awk '{print " Used: " $3 " / " $2 " (" $5 ")"}'
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo "Verification Complete"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "All critical systems checked."
|
||||
echo "If any issues were found, please review the output above."
|
||||
echo ""
|
||||
67
verify-website.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=========================================="
|
||||
echo " SkyArt Shop - Website Verification"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Test Homepage
|
||||
echo "[1/5] Testing Homepage..."
|
||||
HOME_SECTIONS=$(curl -s https://skyarts.ddns.net/ | grep -c "hero\|inspiration")
|
||||
if [ "$HOME_SECTIONS" -gt 0 ]; then
|
||||
echo " ✓ Homepage sections loading ($HOME_SECTIONS found)"
|
||||
else
|
||||
echo " ✗ Homepage sections NOT loading"
|
||||
fi
|
||||
|
||||
# Test Shop Page
|
||||
echo ""
|
||||
echo "[2/5] Testing Shop Page..."
|
||||
PRODUCT_COUNT=$(curl -s https://skyarts.ddns.net/Shop | grep -c "product-card")
|
||||
if [ "$PRODUCT_COUNT" -gt 0 ]; then
|
||||
echo " ✓ Products displaying ($PRODUCT_COUNT products found)"
|
||||
else
|
||||
echo " ✗ Products NOT displaying"
|
||||
fi
|
||||
|
||||
# Test Navigation
|
||||
echo ""
|
||||
echo "[3/5] Testing Navigation..."
|
||||
NAV_COUNT=$(curl -s https://skyarts.ddns.net/ | grep -A 30 "nav-menu" | grep -c "<li>")
|
||||
echo " ✓ Navigation items: $NAV_COUNT"
|
||||
|
||||
# Test Database
|
||||
echo ""
|
||||
echo "[4/5] Testing Database..."
|
||||
DB_PRODUCTS=$(mongosh SkyArtShopDB --quiet --eval "db.Products.countDocuments()")
|
||||
DB_PAGES=$(mongosh SkyArtShopDB --quiet --eval "db.Pages.countDocuments()")
|
||||
DB_MENU=$(mongosh SkyArtShopDB --quiet --eval "db.MenuItems.countDocuments()")
|
||||
echo " ✓ Products in DB: $DB_PRODUCTS"
|
||||
echo " ✓ Pages in DB: $DB_PAGES"
|
||||
echo " ✓ Menu items in DB: $DB_MENU"
|
||||
|
||||
# Test Admin Backend
|
||||
echo ""
|
||||
echo "[5/5] Testing Admin Backend..."
|
||||
curl -c /tmp/admin-verify.txt -b /tmp/admin-verify.txt -X POST "https://skyarts.ddns.net/admin/login" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "email=admin@skyartshop.com&password=Admin123!" \
|
||||
-k -s -o /dev/null
|
||||
|
||||
ADMIN_TITLE=$(curl -b /tmp/admin-verify.txt "https://skyarts.ddns.net/admin/dashboard" -k -s | grep -oP '<title>.*?</title>')
|
||||
rm -f /tmp/admin-verify.txt
|
||||
|
||||
if [[ "$ADMIN_TITLE" == *"Dashboard"* ]]; then
|
||||
echo " ✓ Admin backend accessible"
|
||||
else
|
||||
echo " ✗ Admin backend NOT accessible"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "✓ Website Verification Complete"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Visit: https://skyarts.ddns.net/"
|
||||
echo "Admin: https://skyarts.ddns.net/admin/login"
|
||||
echo ""
|
||||
4223
wwwroot/assets/css/main.css
Executable file
BIN
wwwroot/assets/images/about-1.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/about-2.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/cardmaking.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/craft-supplies.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/hero-craft.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/journals.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/products/placeholder.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/products/product-1.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/products/product-2.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/products/product-3.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/products/product-4.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/stickers.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
BIN
wwwroot/assets/images/washi-tape.jpg
Executable file
|
After Width: | Height: | Size: 103 KiB |
155
wwwroot/assets/js/admin.js
Executable file
@@ -0,0 +1,155 @@
|
||||
// Sky Art Shop - Admin Panel Functions
|
||||
|
||||
// Delete confirmation with fetch
|
||||
function deleteWithConfirmation(url, itemName, redirectUrl) {
|
||||
if (!confirm(`Are you sure you want to delete ${itemName}?`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
window.location.href = redirectUrl || window.location.href;
|
||||
} else {
|
||||
alert("Delete failed. Please try again.");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Error: " + error.message);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Image upload with preview
|
||||
function uploadImageWithPreview(fileInputId, previewId, urlInputId) {
|
||||
const input = document.getElementById(fileInputId);
|
||||
const file = input.files[0];
|
||||
|
||||
if (!file) {
|
||||
alert("Please select a file");
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
fetch("/admin/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
if (result.success) {
|
||||
document.getElementById(urlInputId).value = result.url;
|
||||
const preview = document.getElementById(previewId);
|
||||
if (preview) {
|
||||
preview.src = result.url;
|
||||
preview.style.display = "block";
|
||||
}
|
||||
showAdminNotification("Image uploaded successfully!", "success");
|
||||
} else {
|
||||
showAdminNotification("Upload failed: " + result.message, "error");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
showAdminNotification("Upload error: " + error.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
// Show admin notification
|
||||
function showAdminNotification(message, type = "success") {
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `alert alert-${
|
||||
type === "success" ? "success" : "danger"
|
||||
} alert-dismissible fade show`;
|
||||
notification.style.cssText =
|
||||
"position: fixed; top: 20px; right: 20px; z-index: 9999; min-width: 300px;";
|
||||
notification.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.remove("show");
|
||||
setTimeout(() => notification.remove(), 150);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Copy to clipboard
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
showAdminNotification("Copied to clipboard!", "success");
|
||||
})
|
||||
.catch(() => {
|
||||
showAdminNotification("Failed to copy", "error");
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-generate slug from title
|
||||
function generateSlugFromTitle(titleInputId, slugInputId) {
|
||||
const titleInput = document.getElementById(titleInputId);
|
||||
const slugInput = document.getElementById(slugInputId);
|
||||
|
||||
if (titleInput && slugInput) {
|
||||
titleInput.addEventListener("input", function () {
|
||||
if (slugInput.value === "" || slugInput.dataset.auto === "true") {
|
||||
slugInput.value = titleInput.value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)/g, "");
|
||||
slugInput.dataset.auto = "true";
|
||||
}
|
||||
});
|
||||
|
||||
slugInput.addEventListener("input", function () {
|
||||
slugInput.dataset.auto = "false";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation helper
|
||||
function validateForm(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return true;
|
||||
|
||||
const requiredFields = form.querySelectorAll("[required]");
|
||||
let isValid = true;
|
||||
|
||||
requiredFields.forEach((field) => {
|
||||
if (!field.value.trim()) {
|
||||
field.classList.add("is-invalid");
|
||||
isValid = false;
|
||||
} else {
|
||||
field.classList.remove("is-invalid");
|
||||
}
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
showAdminNotification("Please fill in all required fields", "error");
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// Initialize tooltips
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Bootstrap tooltips
|
||||
var tooltipTriggerList = [].slice.call(
|
||||
document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
);
|
||||
if (typeof bootstrap !== "undefined") {
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
}
|
||||
});
|
||||
378
wwwroot/assets/js/cart.js
Executable file
@@ -0,0 +1,378 @@
|
||||
// Sky Art Shop - Shopping Cart Functions
|
||||
|
||||
// Add item to cart
|
||||
function addToCart(id, name, price, imageUrl = null) {
|
||||
// Get existing cart from localStorage
|
||||
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
|
||||
|
||||
// Check if item already exists
|
||||
const existingItem = cart.find((item) => item.id === id);
|
||||
|
||||
if (existingItem) {
|
||||
existingItem.quantity++;
|
||||
// Update imageUrl if it was null before
|
||||
if (!existingItem.imageUrl && imageUrl) {
|
||||
existingItem.imageUrl = imageUrl;
|
||||
}
|
||||
} else {
|
||||
cart.push({ id, name, price, quantity: 1, imageUrl });
|
||||
}
|
||||
|
||||
// Save cart
|
||||
localStorage.setItem("cart", JSON.stringify(cart));
|
||||
console.log("Cart updated:", cart);
|
||||
|
||||
// Show confirmation
|
||||
showCartNotification(`${name} added to cart!`);
|
||||
updateCartCount();
|
||||
}
|
||||
|
||||
// Remove item from cart
|
||||
function removeFromCart(id) {
|
||||
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
|
||||
cart = cart.filter((item) => item.id !== id);
|
||||
localStorage.setItem("cart", JSON.stringify(cart));
|
||||
updateCartCount();
|
||||
}
|
||||
|
||||
// Update cart item quantity
|
||||
function updateCartQuantity(id, quantity) {
|
||||
let cart = JSON.parse(localStorage.getItem("cart") || "[]");
|
||||
const item = cart.find((item) => item.id === id);
|
||||
if (item) {
|
||||
item.quantity = quantity;
|
||||
if (quantity <= 0) {
|
||||
cart = cart.filter((item) => item.id !== id);
|
||||
}
|
||||
}
|
||||
localStorage.setItem("cart", JSON.stringify(cart));
|
||||
updateCartCount();
|
||||
}
|
||||
|
||||
// Get cart items
|
||||
function getCart() {
|
||||
return JSON.parse(localStorage.getItem("cart") || "[]");
|
||||
}
|
||||
|
||||
// Get cart total
|
||||
function getCartTotal() {
|
||||
const cart = getCart();
|
||||
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
|
||||
}
|
||||
|
||||
// Update cart count badge
|
||||
function updateCartCount() {
|
||||
const cart = getCart();
|
||||
const count = cart.reduce((total, item) => total + item.quantity, 0);
|
||||
|
||||
// Update old badge (if exists)
|
||||
const badge = document.getElementById("cart-count");
|
||||
if (badge) {
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? "inline" : "none";
|
||||
}
|
||||
|
||||
// Update navbar cart badge
|
||||
const navCartBadge = document.querySelector("#cartBtn .badge");
|
||||
if (navCartBadge) {
|
||||
navCartBadge.textContent = count;
|
||||
navCartBadge.style.display = count > 0 ? "block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Show cart notification
|
||||
function showCartNotification(message) {
|
||||
const notification = document.createElement("div");
|
||||
notification.className = "cart-notification";
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
|
||||
z-index: 10000;
|
||||
animation: slideInFromTop 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease";
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Clear entire cart
|
||||
function clearCart() {
|
||||
localStorage.removeItem("cart");
|
||||
updateCartCount();
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Wishlist Functions
|
||||
// ====================================
|
||||
|
||||
// Add item to wishlist
|
||||
function addToWishlist(id, name, price, imageUrl) {
|
||||
let wishlist = JSON.parse(localStorage.getItem("wishlist") || "[]");
|
||||
|
||||
const existingItem = wishlist.find((item) => item.id === id);
|
||||
|
||||
if (existingItem) {
|
||||
showWishlistNotification(`${name} is already in your wishlist!`);
|
||||
return;
|
||||
}
|
||||
|
||||
wishlist.push({ id, name, price, imageUrl });
|
||||
localStorage.setItem("wishlist", JSON.stringify(wishlist));
|
||||
console.log("Wishlist updated:", wishlist);
|
||||
|
||||
showWishlistNotification(`${name} added to wishlist!`);
|
||||
updateWishlistCount();
|
||||
}
|
||||
|
||||
// Remove item from wishlist
|
||||
function removeFromWishlist(id) {
|
||||
let wishlist = JSON.parse(localStorage.getItem("wishlist") || "[]");
|
||||
wishlist = wishlist.filter((item) => item.id !== id);
|
||||
localStorage.setItem("wishlist", JSON.stringify(wishlist));
|
||||
updateWishlistCount();
|
||||
}
|
||||
|
||||
// Get wishlist items
|
||||
function getWishlist() {
|
||||
return JSON.parse(localStorage.getItem("wishlist") || "[]");
|
||||
}
|
||||
|
||||
// Update wishlist count badge
|
||||
function updateWishlistCount() {
|
||||
const wishlist = getWishlist();
|
||||
const count = wishlist.length;
|
||||
|
||||
const navWishlistBadge = document.querySelector("#wishlistBtn .badge");
|
||||
const wishlistIcon = document.querySelector("#wishlistBtn i");
|
||||
|
||||
if (navWishlistBadge) {
|
||||
navWishlistBadge.textContent = count;
|
||||
navWishlistBadge.style.display = count > 0 ? "block" : "none";
|
||||
}
|
||||
|
||||
// Change heart icon based on wishlist status
|
||||
if (wishlistIcon) {
|
||||
if (count > 0) {
|
||||
wishlistIcon.className = "bi bi-heart-fill";
|
||||
wishlistIcon.style.color = "#e74c3c";
|
||||
} else {
|
||||
wishlistIcon.className = "bi bi-heart";
|
||||
wishlistIcon.style.color = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show wishlist notification
|
||||
function showWishlistNotification(message) {
|
||||
const notification = document.createElement("div");
|
||||
notification.className = "wishlist-notification";
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
background: #E91E63;
|
||||
color: white;
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
|
||||
z-index: 10000;
|
||||
animation: slideInFromTop 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease";
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Clear entire wishlist
|
||||
function clearWishlist() {
|
||||
localStorage.removeItem("wishlist");
|
||||
updateWishlistCount();
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Dropdown Functions
|
||||
// ====================================
|
||||
// Render cart dropdown
|
||||
function renderCartDropdown() {
|
||||
const cart = getCart();
|
||||
const cartItems = document.getElementById("cartItems");
|
||||
const cartTotal = document.getElementById("cartTotal");
|
||||
|
||||
if (!cartItems) return;
|
||||
|
||||
if (cart.length === 0) {
|
||||
cartItems.innerHTML = '<p class="empty-message">Your cart is empty</p>';
|
||||
if (cartTotal) cartTotal.textContent = "$0.00";
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Rendering cart:", cart);
|
||||
|
||||
cartItems.innerHTML = cart
|
||||
.map((item) => {
|
||||
const imgSrc = item.imageUrl || "/assets/images/placeholder.jpg";
|
||||
console.log("Cart item image URL:", imgSrc);
|
||||
return `
|
||||
<div class="dropdown-item">
|
||||
<img src="${imgSrc}" alt="${
|
||||
item.name
|
||||
}" class="dropdown-item-image" onerror="this.src='/assets/images/placeholder.jpg'">
|
||||
<div class="dropdown-item-info">
|
||||
<div class="dropdown-item-name">${item.name}</div>
|
||||
<div class="dropdown-item-details">
|
||||
<span class="dropdown-item-quantity">Qty: ${
|
||||
item.quantity
|
||||
}</span>
|
||||
<span class="dropdown-item-price">$${(
|
||||
item.price * item.quantity
|
||||
).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="dropdown-item-remove" onclick="removeFromCartDropdown('${
|
||||
item.id
|
||||
}')">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
if (cartTotal) {
|
||||
const total = getCartTotal();
|
||||
cartTotal.textContent = `$${total.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Render wishlist dropdown
|
||||
function renderWishlistDropdown() {
|
||||
const wishlist = getWishlist();
|
||||
const wishlistItems = document.getElementById("wishlistItems");
|
||||
|
||||
if (!wishlistItems) return;
|
||||
|
||||
if (wishlist.length === 0) {
|
||||
wishlistItems.innerHTML =
|
||||
'<p class="empty-message">Your wishlist is empty</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Rendering wishlist:", wishlist);
|
||||
|
||||
wishlistItems.innerHTML = wishlist
|
||||
.map((item) => {
|
||||
const imgSrc = item.imageUrl || "/assets/images/placeholder.jpg";
|
||||
console.log("Wishlist item image URL:", imgSrc);
|
||||
return `
|
||||
<div class="dropdown-item">
|
||||
<img src="${imgSrc}" alt="${
|
||||
item.name
|
||||
}" class="dropdown-item-image" onerror="this.src='/assets/images/placeholder.jpg'">
|
||||
<div class="dropdown-item-info">
|
||||
<div class="dropdown-item-name">${item.name}</div>
|
||||
<div class="dropdown-item-details">
|
||||
<span class="dropdown-item-price">$${item.price.toFixed(
|
||||
2
|
||||
)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="dropdown-item-remove" onclick="removeFromWishlistDropdown('${
|
||||
item.id
|
||||
}')">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
// Remove from cart via dropdown
|
||||
function removeFromCartDropdown(id) {
|
||||
removeFromCart(id);
|
||||
renderCartDropdown();
|
||||
updateCartCount();
|
||||
}
|
||||
|
||||
// Remove from wishlist via dropdown
|
||||
function removeFromWishlistDropdown(id) {
|
||||
removeFromWishlist(id);
|
||||
renderWishlistDropdown();
|
||||
updateWishlistCount();
|
||||
}
|
||||
|
||||
// Toggle dropdown visibility
|
||||
function toggleDropdown(dropdownId) {
|
||||
const dropdown = document.getElementById(dropdownId);
|
||||
if (!dropdown) return;
|
||||
|
||||
// Close other dropdowns
|
||||
document.querySelectorAll(".icon-dropdown").forEach((d) => {
|
||||
if (d.id !== dropdownId) {
|
||||
d.classList.remove("show");
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.classList.toggle("show");
|
||||
|
||||
// Render content when opening
|
||||
if (dropdown.classList.contains("show")) {
|
||||
if (dropdownId === "cartDropdown") {
|
||||
renderCartDropdown();
|
||||
} else if (dropdownId === "wishlistDropdown") {
|
||||
renderWishlistDropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close cart/wishlist dropdowns when clicking outside
|
||||
document.addEventListener("click", function (e) {
|
||||
if (
|
||||
!e.target.closest(".dropdown-container") &&
|
||||
!e.target.closest(".nav-toggle")
|
||||
) {
|
||||
document.querySelectorAll(".icon-dropdown").forEach((d) => {
|
||||
d.classList.remove("show");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize cart and wishlist count on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
updateCartCount();
|
||||
updateWishlistCount();
|
||||
|
||||
// Add click handlers for dropdown toggles
|
||||
const cartBtn = document.getElementById("cartBtn");
|
||||
const wishlistBtn = document.getElementById("wishlistBtn");
|
||||
|
||||
if (cartBtn) {
|
||||
cartBtn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
toggleDropdown("cartDropdown");
|
||||
});
|
||||
}
|
||||
|
||||
if (wishlistBtn) {
|
||||
wishlistBtn.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
toggleDropdown("wishlistDropdown");
|
||||
});
|
||||
}
|
||||
});
|
||||
427
wwwroot/assets/js/main.js
Executable file
@@ -0,0 +1,427 @@
|
||||
// Sky Art Shop - Main JavaScript File
|
||||
|
||||
// ====================================
|
||||
// Mobile Navigation Toggle
|
||||
// ====================================
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const navToggle = document.querySelector(".nav-toggle");
|
||||
const navMenu = document.querySelector("#navDropdown");
|
||||
|
||||
if (navToggle && navMenu) {
|
||||
// Hover to open dropdown
|
||||
navToggle.addEventListener("mouseenter", function () {
|
||||
navMenu.classList.add("active");
|
||||
this.setAttribute("aria-expanded", "true");
|
||||
|
||||
const spans = this.querySelectorAll("span");
|
||||
spans[0].style.transform = "rotate(45deg) translate(7px, 7px)";
|
||||
spans[1].style.opacity = "0";
|
||||
spans[2].style.transform = "rotate(-45deg) translate(7px, -7px)";
|
||||
});
|
||||
|
||||
// Keep dropdown open when hovering over it
|
||||
navMenu.addEventListener("mouseenter", function () {
|
||||
this.classList.add("active");
|
||||
});
|
||||
|
||||
// Close when mouse leaves both hamburger and dropdown
|
||||
navToggle.addEventListener("mouseleave", function (e) {
|
||||
// Delay closing to allow moving to dropdown
|
||||
setTimeout(() => {
|
||||
if (!navMenu.matches(":hover") && !navToggle.matches(":hover")) {
|
||||
navMenu.classList.remove("active");
|
||||
navToggle.setAttribute("aria-expanded", "false");
|
||||
|
||||
const spans = navToggle.querySelectorAll("span");
|
||||
spans[0].style.transform = "none";
|
||||
spans[1].style.opacity = "1";
|
||||
spans[2].style.transform = "none";
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
navMenu.addEventListener("mouseleave", function () {
|
||||
setTimeout(() => {
|
||||
if (!navMenu.matches(":hover") && !navToggle.matches(":hover")) {
|
||||
navMenu.classList.remove("active");
|
||||
navToggle.setAttribute("aria-expanded", "false");
|
||||
|
||||
const spans = navToggle.querySelectorAll("span");
|
||||
spans[0].style.transform = "none";
|
||||
spans[1].style.opacity = "1";
|
||||
spans[2].style.transform = "none";
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// Click to toggle (for mobile/touch)
|
||||
navToggle.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
const isActive = navMenu.classList.toggle("active");
|
||||
this.setAttribute("aria-expanded", isActive ? "true" : "false");
|
||||
|
||||
// Animate hamburger menu
|
||||
const spans = this.querySelectorAll("span");
|
||||
if (isActive) {
|
||||
spans[0].style.transform = "rotate(45deg) translate(7px, 7px)";
|
||||
spans[1].style.opacity = "0";
|
||||
spans[2].style.transform = "rotate(-45deg) translate(7px, -7px)";
|
||||
} else {
|
||||
spans[0].style.transform = "none";
|
||||
spans[1].style.opacity = "1";
|
||||
spans[2].style.transform = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when clicking on a link
|
||||
const dropdownLinks = navMenu.querySelectorAll("a");
|
||||
dropdownLinks.forEach((link) => {
|
||||
link.addEventListener("click", function () {
|
||||
navMenu.classList.remove("active");
|
||||
navToggle.setAttribute("aria-expanded", "false");
|
||||
const spans = navToggle.querySelectorAll("span");
|
||||
spans[0].style.transform = "none";
|
||||
spans[1].style.opacity = "1";
|
||||
spans[2].style.transform = "none";
|
||||
});
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener("click", function (event) {
|
||||
// Don't close if clicking on cart/wishlist dropdowns
|
||||
if (
|
||||
event.target.closest(".dropdown-container") ||
|
||||
event.target.closest(".icon-dropdown")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isClickInside =
|
||||
navToggle.contains(event.target) || navMenu.contains(event.target);
|
||||
if (!isClickInside && navMenu.classList.contains("active")) {
|
||||
navMenu.classList.remove("active");
|
||||
navToggle.setAttribute("aria-expanded", "false");
|
||||
const spans = navToggle.querySelectorAll("span");
|
||||
spans[0].style.transform = "none";
|
||||
spans[1].style.opacity = "1";
|
||||
spans[2].style.transform = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ====================================
|
||||
// Smooth Scrolling for Anchor Links
|
||||
// ====================================
|
||||
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||
anchor.addEventListener("click", function (e) {
|
||||
const href = this.getAttribute("href");
|
||||
if (href !== "#" && href !== "#instagram" && href !== "#wishlist") {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(href);
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================
|
||||
// Shop Page Filtering
|
||||
// ====================================
|
||||
const categoryFilter = document.getElementById("category-filter");
|
||||
const sortFilter = document.getElementById("sort-filter");
|
||||
|
||||
if (categoryFilter) {
|
||||
categoryFilter.addEventListener("change", function () {
|
||||
const selectedCategory = this.value;
|
||||
const productCards = document.querySelectorAll(".product-card");
|
||||
|
||||
productCards.forEach((card) => {
|
||||
if (selectedCategory === "all") {
|
||||
card.style.display = "block";
|
||||
} else {
|
||||
const cardCategory = card.getAttribute("data-category");
|
||||
if (cardCategory === selectedCategory) {
|
||||
card.style.display = "block";
|
||||
} else {
|
||||
card.style.display = "none";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sortFilter) {
|
||||
sortFilter.addEventListener("change", function () {
|
||||
const sortValue = this.value;
|
||||
const productsGrid = document.querySelector(".products-grid");
|
||||
const productCards = Array.from(document.querySelectorAll(".product-card"));
|
||||
|
||||
if (sortValue === "price-low") {
|
||||
productCards.sort((a, b) => {
|
||||
const priceA = parseFloat(
|
||||
a.querySelector(".price").textContent.replace("$", "")
|
||||
);
|
||||
const priceB = parseFloat(
|
||||
b.querySelector(".price").textContent.replace("$", "")
|
||||
);
|
||||
return priceA - priceB;
|
||||
});
|
||||
} else if (sortValue === "price-high") {
|
||||
productCards.sort((a, b) => {
|
||||
const priceA = parseFloat(
|
||||
a.querySelector(".price").textContent.replace("$", "")
|
||||
);
|
||||
const priceB = parseFloat(
|
||||
b.querySelector(".price").textContent.replace("$", "")
|
||||
);
|
||||
return priceB - priceA;
|
||||
});
|
||||
}
|
||||
|
||||
// Re-append sorted cards
|
||||
productCards.forEach((card) => {
|
||||
productsGrid.appendChild(card);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Add to Cart Functionality (Basic)
|
||||
// ====================================
|
||||
document.querySelectorAll(".product-card .btn").forEach((button) => {
|
||||
button.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get product details
|
||||
const productCard = this.closest(".product-card");
|
||||
const productName = productCard.querySelector("h3").textContent;
|
||||
const productPrice = productCard.querySelector(".price").textContent;
|
||||
|
||||
// Show notification
|
||||
showNotification(`${productName} added to cart!`);
|
||||
|
||||
// You can expand this to actually store cart items
|
||||
// For example, using localStorage or sending to a server
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================
|
||||
// Contact Form Handling
|
||||
// ====================================
|
||||
const contactForm = document.getElementById("contactForm");
|
||||
|
||||
if (contactForm) {
|
||||
contactForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form values
|
||||
const name = document.getElementById("name").value;
|
||||
const email = document.getElementById("email").value;
|
||||
const phone = document.getElementById("phone").value;
|
||||
const subject = document.getElementById("subject").value;
|
||||
const message = document.getElementById("message").value;
|
||||
|
||||
// Basic validation
|
||||
if (!name || !email || !subject || !message) {
|
||||
showNotification("Please fill in all required fields!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
showNotification("Please enter a valid email address!", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Here you would typically send the form data to a server
|
||||
// For now, we'll just show a success message
|
||||
showNotification(
|
||||
"Thank you! Your message has been sent. We'll get back to you soon.",
|
||||
"success"
|
||||
);
|
||||
|
||||
// Reset form
|
||||
contactForm.reset();
|
||||
});
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Notification System
|
||||
// ====================================
|
||||
function showNotification(message, type = "success") {
|
||||
// Create notification element
|
||||
const notification = document.createElement("div");
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Style the notification
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
background-color: ${type === "success" ? "#4CAF50" : "#F44336"};
|
||||
color: white;
|
||||
padding: 15px 25px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
z-index: 10000;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
`;
|
||||
|
||||
// Add animation
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Add to page
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.animation = "slideOut 0.3s ease-out";
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Image Lazy Loading (Optional Enhancement)
|
||||
// ====================================
|
||||
if ("IntersectionObserver" in window) {
|
||||
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src || img.src;
|
||||
img.classList.add("loaded");
|
||||
observer.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("img").forEach((img) => {
|
||||
imageObserver.observe(img);
|
||||
});
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Scroll to Top Button
|
||||
// ====================================
|
||||
function createScrollToTopButton() {
|
||||
const button = document.createElement("button");
|
||||
button.innerHTML = "↑";
|
||||
button.className = "scroll-to-top";
|
||||
button.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
background-color: #6B4E9B;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
`;
|
||||
|
||||
document.body.appendChild(button);
|
||||
|
||||
// Show/hide button based on scroll position
|
||||
window.addEventListener("scroll", () => {
|
||||
if (window.pageYOffset > 300) {
|
||||
button.style.display = "block";
|
||||
} else {
|
||||
button.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// Scroll to top when clicked
|
||||
button.addEventListener("click", () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
});
|
||||
|
||||
// Hover effect
|
||||
button.addEventListener("mouseenter", () => {
|
||||
button.style.backgroundColor = "#5a3e82";
|
||||
button.style.transform = "translateY(-3px)";
|
||||
});
|
||||
|
||||
button.addEventListener("mouseleave", () => {
|
||||
button.style.backgroundColor = "#6B4E9B";
|
||||
button.style.transform = "translateY(0)";
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize scroll to top button
|
||||
createScrollToTopButton();
|
||||
|
||||
// ====================================
|
||||
// Portfolio Gallery Hover Effects
|
||||
// ====================================
|
||||
document.querySelectorAll(".portfolio-category").forEach((category) => {
|
||||
category.addEventListener("mouseenter", function () {
|
||||
this.style.transition = "all 0.3s ease";
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================
|
||||
// Active Navigation Link Highlighting
|
||||
// ====================================
|
||||
function highlightActiveNavLink() {
|
||||
const currentPage = window.location.pathname.split("/").pop() || "index.html";
|
||||
const navLinks = document.querySelectorAll(".nav-menu a");
|
||||
|
||||
navLinks.forEach((link) => {
|
||||
const linkPage = link.getAttribute("href").split("/").pop().split("#")[0];
|
||||
if (linkPage === currentPage) {
|
||||
link.classList.add("active");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
highlightActiveNavLink();
|
||||
|
||||
// ====================================
|
||||
// Print console message
|
||||
// ====================================
|
||||
console.log(
|
||||
"%c Sky Art Shop Website ",
|
||||
"background: #6B4E9B; color: white; font-size: 20px; padding: 10px;"
|
||||
);
|
||||
console.log("Welcome to Sky Art Shop! 🎨");
|
||||