This commit is contained in:
Local Server
2025-12-19 20:44:46 -06:00
parent 701f799cde
commit e4b3de4a46
113 changed files with 16673 additions and 2174 deletions

View File

@@ -0,0 +1,84 @@
const path = require("path");
const ENVIRONMENTS = {
DEVELOPMENT: "development",
PRODUCTION: "production",
};
const HTTP_STATUS = {
OK: 200,
CREATED: 201,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
TOO_MANY_REQUESTS: 429,
INTERNAL_ERROR: 500,
SERVICE_UNAVAILABLE: 503,
};
const RATE_LIMITS = {
API: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
},
AUTH: {
windowMs: 15 * 60 * 1000,
max: 5,
},
UPLOAD: {
windowMs: 60 * 60 * 1000, // 1 hour
max: 50,
},
};
const SESSION_CONFIG = {
COOKIE_MAX_AGE: 24 * 60 * 60 * 1000, // 24 hours
SESSION_NAME: "skyartshop.sid",
};
const BODY_PARSER_LIMITS = {
JSON: "10mb",
URLENCODED: "10mb",
};
const isDevelopment = () => process.env.NODE_ENV !== ENVIRONMENTS.PRODUCTION;
const getBaseDir = () =>
isDevelopment()
? path.join(__dirname, "..", "..", "website")
: "/var/www/skyartshop";
const CRITICAL_IMAGES = [
"/assets/images/hero-image.jpg",
"/assets/images/products/placeholder.jpg",
];
const STATIC_ASSET_EXTENSIONS =
/\.(jpg|jpeg|png|gif|svg|css|js|ico|webp|woff|woff2|ttf|eot)$/i;
const PG_ERROR_CODES = {
UNIQUE_VIOLATION: "23505",
FOREIGN_KEY_VIOLATION: "23503",
INVALID_TEXT: "22P02",
};
const MULTER_ERROR_CODES = {
FILE_SIZE: "LIMIT_FILE_SIZE",
FILE_COUNT: "LIMIT_FILE_COUNT",
};
module.exports = {
ENVIRONMENTS,
HTTP_STATUS,
RATE_LIMITS,
SESSION_CONFIG,
BODY_PARSER_LIMITS,
CRITICAL_IMAGES,
STATIC_ASSET_EXTENSIONS,
PG_ERROR_CODES,
MULTER_ERROR_CODES,
isDevelopment,
getBaseDir,
};

View File

@@ -1,31 +1,69 @@
const { Pool } = require('pg');
require('dotenv').config();
const { Pool } = require("pg");
const logger = require("./logger");
require("dotenv").config();
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
host: process.env.DB_HOST || "localhost",
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'skyartshop',
user: process.env.DB_USER || 'skyartapp',
database: process.env.DB_NAME || "skyartshop",
user: process.env.DB_USER || "skyartapp",
password: process.env.DB_PASSWORD,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
pool.on('connect', () => console.log('✓ PostgreSQL connected'));
pool.on('error', (err) => console.error('PostgreSQL error:', err));
pool.on("connect", () => logger.info("✓ PostgreSQL connected"));
pool.on("error", (err) => logger.error("PostgreSQL error:", err));
const query = async (text, params) => {
const start = Date.now();
try {
const res = await pool.query(text, params);
const duration = Date.now() - start;
console.log('Executed query', { text, duration, rows: res.rowCount });
logger.debug("Executed query", { duration, rows: res.rowCount });
return res;
} catch (error) {
console.error('Query error:', error);
logger.error("Query error:", { text, error: error.message });
throw error;
}
};
module.exports = { pool, query };
// Transaction helper
const transaction = async (callback) => {
const client = await pool.connect();
try {
await client.query("BEGIN");
const result = await callback(client);
await client.query("COMMIT");
return result;
} catch (error) {
await client.query("ROLLBACK");
logger.error("Transaction rolled back:", error);
throw error;
} finally {
client.release();
}
};
// Health check
const healthCheck = async () => {
try {
const result = await query(
"SELECT NOW() as time, current_database() as database"
);
return {
healthy: true,
database: result.rows[0].database,
timestamp: result.rows[0].time,
};
} catch (error) {
logger.error("Database health check failed:", error);
return {
healthy: false,
error: error.message,
};
}
};
module.exports = { pool, query, transaction, healthCheck };

69
backend/config/logger.js Normal file
View File

@@ -0,0 +1,69 @@
const winston = require("winston");
const path = require("path");
require("dotenv").config();
// Define log format
const logFormat = winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
// Console format for development
const consoleFormat = winston.format.combine(
winston.format.colorize(),
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
let msg = `${timestamp} [${level}]: ${message}`;
if (Object.keys(meta).length > 0) {
msg += ` ${JSON.stringify(meta)}`;
}
return msg;
})
);
// Create logs directory if it doesn't exist
const fs = require("fs");
const logsDir = path.join(__dirname, "..", "logs");
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Create logger instance
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || "info",
format: logFormat,
defaultMeta: { service: "skyartshop" },
transports: [
// Error logs
new winston.transports.File({
filename: path.join(logsDir, "error.log"),
level: "error",
maxsize: 10485760, // 10MB
maxFiles: 5,
}),
// Combined logs
new winston.transports.File({
filename: path.join(logsDir, "combined.log"),
maxsize: 10485760, // 10MB
maxFiles: 5,
}),
],
});
// Add console transport in non-production
if (process.env.NODE_ENV !== "production") {
logger.add(
new winston.transports.Console({
format: consoleFormat,
})
);
}
// Create a stream for Morgan HTTP logger
logger.stream = {
write: (message) => logger.info(message.trim()),
};
module.exports = logger;

View File

@@ -0,0 +1,66 @@
const rateLimit = require("express-rate-limit");
const logger = require("./logger");
const { RATE_LIMITS, HTTP_STATUS } = require("./constants");
const createRateLimiter = (config, limitType = "API") => {
return rateLimit({
windowMs: config.windowMs,
max: config.max,
skipSuccessfulRequests: config.skipSuccessfulRequests || false,
message: {
success: false,
message: config.message,
},
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
logger.warn(`${limitType} rate limit exceeded`, {
ip: req.ip,
path: req.path,
email: req.body?.email,
});
res.status(HTTP_STATUS.TOO_MANY_REQUESTS).json({
success: false,
message: config.message,
});
},
});
};
// General API rate limiter
const apiLimiter = createRateLimiter(
{
windowMs:
parseInt(process.env.RATE_LIMIT_WINDOW_MS) || RATE_LIMITS.API.windowMs,
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || RATE_LIMITS.API.max,
message: "Too many requests from this IP, please try again later.",
},
"API"
);
// Strict limiter for authentication endpoints
const authLimiter = createRateLimiter(
{
windowMs: RATE_LIMITS.AUTH.windowMs,
max: RATE_LIMITS.AUTH.max,
skipSuccessfulRequests: true,
message: "Too many login attempts, please try again after 15 minutes.",
},
"Auth"
);
// File upload limiter
const uploadLimiter = createRateLimiter(
{
windowMs: RATE_LIMITS.UPLOAD.windowMs,
max: RATE_LIMITS.UPLOAD.max,
message: "Upload limit reached, please try again later.",
},
"Upload"
);
module.exports = {
apiLimiter,
authLimiter,
uploadLimiter,
};