updateweb

This commit is contained in:
Local Server
2026-01-01 22:24:30 -06:00
parent 017c6376fc
commit 1919f6f8bb
185 changed files with 19860 additions and 17603 deletions

View File

@@ -0,0 +1,48 @@
/**
* Shared TypeScript type definitions for backend
*
* Purpose: Centralized type definitions used across controllers, services, and models
* Ensures type safety and consistency throughout the backend codebase
*/
// User types
export interface User {
id: number;
username: string;
email: string;
role: 'admin' | 'customer';
createdAt: Date;
updatedAt: Date;
}
export interface AuthPayload {
userId: number;
username: string;
role: string;
}
// Product types
export interface Product {
id: number;
name: string;
description: string;
price: number;
category: string;
stock: number;
images: string[];
createdAt: Date;
updatedAt: Date;
}
// API Response types
export interface ApiResponse<T = any> {
success: boolean;
data?: T;
error?: string;
message?: string;
}
// Request types (extends Express Request)
export interface AuthRequest extends Request {
user?: AuthPayload;
}

31
backend/src/config/app.ts Normal file
View File

@@ -0,0 +1,31 @@
/**
* Application Configuration
*
* Purpose: General application settings (port, CORS, JWT, rate limiting)
* Loaded from environment variables with sensible defaults
*/
import dotenv from 'dotenv';
dotenv.config();
export const appConfig = {
// Server
port: parseInt(process.env.PORT || '3000'),
env: process.env.NODE_ENV || 'development',
// JWT
jwtSecret: process.env.JWT_SECRET || 'your-secret-key-change-in-production',
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',
// CORS
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:5173',
// Upload limits
maxFileSize: parseInt(process.env.MAX_FILE_SIZE || '5242880'), // 5MB default
// Rate limiting
rateLimitWindow: 15 * 60 * 1000, // 15 minutes
rateLimitMax: 100, // requests per window
};
export default appConfig;

View File

@@ -0,0 +1,31 @@
/**
* Database Configuration
*
* Purpose: Centralized database connection settings
* Manages connection pooling, SSL, and environment-specific configs
*/
import dotenv from 'dotenv';
dotenv.config();
export const databaseConfig = {
// Database connection
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'skyartshop',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || '',
// Connection pool settings
pool: {
min: 2,
max: 10,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
},
// SSL for production
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
};
export default databaseConfig;

View File

@@ -0,0 +1,3 @@
# Controllers go here
# Each controller handles HTTP requests for a specific resource
# Example: productController.ts, authController.ts, userController.ts

View File

@@ -0,0 +1,24 @@
/**
* JWT Helper Functions
*
* Purpose: Generate and verify JWT tokens for authentication
* Centralized token logic for consistency
*/
import jwt from 'jsonwebtoken';
import { appConfig } from '../config/app';
import { AuthPayload } from '../@types';
export function generateToken(payload: AuthPayload): string {
return jwt.sign(payload, appConfig.jwtSecret, {
expiresIn: appConfig.jwtExpiresIn,
});
}
export function verifyToken(token: string): AuthPayload | null {
try {
return jwt.verify(token, appConfig.jwtSecret) as AuthPayload;
} catch (error) {
return null;
}
}

View File

@@ -0,0 +1,34 @@
/**
* Response Helper Functions
*
* Purpose: Consistent API response formatting across all endpoints
* Ensures all responses follow the same structure
*/
import { Response } from 'express';
import { ApiResponse } from '../@types';
export function sendSuccess<T>(res: Response, data: T, message?: string, statusCode = 200): void {
const response: ApiResponse<T> = {
success: true,
data,
...(message && { message }),
};
res.status(statusCode).json(response);
}
export function sendError(res: Response, error: string, statusCode = 400): void {
const response: ApiResponse = {
success: false,
error,
};
res.status(statusCode).json(response);
}
export function sendCreated<T>(res: Response, data: T, message = 'Resource created successfully'): void {
sendSuccess(res, data, message, 201);
}
export function sendNoContent(res: Response): void {
res.status(204).send();
}

View File

@@ -0,0 +1,49 @@
/**
* Authentication Middleware
*
* Purpose: Verify JWT tokens and attach user info to requests
* Applied to protected routes that require user authentication
*/
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { appConfig } from '../config/app';
import { AuthPayload } from '../@types';
export interface AuthRequest extends Request {
user?: AuthPayload;
}
export function authenticate(req: AuthRequest, res: Response, next: NextFunction): void {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({ success: false, error: 'No token provided' });
return;
}
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
const decoded = jwt.verify(token, appConfig.jwtSecret) as AuthPayload;
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ success: false, error: 'Invalid or expired token' });
}
}
export function isAdmin(req: AuthRequest, res: Response, next: NextFunction): void {
if (!req.user) {
res.status(401).json({ success: false, error: 'Authentication required' });
return;
}
if (req.user.role !== 'admin') {
res.status(403).json({ success: false, error: 'Admin access required' });
return;
}
next();
}

View File

@@ -0,0 +1,45 @@
/**
* Global Error Handler Middleware
*
* Purpose: Catch all errors, log them, and return consistent error responses
* Applied as the last middleware in the Express chain
*/
import { Request, Response, NextFunction } from 'express';
import { appConfig } from '../config/app';
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export function errorHandler(
err: Error | AppError,
req: Request,
res: Response,
next: NextFunction
): void {
console.error('Error:', err);
if (err instanceof AppError) {
res.status(err.statusCode).json({
success: false,
error: err.message,
});
return;
}
// Unexpected errors
res.status(500).json({
success: false,
error: appConfig.env === 'production'
? 'Internal server error'
: err.message,
});
}

View File

@@ -0,0 +1,22 @@
/**
* Request Logger Middleware
*
* Purpose: Log all incoming requests with method, path, IP, and response time
* Useful for debugging and monitoring API usage
*/
import { Request, Response, NextFunction } from 'express';
export function requestLogger(req: Request, res: Response, next: NextFunction): void {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const { method, originalUrl, ip } = req;
const { statusCode } = res;
console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} - ${statusCode} - ${duration}ms - ${ip}`);
});
next();
}

View File

@@ -0,0 +1,3 @@
# Models/Repositories go here
# Database access layer and query methods
# Example: Product.ts, User.ts, Order.ts

View File

@@ -0,0 +1,3 @@
# Route definitions go here
# Maps URLs to controllers and applies middleware
# Example: products.ts, auth.ts, users.ts

62
backend/src/server.ts Normal file
View File

@@ -0,0 +1,62 @@
/**
* Backend Entry Point
*
* Purpose: Initialize Express app, apply middleware, mount routes, start server
* This is where the entire backend application comes together
*/
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import { appConfig } from './config/app';
import { requestLogger } from './middlewares/requestLogger';
import { errorHandler } from './middlewares/errorHandler';
// Initialize Express app
const app: Application = express();
// Security middleware
app.use(helmet());
app.use(cors({ origin: appConfig.corsOrigin }));
// Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Compression
app.use(compression());
// Request logging
app.use(requestLogger);
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
success: true,
message: 'Server is running',
environment: appConfig.env,
timestamp: new Date().toISOString(),
});
});
// API Routes
// TODO: Import and mount your route files here
// Example:
// import authRoutes from './routes/auth';
// import productRoutes from './routes/products';
// app.use('/api/auth', authRoutes);
// app.use('/api/products', productRoutes);
// Error handling (must be last)
app.use(errorHandler);
// Start server
const PORT = appConfig.port;
app.listen(PORT, () => {
console.log(`🚀 Backend server running on http://localhost:${PORT}`);
console.log(`📝 Environment: ${appConfig.env}`);
console.log(`🔗 CORS enabled for: ${appConfig.corsOrigin}`);
});
export default app;

View File

@@ -0,0 +1,3 @@
# Services go here
# Contains business logic, data processing, and orchestration
# Example: productService.ts, authService.ts, emailService.ts

View File

@@ -0,0 +1,54 @@
/**
* Product Validation Schemas
*
* Purpose: Validate product-related request data before it reaches controllers
* Prevents invalid data from entering the system
*/
import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';
export const createProductSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().min(10).max(5000),
price: z.number().positive(),
category: z.string().min(1),
stock: z.number().int().nonnegative(),
images: z.array(z.string().url()).optional(),
});
export const updateProductSchema = createProductSchema.partial();
export function validateCreateProduct(req: Request, res: Response, next: NextFunction): void {
try {
createProductSchema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
success: false,
error: 'Validation failed',
details: error.errors,
});
return;
}
next(error);
}
}
export function validateUpdateProduct(req: Request, res: Response, next: NextFunction): void {
try {
updateProductSchema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({
success: false,
error: 'Validation failed',
details: error.errors,
});
return;
}
next(error);
}
}