updateweb

This commit is contained in:
Local Server
2025-12-14 01:54:40 -06:00
parent dce6460994
commit 61929a5daf
454 changed files with 12193 additions and 42002 deletions

View File

@@ -0,0 +1,65 @@
-- Add site_settings table for storing configuration
CREATE TABLE IF NOT EXISTS site_settings (
key VARCHAR(100) PRIMARY KEY,
settings JSONB NOT NULL DEFAULT '{}',
createdat TIMESTAMP DEFAULT NOW(),
updatedat TIMESTAMP DEFAULT NOW()
);
-- Add indexes for better performance
CREATE INDEX IF NOT EXISTS idx_site_settings_key ON site_settings(key);
-- Insert default settings if they don't exist
INSERT INTO site_settings (key, settings, createdat, updatedat)
VALUES
('general', '{}', NOW(), NOW()),
('homepage', '{}', NOW(), NOW()),
('menu', '{"items":[]}', NOW(), NOW())
ON CONFLICT (key) DO NOTHING;
-- Ensure products table has all necessary columns
ALTER TABLE products
ADD COLUMN IF NOT EXISTS isbestseller BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS category VARCHAR(255),
ADD COLUMN IF NOT EXISTS updatedat TIMESTAMP DEFAULT NOW();
-- Ensure portfolioprojects table has all necessary columns
ALTER TABLE portfolioprojects
ADD COLUMN IF NOT EXISTS category VARCHAR(255),
ADD COLUMN IF NOT EXISTS isactive BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS updatedat TIMESTAMP DEFAULT NOW();
-- Ensure blogposts table has all necessary columns
ALTER TABLE blogposts
ADD COLUMN IF NOT EXISTS metatitle VARCHAR(255),
ADD COLUMN IF NOT EXISTS metadescription TEXT,
ADD COLUMN IF NOT EXISTS updatedat TIMESTAMP DEFAULT NOW();
-- Ensure pages table has all necessary columns
ALTER TABLE pages
ADD COLUMN IF NOT EXISTS metatitle VARCHAR(255),
ADD COLUMN IF NOT EXISTS metadescription TEXT,
ADD COLUMN IF NOT EXISTS updatedat TIMESTAMP DEFAULT NOW();
-- Ensure adminusers table has all necessary columns
ALTER TABLE adminusers
ADD COLUMN IF NOT EXISTS name VARCHAR(255),
ADD COLUMN IF NOT EXISTS username VARCHAR(255) UNIQUE,
ADD COLUMN IF NOT EXISTS passwordneverexpires BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS updatedat TIMESTAMP DEFAULT NOW();
-- Add username for existing users if not exists
UPDATE adminusers
SET username = LOWER(REGEXP_REPLACE(email, '@.*$', ''))
WHERE username IS NULL;
-- Add name for existing users if not exists
UPDATE adminusers
SET name = INITCAP(REGEXP_REPLACE(email, '@.*$', ''))
WHERE name IS NULL;
COMMENT ON TABLE site_settings IS 'Stores site-wide configuration settings in JSON format';
COMMENT ON TABLE products IS 'Product catalog with variants and inventory';
COMMENT ON TABLE portfolioprojects IS 'Portfolio showcase projects';
COMMENT ON TABLE blogposts IS 'Blog posts with SEO metadata';
COMMENT ON TABLE pages IS 'Custom pages with SEO metadata';

View File

@@ -8,15 +8,15 @@
"dev": "nodemon server.js"
},
"dependencies": {
"bcrypt": "^5.1.1",
"connect-pg-simple": "^9.0.1",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-session": "^1.17.3",
"connect-pg-simple": "^9.0.1",
"pg": "^8.11.3",
"bcrypt": "^5.1.1",
"ejs": "^3.1.9",
"dotenv": "^16.3.1",
"express-validator": "^7.0.1",
"multer": "^1.4.5-lts.1",
"pg": "^8.11.3",
"uuid": "^9.0.1"
}
}

View File

@@ -95,4 +95,515 @@ router.get("/pages", requireAuth, async (req, res) => {
}
});
// Get single product
router.get("/products/:id", requireAuth, async (req, res) => {
try {
const result = await query("SELECT * FROM products WHERE id = $1", [
req.params.id,
]);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Product not found" });
}
res.json({
success: true,
product: result.rows[0],
});
} catch (error) {
console.error("Product error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Create product
router.post("/products", requireAuth, async (req, res) => {
try {
const {
name,
description,
price,
stockquantity,
category,
isactive,
isbestseller,
} = req.body;
const result = await query(
`INSERT INTO products (name, description, price, stockquantity, category, isactive, isbestseller, createdat)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
RETURNING *`,
[
name,
description,
price,
stockquantity || 0,
category,
isactive !== false,
isbestseller || false,
]
);
res.json({
success: true,
product: result.rows[0],
message: "Product created successfully",
});
} catch (error) {
console.error("Create product error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Update product
router.put("/products/:id", requireAuth, async (req, res) => {
try {
const {
name,
description,
price,
stockquantity,
category,
isactive,
isbestseller,
} = req.body;
const result = await query(
`UPDATE products
SET name = $1, description = $2, price = $3, stockquantity = $4,
category = $5, isactive = $6, isbestseller = $7, updatedat = NOW()
WHERE id = $8
RETURNING *`,
[
name,
description,
price,
stockquantity || 0,
category,
isactive !== false,
isbestseller || false,
req.params.id,
]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Product not found" });
}
res.json({
success: true,
product: result.rows[0],
message: "Product updated successfully",
});
} catch (error) {
console.error("Update product error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Delete product
router.delete("/products/:id", requireAuth, async (req, res) => {
try {
const result = await query(
"DELETE FROM products WHERE id = $1 RETURNING id",
[req.params.id]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Product not found" });
}
res.json({
success: true,
message: "Product deleted successfully",
});
} catch (error) {
console.error("Delete product error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Portfolio Project CRUD
router.get("/portfolio/projects/:id", requireAuth, async (req, res) => {
try {
const result = await query(
"SELECT * FROM portfolioprojects WHERE id = $1",
[req.params.id]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Project not found" });
}
res.json({ success: true, project: result.rows[0] });
} catch (error) {
console.error("Portfolio project error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.post("/portfolio/projects", requireAuth, async (req, res) => {
try {
const { title, description, category, isactive } = req.body;
const result = await query(
`INSERT INTO portfolioprojects (title, description, category, isactive, createdat)
VALUES ($1, $2, $3, $4, NOW()) RETURNING *`,
[title, description, category, isactive !== false]
);
res.json({
success: true,
project: result.rows[0],
message: "Project created successfully",
});
} catch (error) {
console.error("Create portfolio project error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.put("/portfolio/projects/:id", requireAuth, async (req, res) => {
try {
const { title, description, category, isactive } = req.body;
const result = await query(
`UPDATE portfolioprojects
SET title = $1, description = $2, category = $3, isactive = $4, updatedat = NOW()
WHERE id = $5 RETURNING *`,
[title, description, category, isactive !== false, req.params.id]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Project not found" });
}
res.json({
success: true,
project: result.rows[0],
message: "Project updated successfully",
});
} catch (error) {
console.error("Update portfolio project error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.delete("/portfolio/projects/:id", requireAuth, async (req, res) => {
try {
const result = await query(
"DELETE FROM portfolioprojects WHERE id = $1 RETURNING id",
[req.params.id]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Project not found" });
}
res.json({ success: true, message: "Project deleted successfully" });
} catch (error) {
console.error("Delete portfolio project error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Blog Post CRUD
router.get("/blog/:id", requireAuth, async (req, res) => {
try {
const result = await query("SELECT * FROM blogposts WHERE id = $1", [
req.params.id,
]);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Blog post not found" });
}
res.json({ success: true, post: result.rows[0] });
} catch (error) {
console.error("Blog post error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.post("/blog", requireAuth, async (req, res) => {
try {
const {
title,
slug,
excerpt,
content,
metatitle,
metadescription,
ispublished,
} = req.body;
const result = await query(
`INSERT INTO blogposts (title, slug, excerpt, content, metatitle, metadescription, ispublished, createdat)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) RETURNING *`,
[
title,
slug,
excerpt,
content,
metatitle,
metadescription,
ispublished || false,
]
);
res.json({
success: true,
post: result.rows[0],
message: "Blog post created successfully",
});
} catch (error) {
console.error("Create blog post error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.put("/blog/:id", requireAuth, async (req, res) => {
try {
const {
title,
slug,
excerpt,
content,
metatitle,
metadescription,
ispublished,
} = req.body;
const result = await query(
`UPDATE blogposts
SET title = $1, slug = $2, excerpt = $3, content = $4, metatitle = $5,
metadescription = $6, ispublished = $7, updatedat = NOW()
WHERE id = $8 RETURNING *`,
[
title,
slug,
excerpt,
content,
metatitle,
metadescription,
ispublished || false,
req.params.id,
]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Blog post not found" });
}
res.json({
success: true,
post: result.rows[0],
message: "Blog post updated successfully",
});
} catch (error) {
console.error("Update blog post error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.delete("/blog/:id", requireAuth, async (req, res) => {
try {
const result = await query(
"DELETE FROM blogposts WHERE id = $1 RETURNING id",
[req.params.id]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Blog post not found" });
}
res.json({ success: true, message: "Blog post deleted successfully" });
} catch (error) {
console.error("Delete blog post error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Custom Pages CRUD
router.get("/pages/:id", requireAuth, async (req, res) => {
try {
const result = await query("SELECT * FROM pages WHERE id = $1", [
req.params.id,
]);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Page not found" });
}
res.json({ success: true, page: result.rows[0] });
} catch (error) {
console.error("Page error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.post("/pages", requireAuth, async (req, res) => {
try {
const { title, slug, content, metatitle, metadescription, ispublished } =
req.body;
const result = await query(
`INSERT INTO pages (title, slug, content, metatitle, metadescription, ispublished, createdat)
VALUES ($1, $2, $3, $4, $5, $6, NOW()) RETURNING *`,
[title, slug, content, metatitle, metadescription, ispublished !== false]
);
res.json({
success: true,
page: result.rows[0],
message: "Page created successfully",
});
} catch (error) {
console.error("Create page error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.put("/pages/:id", requireAuth, async (req, res) => {
try {
const { title, slug, content, metatitle, metadescription, ispublished } =
req.body;
const result = await query(
`UPDATE pages
SET title = $1, slug = $2, content = $3, metatitle = $4,
metadescription = $5, ispublished = $6, updatedat = NOW()
WHERE id = $7 RETURNING *`,
[
title,
slug,
content,
metatitle,
metadescription,
ispublished !== false,
req.params.id,
]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Page not found" });
}
res.json({
success: true,
page: result.rows[0],
message: "Page updated successfully",
});
} catch (error) {
console.error("Update page error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
router.delete("/pages/:id", requireAuth, async (req, res) => {
try {
const result = await query("DELETE FROM pages WHERE id = $1 RETURNING id", [
req.params.id,
]);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Page not found" });
}
res.json({ success: true, message: "Page deleted successfully" });
} catch (error) {
console.error("Delete page error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Homepage Settings
router.get("/homepage/settings", requireAuth, async (req, res) => {
try {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'homepage'"
);
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
res.json({ success: true, settings });
} catch (error) {
console.error("Homepage settings error:", error);
res.json({ success: true, settings: {} });
}
});
router.post("/homepage/settings", requireAuth, async (req, res) => {
try {
const settings = req.body;
await query(
`INSERT INTO site_settings (key, settings, updatedat)
VALUES ('homepage', $1, NOW())
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
[JSON.stringify(settings)]
);
res.json({
success: true,
message: "Homepage settings saved successfully",
});
} catch (error) {
console.error("Save homepage settings error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// General Settings
router.get("/settings", requireAuth, async (req, res) => {
try {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'general'"
);
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
res.json({ success: true, settings });
} catch (error) {
console.error("Settings error:", error);
res.json({ success: true, settings: {} });
}
});
router.post("/settings", requireAuth, async (req, res) => {
try {
const settings = req.body;
await query(
`INSERT INTO site_settings (key, settings, updatedat)
VALUES ('general', $1, NOW())
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
[JSON.stringify(settings)]
);
res.json({ success: true, message: "Settings saved successfully" });
} catch (error) {
console.error("Save settings error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Menu Management
router.get("/menu", requireAuth, async (req, res) => {
try {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'menu'"
);
const items =
result.rows.length > 0 ? result.rows[0].settings.items || [] : [];
res.json({ success: true, items });
} catch (error) {
console.error("Menu error:", error);
res.json({ success: true, items: [] });
}
});
router.post("/menu", requireAuth, async (req, res) => {
try {
const { items } = req.body;
await query(
`INSERT INTO site_settings (key, settings, updatedat)
VALUES ('menu', $1, NOW())
ON CONFLICT (key) DO UPDATE SET settings = $1, updatedat = NOW()`,
[JSON.stringify({ items })]
);
res.json({ success: true, message: "Menu saved successfully" });
} catch (error) {
console.error("Save menu error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
module.exports = router;

View File

@@ -92,7 +92,7 @@ router.get("/homepage/sections", async (req, res) => {
router.get("/portfolio/projects", async (req, res) => {
try {
const result = await query(
"SELECT id, title, description, imageurl, categoryid, createdat FROM portfolioprojects ORDER BY createdat DESC"
"SELECT id, title, description, featuredimage, images, category, categoryid, isactive, createdat FROM portfolioprojects WHERE isactive = true ORDER BY displayorder ASC, createdat DESC"
);
res.json({
success: true,
@@ -120,4 +120,101 @@ router.get("/blog/posts", async (req, res) => {
}
});
// Get single blog post by slug
router.get("/blog/posts/:slug", async (req, res) => {
try {
const result = await query(
"SELECT * FROM blogposts WHERE slug = $1 AND ispublished = true",
[req.params.slug]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Blog post not found" });
}
res.json({
success: true,
post: result.rows[0],
});
} catch (error) {
console.error("Blog post detail error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Get custom pages
router.get("/pages", async (req, res) => {
try {
const result = await query(
"SELECT id, title, slug, content, metatitle, metadescription, isactive, createdat FROM pages WHERE isactive = true ORDER BY createdat DESC"
);
res.json({
success: true,
pages: result.rows,
});
} catch (error) {
console.error("Pages error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Get single page by slug
router.get("/pages/:slug", async (req, res) => {
try {
const result = await query(
"SELECT * FROM pages WHERE slug = $1 AND isactive = true",
[req.params.slug]
);
if (result.rows.length === 0) {
return res
.status(404)
.json({ success: false, message: "Page not found" });
}
res.json({
success: true,
page: result.rows[0],
});
} catch (error) {
console.error("Page detail error:", error);
res.status(500).json({ success: false, message: "Server error" });
}
});
// Get menu items for frontend navigation
router.get("/menu", async (req, res) => {
try {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'menu'"
);
const items =
result.rows.length > 0 ? result.rows[0].settings.items || [] : [];
// Filter only visible items
const visibleItems = items.filter((item) => item.visible !== false);
res.json({
success: true,
items: visibleItems,
});
} catch (error) {
console.error("Menu error:", error);
res.json({ success: true, items: [] });
}
});
// Get homepage settings for frontend
router.get("/homepage/settings", async (req, res) => {
try {
const result = await query(
"SELECT settings FROM site_settings WHERE key = 'homepage'"
);
const settings = result.rows.length > 0 ? result.rows[0].settings : {};
res.json({
success: true,
settings,
});
} catch (error) {
console.error("Homepage settings error:", error);
res.json({ success: true, settings: {} });
}
});
module.exports = router;

207
backend/routes/upload.js Normal file
View File

@@ -0,0 +1,207 @@
const express = require("express");
const router = express.Router();
const multer = require("multer");
const path = require("path");
const fs = require("fs").promises;
const { requireAuth } = require("../middleware/auth");
const { pool } = require("../config/database");
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: async function (req, file, cb) {
const uploadDir = path.join(__dirname, "..", "..", "website", "uploads");
try {
await fs.mkdir(uploadDir, { recursive: true });
cb(null, uploadDir);
} catch (error) {
cb(error);
}
},
filename: function (req, file, cb) {
// Generate unique filename
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname);
const name = path
.basename(file.originalname, ext)
.replace(/[^a-z0-9]/gi, "-")
.toLowerCase();
cb(null, name + "-" + uniqueSuffix + ext);
},
});
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
},
fileFilter: function (req, file, cb) {
// Accept images only
if (!file.mimetype.startsWith("image/")) {
return cb(new Error("Only image files are allowed!"), false);
}
cb(null, true);
},
});
// Upload multiple files
router.post(
"/upload",
requireAuth,
upload.array("files", 10),
async (req, res) => {
try {
const uploadedBy = req.session.user?.id || null;
const files = [];
// Insert each file into database
for (const file of req.files) {
const result = await pool.query(
`INSERT INTO uploads
(filename, original_name, file_path, file_size, mime_type, uploaded_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
RETURNING id, filename, original_name, file_path, file_size, mime_type, created_at`,
[
file.filename,
file.originalname,
`/uploads/${file.filename}`,
file.size,
file.mimetype,
uploadedBy,
]
);
files.push({
id: result.rows[0].id,
filename: result.rows[0].filename,
originalName: result.rows[0].original_name,
size: result.rows[0].file_size,
mimetype: result.rows[0].mime_type,
path: result.rows[0].file_path,
uploadDate: result.rows[0].created_at,
});
}
res.json({
success: true,
message: `${files.length} file(s) uploaded successfully`,
files: files,
});
} catch (error) {
console.error("Upload error:", error);
// If database insert fails, clean up uploaded files
if (req.files) {
for (const file of req.files) {
try {
await fs.unlink(file.path);
} catch (unlinkError) {
console.error("Error cleaning up file:", unlinkError);
}
}
}
res.status(500).json({
success: false,
error: error.message,
});
}
}
);
// Get all uploaded files
router.get("/uploads", requireAuth, async (req, res) => {
try {
// Query files from database
const result = await pool.query(
`SELECT
id,
filename,
original_name,
file_path,
file_size,
mime_type,
uploaded_by,
created_at,
updated_at,
used_in_type,
used_in_id
FROM uploads
ORDER BY created_at DESC`
);
const files = result.rows.map((row) => ({
id: row.id,
filename: row.filename,
originalName: row.original_name,
size: row.file_size,
mimetype: row.mime_type,
path: row.file_path,
uploadDate: row.created_at,
uploadedBy: row.uploaded_by,
usedInType: row.used_in_type,
usedInId: row.used_in_id,
}));
res.json({
success: true,
files: files,
});
} catch (error) {
console.error("Error listing files:", error);
res.status(500).json({
success: false,
error: error.message,
});
}
});
// Delete a file
router.delete("/uploads/:filename", requireAuth, async (req, res) => {
try {
const filename = req.params.filename;
const uploadDir = path.join(__dirname, "..", "..", "website", "uploads");
const filePath = path.join(uploadDir, filename);
// Security check: ensure file is within uploads directory
if (!filePath.startsWith(uploadDir)) {
return res.status(403).json({
success: false,
error: "Invalid file path",
});
}
// Start transaction: delete from database first
const result = await pool.query(
"DELETE FROM uploads WHERE filename = $1 RETURNING id",
[filename]
);
if (result.rowCount === 0) {
return res.status(404).json({
success: false,
error: "File not found in database",
});
}
// Then delete physical file
try {
await fs.unlink(filePath);
} catch (fileError) {
console.warn("File already deleted from disk:", filename);
// Continue anyway since database record is deleted
}
res.json({
success: true,
message: "File deleted successfully",
});
} catch (error) {
console.error("Error deleting file:", error);
res.status(500).json({
success: false,
error: error.message,
});
}
});
module.exports = router;

View File

@@ -13,12 +13,9 @@ router.get("/", async (req, res) => {
try {
const result = await query(`
SELECT
u.id, u.username, u.email, u.role_id, u.isactive,
u.last_login, u.createdat, u.password_never_expires,
u.password_expires_at, u.last_password_change,
r.name as role_name, r.description as role_description
u.id, u.username, u.email, u.name, u.role, u.isactive,
u.last_login, u.createdat, u.passwordneverexpires
FROM adminusers u
LEFT JOIN roles r ON u.role_id = r.id
ORDER BY u.createdat DESC
`);

View File

@@ -8,10 +8,17 @@ require("dotenv").config();
const app = express();
const PORT = process.env.PORT || 5000;
// Serve static files from /var/www/skyartshop
app.use(express.static("/var/www/skyartshop/public"));
app.use("/assets", express.static("/var/www/skyartshop/assets"));
app.use("/uploads", express.static("/var/www/skyartshop/uploads"));
// Development mode - Serve static files from development directory
const isDevelopment = process.env.NODE_ENV !== "production";
const baseDir = isDevelopment
? path.join(__dirname, "..", "website")
: "/var/www/skyartshop";
console.log(`📁 Serving from: ${baseDir}`);
app.use(express.static(path.join(baseDir, "public")));
app.use("/assets", express.static(path.join(baseDir, "assets")));
app.use("/uploads", express.static(path.join(baseDir, "uploads")));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
@@ -27,13 +34,12 @@ app.use(
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production" ? true : false,
secure: false, // Always false for localhost development
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
sameSite: "lax",
domain: process.env.NODE_ENV === "production" ? ".ddns.net" : "localhost",
},
proxy: true,
proxy: false, // No proxy in development
name: "skyartshop.sid",
})
);
@@ -49,6 +55,7 @@ const authRoutes = require("./routes/auth");
const adminRoutes = require("./routes/admin");
const publicRoutes = require("./routes/public");
const usersRoutes = require("./routes/users");
const uploadRoutes = require("./routes/upload");
// Admin redirect - handle /admin to redirect to login (must be before static files)
app.get("/admin", (req, res) => {
@@ -63,14 +70,15 @@ app.get("/admin/", (req, res) => {
app.use("/api/admin", authRoutes);
app.use("/api/admin", adminRoutes);
app.use("/api/admin/users", usersRoutes);
app.use("/api/admin", uploadRoutes);
app.use("/api", publicRoutes);
// Admin static files (must be after redirect routes)
app.use("/admin", express.static("/var/www/skyartshop/admin"));
app.use("/admin", express.static(path.join(baseDir, "admin")));
// Root redirect to admin login
// Root redirect to home page
app.get("/", (req, res) => {
res.redirect("/admin/login.html");
res.sendFile(path.join(baseDir, "public", "index.html"));
});
app.get("/health", (req, res) => {

67
backend/test-navigation.sh Executable file
View File

@@ -0,0 +1,67 @@
#!/bin/bash
# Backend Navigation Test Script
echo "=========================================="
echo " Testing Backend Admin Panel Navigation"
echo "=========================================="
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Test if backend is running
echo -e "\n1. Checking if backend server is running..."
if curl -s http://localhost:5000/health > /dev/null; then
echo -e "${GREEN}✓ Backend server is running${NC}"
else
echo -e "${RED}✗ Backend server is not responding${NC}"
echo "Please start the backend server first:"
echo " cd /media/pts/Website/SkyArtShop/backend && npm start"
exit 1
fi
# Check if admin files are accessible
echo -e "\n2. Checking admin panel files..."
pages=("dashboard.html" "products.html" "portfolio.html" "blog.html" "pages.html" "menu.html" "settings.html" "users.html" "homepage.html")
for page in "${pages[@]}"; do
if curl -s -o /dev/null -w "%{http_code}" http://localhost:5000/admin/$page | grep -q "200\|304"; then
echo -e "${GREEN}✓ /admin/$page accessible${NC}"
else
echo -e "${RED}✗ /admin/$page not found${NC}"
fi
done
# Check API endpoints
echo -e "\n3. Checking API endpoints..."
endpoints=(
"/api/admin/session"
"/api/products"
"/api/portfolio/projects"
"/api/blog/posts"
"/api/pages"
"/api/menu"
"/api/homepage/settings"
)
for endpoint in "${endpoints[@]}"; do
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:5000$endpoint)
if [ "$status" == "200" ] || [ "$status" == "401" ]; then
echo -e "${GREEN}$endpoint responding (HTTP $status)${NC}"
else
echo -e "${RED}$endpoint not responding properly (HTTP $status)${NC}"
fi
done
echo -e "\n=========================================="
echo " Test Complete!"
echo "=========================================="
echo ""
echo "Next Steps:"
echo "1. Login to the admin panel at http://localhost:5000/admin/login.html"
echo "2. After login, navigate through different sections"
echo "3. Verify you stay logged in when clicking navigation links"
echo "4. Create/Edit content in each section"
echo "5. Verify changes appear on the frontend"
echo ""

124
backend/test-upload-db.js Executable file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env node
/**
* Test Script: Upload Database Integration
*
* This script tests that file uploads are properly recorded in PostgreSQL
*/
const { pool } = require("./config/database");
async function testUploadDatabase() {
console.log("🔍 Testing Upload Database Integration...\n");
try {
// Test 1: Check if uploads table exists
console.log("1⃣ Checking uploads table...");
const tableCheck = await pool.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'uploads'
);
`);
if (tableCheck.rows[0].exists) {
console.log(" ✅ uploads table exists\n");
} else {
console.log(" ❌ uploads table not found\n");
return;
}
// Test 2: Check table structure
console.log("2⃣ Checking table structure...");
const columns = await pool.query(`
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'uploads'
ORDER BY ordinal_position;
`);
console.log(" Columns:");
columns.rows.forEach((col) => {
console.log(
` - ${col.column_name} (${col.data_type}) ${
col.is_nullable === "YES" ? "NULL" : "NOT NULL"
}`
);
});
console.log();
// Test 3: Check indexes
console.log("3⃣ Checking indexes...");
const indexes = await pool.query(`
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'uploads';
`);
console.log(` Found ${indexes.rows.length} index(es):`);
indexes.rows.forEach((idx) => {
console.log(` - ${idx.indexname}`);
});
console.log();
// Test 4: Query existing uploads
console.log("4⃣ Querying existing uploads...");
const uploads = await pool.query(`
SELECT id, filename, original_name, file_size, mime_type, created_at
FROM uploads
ORDER BY created_at DESC
LIMIT 10;
`);
console.log(` Found ${uploads.rows.length} upload(s) in database:`);
if (uploads.rows.length > 0) {
uploads.rows.forEach((upload) => {
console.log(
` - [${upload.id}] ${upload.original_name} (${upload.filename})`
);
console.log(
` Size: ${(upload.file_size / 1024).toFixed(2)}KB | Type: ${
upload.mime_type
}`
);
console.log(` Uploaded: ${upload.created_at}`);
});
} else {
console.log(" No uploads found yet. Upload a file to test!");
}
console.log();
// Test 5: Check foreign key constraint
console.log("5⃣ Checking foreign key constraints...");
const fkeys = await pool.query(`
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE contype = 'f' AND conrelid = 'uploads'::regclass;
`);
if (fkeys.rows.length > 0) {
console.log(` Found ${fkeys.rows.length} foreign key(s):`);
fkeys.rows.forEach((fk) => {
console.log(` - ${fk.conname}: ${fk.conrelid} -> ${fk.confrelid}`);
});
} else {
console.log(" No foreign keys found");
}
console.log();
console.log("✅ Database integration test complete!\n");
console.log("📋 Summary:");
console.log(" - Database: skyartshop");
console.log(" - Table: uploads");
console.log(" - Records: " + uploads.rows.length);
console.log(" - Status: Ready for production ✨\n");
} catch (error) {
console.error("❌ Test failed:", error.message);
console.error(error);
} finally {
await pool.end();
}
}
// Run test
testUploadDatabase().catch(console.error);

View File

@@ -0,0 +1,34 @@
-- Create uploads table to track all uploaded media files
CREATE TABLE IF NOT EXISTS uploads (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) NOT NULL UNIQUE,
original_name VARCHAR(255) NOT NULL,
file_path VARCHAR(500) NOT NULL,
file_size INTEGER NOT NULL,
mime_type VARCHAR(100) NOT NULL,
uploaded_by INTEGER REFERENCES adminusers(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create index for faster queries
CREATE INDEX IF NOT EXISTS idx_uploads_filename ON uploads(filename);
CREATE INDEX IF NOT EXISTS idx_uploads_created_at ON uploads(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_uploads_uploaded_by ON uploads(uploaded_by);
-- Add column to track which entity uses this upload
ALTER TABLE uploads ADD COLUMN IF NOT EXISTS used_in_type VARCHAR(50);
ALTER TABLE uploads ADD COLUMN IF NOT EXISTS used_in_id INTEGER;
-- Create composite index for usage tracking
CREATE INDEX IF NOT EXISTS idx_uploads_usage ON uploads(used_in_type, used_in_id);
COMMENT ON TABLE uploads IS 'Tracks all uploaded media files (images, documents, etc.)';
COMMENT ON COLUMN uploads.filename IS 'Unique filename stored on disk';
COMMENT ON COLUMN uploads.original_name IS 'Original filename from user upload';
COMMENT ON COLUMN uploads.file_path IS 'Relative path to file (e.g., /uploads/image.jpg)';
COMMENT ON COLUMN uploads.file_size IS 'File size in bytes';
COMMENT ON COLUMN uploads.mime_type IS 'MIME type of the file';
COMMENT ON COLUMN uploads.uploaded_by IS 'Admin user who uploaded the file';
COMMENT ON COLUMN uploads.used_in_type IS 'Type of entity using this file (product, blog, portfolio, etc.)';
COMMENT ON COLUMN uploads.used_in_id IS 'ID of the entity using this file';