Updatweb
This commit is contained in:
264
config/database-fixes.sql
Normal file
264
config/database-fixes.sql
Normal file
@@ -0,0 +1,264 @@
|
||||
-- SkyArtShop Database Schema Fixes
|
||||
-- Date: December 18, 2025
|
||||
-- Purpose: Fix missing columns, add constraints, optimize queries
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 1: Fix Missing Columns
|
||||
-- =====================================================
|
||||
|
||||
-- Fix pages table - add ispublished column
|
||||
ALTER TABLE pages
|
||||
ADD COLUMN IF NOT EXISTS ispublished BOOLEAN DEFAULT true;
|
||||
|
||||
-- Standardize pages table columns
|
||||
UPDATE pages SET ispublished = isactive WHERE ispublished IS NULL;
|
||||
|
||||
-- Fix portfolioprojects table - add imageurl column
|
||||
ALTER TABLE portfolioprojects
|
||||
ADD COLUMN IF NOT EXISTS imageurl VARCHAR(500);
|
||||
|
||||
-- Migrate featuredimage to imageurl for consistency
|
||||
UPDATE portfolioprojects
|
||||
SET imageurl = featuredimage
|
||||
WHERE imageurl IS NULL AND featuredimage IS NOT NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 2: Add Missing Indexes for Performance
|
||||
-- =====================================================
|
||||
|
||||
-- Products table indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_products_isactive ON products(isactive);
|
||||
CREATE INDEX IF NOT EXISTS idx_products_category ON products(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_products_isfeatured ON products(isfeatured) WHERE isfeatured = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_products_createdat ON products(createdat DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_products_slug ON products(slug);
|
||||
|
||||
-- Blog posts indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_blogposts_ispublished ON blogposts(ispublished);
|
||||
CREATE INDEX IF NOT EXISTS idx_blogposts_slug ON blogposts(slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_blogposts_createdat ON blogposts(createdat DESC);
|
||||
|
||||
-- Portfolio projects indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_portfolioprojects_isactive ON portfolioprojects(isactive);
|
||||
CREATE INDEX IF NOT EXISTS idx_portfolioprojects_categoryid ON portfolioprojects(categoryid);
|
||||
CREATE INDEX IF NOT EXISTS idx_portfolioprojects_displayorder ON portfolioprojects(displayorder);
|
||||
|
||||
-- Pages indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_pages_isactive ON pages(isactive);
|
||||
CREATE INDEX IF NOT EXISTS idx_pages_slug ON pages(slug);
|
||||
|
||||
-- Admin users indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_adminusers_email ON adminusers(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_adminusers_isactive ON adminusers(isactive);
|
||||
CREATE INDEX IF NOT EXISTS idx_adminusers_role_id ON adminusers(role_id);
|
||||
|
||||
-- Session index (for cleanup)
|
||||
CREATE INDEX IF NOT EXISTS idx_session_expire ON session(expire);
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 3: Add Constraints and Validations
|
||||
-- =====================================================
|
||||
|
||||
-- Ensure NOT NULL constraints where appropriate
|
||||
ALTER TABLE products
|
||||
ALTER COLUMN isactive SET DEFAULT true,
|
||||
ALTER COLUMN isfeatured SET DEFAULT false,
|
||||
ALTER COLUMN isbestseller SET DEFAULT false,
|
||||
ALTER COLUMN stockquantity SET DEFAULT 0;
|
||||
|
||||
ALTER TABLE blogposts
|
||||
ALTER COLUMN ispublished SET DEFAULT false,
|
||||
ALTER COLUMN views SET DEFAULT 0;
|
||||
|
||||
ALTER TABLE portfolioprojects
|
||||
ALTER COLUMN isactive SET DEFAULT true,
|
||||
ALTER COLUMN displayorder SET DEFAULT 0;
|
||||
|
||||
ALTER TABLE pages
|
||||
ALTER COLUMN isactive SET DEFAULT true;
|
||||
|
||||
-- Add unique constraints
|
||||
ALTER TABLE products DROP CONSTRAINT IF EXISTS unique_products_slug;
|
||||
ALTER TABLE products ADD CONSTRAINT unique_products_slug UNIQUE(slug);
|
||||
|
||||
ALTER TABLE blogposts DROP CONSTRAINT IF EXISTS unique_blogposts_slug;
|
||||
ALTER TABLE blogposts ADD CONSTRAINT unique_blogposts_slug UNIQUE(slug);
|
||||
|
||||
ALTER TABLE pages DROP CONSTRAINT IF EXISTS unique_pages_slug;
|
||||
ALTER TABLE pages ADD CONSTRAINT unique_pages_slug UNIQUE(slug);
|
||||
|
||||
ALTER TABLE adminusers DROP CONSTRAINT IF EXISTS unique_adminusers_email;
|
||||
ALTER TABLE adminusers ADD CONSTRAINT unique_adminusers_email UNIQUE(email);
|
||||
|
||||
-- Add check constraints
|
||||
ALTER TABLE products DROP CONSTRAINT IF EXISTS check_products_price_positive;
|
||||
ALTER TABLE products ADD CONSTRAINT check_products_price_positive CHECK (price >= 0);
|
||||
|
||||
ALTER TABLE products DROP CONSTRAINT IF EXISTS check_products_stock_nonnegative;
|
||||
ALTER TABLE products ADD CONSTRAINT check_products_stock_nonnegative CHECK (stockquantity >= 0);
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 4: Add Foreign Key Constraints
|
||||
-- =====================================================
|
||||
|
||||
-- Portfolio projects to categories (if portfoliocategories table exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'portfoliocategories') THEN
|
||||
ALTER TABLE portfolioprojects DROP CONSTRAINT IF EXISTS fk_portfolioprojects_category;
|
||||
ALTER TABLE portfolioprojects
|
||||
ADD CONSTRAINT fk_portfolioprojects_category
|
||||
FOREIGN KEY (categoryid) REFERENCES portfoliocategories(id)
|
||||
ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 5: Optimize Existing Data
|
||||
-- =====================================================
|
||||
|
||||
-- Ensure all timestamps are set
|
||||
UPDATE products SET updatedat = createdat WHERE updatedat IS NULL;
|
||||
UPDATE blogposts SET updatedat = createdat WHERE updatedat IS NULL;
|
||||
UPDATE portfolioprojects SET updatedat = createdat WHERE updatedat IS NULL;
|
||||
UPDATE pages SET updatedat = createdat WHERE updatedat IS NULL;
|
||||
|
||||
-- Set default values for nullable booleans
|
||||
UPDATE products SET isactive = true WHERE isactive IS NULL;
|
||||
UPDATE products SET isfeatured = false WHERE isfeatured IS NULL;
|
||||
UPDATE products SET isbestseller = false WHERE isbestseller IS NULL;
|
||||
|
||||
UPDATE blogposts SET ispublished = false WHERE ispublished IS NULL;
|
||||
UPDATE portfolioprojects SET isactive = true WHERE isactive IS NULL;
|
||||
UPDATE pages SET isactive = true WHERE isactive IS NULL;
|
||||
UPDATE pages SET ispublished = true WHERE ispublished IS NULL;
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 6: Create Useful Views for Reporting
|
||||
-- =====================================================
|
||||
|
||||
-- View for active products with sales data
|
||||
CREATE OR REPLACE VIEW v_active_products AS
|
||||
SELECT
|
||||
id, name, slug, price, stockquantity, category,
|
||||
imageurl, isfeatured, isbestseller,
|
||||
unitssold, totalrevenue, averagerating, totalreviews,
|
||||
createdat
|
||||
FROM products
|
||||
WHERE isactive = true
|
||||
ORDER BY createdat DESC;
|
||||
|
||||
-- View for published blog posts
|
||||
CREATE OR REPLACE VIEW v_published_blogposts AS
|
||||
SELECT
|
||||
id, title, slug, excerpt, imageurl,
|
||||
authorname, publisheddate, views, tags,
|
||||
createdat
|
||||
FROM blogposts
|
||||
WHERE ispublished = true
|
||||
ORDER BY publisheddate DESC NULLS LAST, createdat DESC;
|
||||
|
||||
-- View for active portfolio projects
|
||||
CREATE OR REPLACE VIEW v_active_portfolio AS
|
||||
SELECT
|
||||
id, title, description, imageurl, featuredimage,
|
||||
category, categoryid, displayorder,
|
||||
createdat
|
||||
FROM portfolioprojects
|
||||
WHERE isactive = true
|
||||
ORDER BY displayorder ASC, createdat DESC;
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 7: Add Triggers for Automatic Timestamps
|
||||
-- =====================================================
|
||||
|
||||
-- Function to update timestamp
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updatedat = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Add triggers for products
|
||||
DROP TRIGGER IF EXISTS trg_products_update ON products;
|
||||
CREATE TRIGGER trg_products_update
|
||||
BEFORE UPDATE ON products
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
-- Add triggers for blogposts
|
||||
DROP TRIGGER IF EXISTS trg_blogposts_update ON blogposts;
|
||||
CREATE TRIGGER trg_blogposts_update
|
||||
BEFORE UPDATE ON blogposts
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
-- Add triggers for portfolioprojects
|
||||
DROP TRIGGER IF EXISTS trg_portfolioprojects_update ON portfolioprojects;
|
||||
CREATE TRIGGER trg_portfolioprojects_update
|
||||
BEFORE UPDATE ON portfolioprojects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
-- Add triggers for pages
|
||||
DROP TRIGGER IF EXISTS trg_pages_update ON pages;
|
||||
CREATE TRIGGER trg_pages_update
|
||||
BEFORE UPDATE ON pages
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
-- Add triggers for adminusers
|
||||
DROP TRIGGER IF EXISTS trg_adminusers_update ON adminusers;
|
||||
CREATE TRIGGER trg_adminusers_update
|
||||
BEFORE UPDATE ON adminusers
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
-- =====================================================
|
||||
-- PHASE 8: Clean Up Expired Sessions
|
||||
-- =====================================================
|
||||
|
||||
-- Delete expired sessions (run periodically)
|
||||
DELETE FROM session WHERE expire < NOW();
|
||||
|
||||
-- =====================================================
|
||||
-- VERIFICATION QUERIES
|
||||
-- =====================================================
|
||||
|
||||
-- Verify all critical columns exist
|
||||
SELECT
|
||||
'products' as table_name,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='products' AND column_name='isactive') as has_isactive,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='products' AND column_name='isfeatured') as has_isfeatured;
|
||||
|
||||
SELECT
|
||||
'pages' as table_name,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='pages' AND column_name='ispublished') as has_ispublished,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='pages' AND column_name='isactive') as has_isactive;
|
||||
|
||||
SELECT
|
||||
'portfolioprojects' as table_name,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='portfolioprojects' AND column_name='imageurl') as has_imageurl,
|
||||
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='portfolioprojects' AND column_name='isactive') as has_isactive;
|
||||
|
||||
-- Count indexes
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
COUNT(*) as index_count
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
GROUP BY schemaname, tablename
|
||||
ORDER BY tablename;
|
||||
|
||||
-- Verify constraints
|
||||
SELECT
|
||||
tc.table_name,
|
||||
tc.constraint_type,
|
||||
COUNT(*) as constraint_count
|
||||
FROM information_schema.table_constraints tc
|
||||
WHERE tc.table_schema = 'public'
|
||||
GROUP BY tc.table_name, tc.constraint_type
|
||||
ORDER BY tc.table_name;
|
||||
22
config/ecosystem.config.js
Normal file
22
config/ecosystem.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "skyartshop",
|
||||
script: "server.js",
|
||||
cwd: "/media/pts/Website/SkyArtShop/backend",
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: "500M",
|
||||
// Environment variables are loaded from .env file via dotenv
|
||||
// Do not hardcode sensitive information here
|
||||
env_file: "/media/pts/Website/SkyArtShop/.env",
|
||||
error_file: "/var/log/skyartshop/pm2-error.log",
|
||||
out_file: "/var/log/skyartshop/pm2-output.log",
|
||||
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
|
||||
merge_logs: true,
|
||||
kill_timeout: 5000,
|
||||
listen_timeout: 10000,
|
||||
},
|
||||
],
|
||||
};
|
||||
171
config/nginx-skyartshop-localhost.conf
Normal file
171
config/nginx-skyartshop-localhost.conf
Normal file
@@ -0,0 +1,171 @@
|
||||
# HTTP Server - Serves both localhost and redirects skyarts.ddns.net to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost skyarts.ddns.net;
|
||||
|
||||
# Redirect HTTPS for skyarts.ddns.net only
|
||||
if ($host = skyarts.ddns.net) {
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
# For localhost, serve the site
|
||||
# Logs
|
||||
access_log /var/log/nginx/skyartshop-access.log;
|
||||
error_log /var/log/nginx/skyartshop-error.log;
|
||||
|
||||
# Root directory
|
||||
root /var/www/skyartshop/public;
|
||||
index index.html;
|
||||
|
||||
# Admin area - exact matches to redirect
|
||||
location = /admin {
|
||||
return 302 /admin/login.html;
|
||||
}
|
||||
|
||||
location = /admin/ {
|
||||
return 302 /admin/login.html;
|
||||
}
|
||||
|
||||
# API proxy to Node.js backend
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location /assets/ {
|
||||
alias /var/www/skyartshop/assets/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /uploads/ {
|
||||
alias /var/www/skyartshop/uploads/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Admin static files
|
||||
location /admin/ {
|
||||
alias /var/www/skyartshop/admin/;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Root redirect handled by backend
|
||||
location = / {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS - Main Server for skyarts.ddns.net
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name skyarts.ddns.net;
|
||||
|
||||
# SSL Configuration
|
||||
ssl_certificate /etc/letsencrypt/live/skyarts.ddns.net/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/skyarts.ddns.net/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Security Headers
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Logs
|
||||
access_log /var/log/nginx/skyartshop-access.log;
|
||||
error_log /var/log/nginx/skyartshop-error.log;
|
||||
|
||||
# Root directory
|
||||
root /var/www/skyartshop/public;
|
||||
index index.html;
|
||||
|
||||
# Admin area - exact matches to redirect
|
||||
location = /admin {
|
||||
return 302 /admin/login.html;
|
||||
}
|
||||
|
||||
location = /admin/ {
|
||||
return 302 /admin/login.html;
|
||||
}
|
||||
|
||||
# API proxy to Node.js backend
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location /assets/ {
|
||||
alias /var/www/skyartshop/assets/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /uploads/ {
|
||||
alias /var/www/skyartshop/uploads/;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# Admin static files
|
||||
location /admin/ {
|
||||
alias /var/www/skyartshop/admin/;
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# Root redirect handled by backend
|
||||
location = / {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Health check
|
||||
location /health {
|
||||
proxy_pass http://localhost:5000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
199
config/nginx-skyartshop-secured.conf
Executable file
199
config/nginx-skyartshop-secured.conf
Executable file
@@ -0,0 +1,199 @@
|
||||
# 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 root redirect - exact matches for /admin and /admin/
|
||||
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 1M;
|
||||
}
|
||||
|
||||
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 1M;
|
||||
}
|
||||
|
||||
# API routes - proxy to backend
|
||||
location /api/ {
|
||||
limit_req zone=general burst=100 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;
|
||||
}
|
||||
|
||||
# Admin static files (HTML, CSS, JS)
|
||||
location /admin/ {
|
||||
limit_req zone=admin burst=20 nodelay;
|
||||
|
||||
alias /var/www/skyartshop/admin/;
|
||||
try_files $uri $uri/ =404;
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# No cache for HTML files
|
||||
location ~* \.html$ {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
add_header Expires "0";
|
||||
}
|
||||
}
|
||||
|
||||
# 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
|
||||
}
|
||||
30
config/skyartshop.service
Normal file
30
config/skyartshop.service
Normal file
@@ -0,0 +1,30 @@
|
||||
[Unit]
|
||||
Description=SkyArtShop Node.js Backend Server
|
||||
Documentation=https://github.com/yourusername/skyartshop
|
||||
After=network.target postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=pts
|
||||
WorkingDirectory=/media/pts/Website/SkyArtShop/backend
|
||||
EnvironmentFile=/media/pts/Website/SkyArtShop/backend/.env
|
||||
ExecStartPre=/media/pts/Website/SkyArtShop/pre-start.sh
|
||||
ExecStart=/usr/bin/node /media/pts/Website/SkyArtShop/backend/server.js
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StartLimitInterval=200
|
||||
StartLimitBurst=5
|
||||
StandardOutput=append:/var/log/skyartshop/output.log
|
||||
StandardError=append:/var/log/skyartshop/error.log
|
||||
|
||||
# Restart service after 10 seconds if node service crashes
|
||||
RestartSec=10
|
||||
# Output to systemd journal for easy debugging
|
||||
SyslogIdentifier=skyartshop
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user