Initial commit: Static hotel website (CMS/backend excluded)
This commit is contained in:
820
docs/DEPLOYMENT.md
Normal file
820
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,820 @@
|
||||
# Deployment Guide - The Old Vine Hotel
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
This guide covers deploying The Old Vine Hotel website to production environments.
|
||||
|
||||
## 🏗️ Infrastructure Requirements
|
||||
|
||||
### Minimum Server Specifications
|
||||
|
||||
**Backend Server:**
|
||||
- CPU: 2 vCPUs
|
||||
- RAM: 4GB
|
||||
- Storage: 50GB SSD
|
||||
- OS: Ubuntu 20.04 LTS or CentOS 8
|
||||
|
||||
**Database Server:**
|
||||
- CPU: 2 vCPUs
|
||||
- RAM: 4GB
|
||||
- Storage: 100GB SSD
|
||||
- MongoDB 5.0+
|
||||
|
||||
**Recommended for High Traffic:**
|
||||
- Load Balancer (nginx)
|
||||
- Redis for caching
|
||||
- CDN for static assets
|
||||
- Multiple application instances
|
||||
|
||||
## 🐳 Docker Deployment (Recommended)
|
||||
|
||||
### 1. Build Production Images
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <your-repo-url>
|
||||
cd old-vine-hotel-website
|
||||
|
||||
# Build frontend
|
||||
cd client
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Build backend
|
||||
cd ../server
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. Docker Configuration
|
||||
|
||||
**Frontend Dockerfile:**
|
||||
```dockerfile
|
||||
# client/Dockerfile
|
||||
FROM node:16-alpine as builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=builder /app/build /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
```
|
||||
|
||||
**Backend Dockerfile:**
|
||||
```dockerfile
|
||||
# server/Dockerfile
|
||||
FROM node:16-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Create logs directory
|
||||
RUN mkdir -p logs
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/health || exit 1
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
### 3. Production Docker Compose
|
||||
|
||||
```yaml
|
||||
# docker-compose.prod.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
restart: unless-stopped
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: ./client
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- MONGODB_URI=${MONGODB_URI}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||
env_file:
|
||||
- .env.production
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
depends_on:
|
||||
- mongodb
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
mongodb:
|
||||
image: mongo:5.0
|
||||
environment:
|
||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_ROOT_USERNAME}
|
||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_ROOT_PASSWORD}
|
||||
- MONGO_INITDB_DATABASE=oldvinehotel
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
- ./mongodb/mongod.conf:/etc/mongod.conf:ro
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
### 4. Nginx Configuration
|
||||
|
||||
```nginx
|
||||
# nginx/nginx.conf
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream backend {
|
||||
server backend:5000;
|
||||
}
|
||||
|
||||
upstream frontend {
|
||||
server frontend:80;
|
||||
}
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
|
||||
|
||||
# SSL Configuration
|
||||
server {
|
||||
listen 80;
|
||||
server_name oldvinehotel.com www.oldvinehotel.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name oldvinehotel.com www.oldvinehotel.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
|
||||
|
||||
# API routes
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
proxy_pass http://backend;
|
||||
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;
|
||||
}
|
||||
|
||||
# Auth routes with stricter limits
|
||||
location /api/auth/login {
|
||||
limit_req zone=login burst=3 nodelay;
|
||||
proxy_pass http://backend;
|
||||
}
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://frontend;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Static files caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
proxy_pass http://frontend;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Environment Configuration
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
NODE_ENV=production
|
||||
PORT=5000
|
||||
|
||||
# Database
|
||||
MONGODB_URI=mongodb://admin:secure_password@mongodb:27017/oldvinehotel?authSource=admin
|
||||
MONGO_ROOT_USERNAME=admin
|
||||
MONGO_ROOT_PASSWORD=secure_password
|
||||
|
||||
# Security
|
||||
JWT_SECRET=your_super_secure_jwt_secret_key_for_production
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# External APIs
|
||||
STRIPE_SECRET_KEY=sk_live_your_live_stripe_key
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
|
||||
GOOGLE_MAPS_API_KEY=your_production_maps_key
|
||||
|
||||
# Email
|
||||
EMAIL_HOST=smtp.sendgrid.net
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=apikey
|
||||
EMAIL_PASS=your_sendgrid_api_key
|
||||
EMAIL_FROM=noreply@oldvinehotel.com
|
||||
|
||||
# Hotel Information
|
||||
HOTEL_NAME=The Old Vine Hotel
|
||||
HOTEL_ADDRESS=123 Luxury Avenue, Downtown District, City, State 12345
|
||||
HOTEL_PHONE=+1 (555) 123-4567
|
||||
HOTEL_EMAIL=info@oldvinehotel.com
|
||||
|
||||
# Integration APIs
|
||||
OPERA_PMS_URL=https://your-opera-pms.com/api
|
||||
OPERA_PMS_USERNAME=production_user
|
||||
OPERA_PMS_PASSWORD=production_password
|
||||
|
||||
BOOKING_COM_API_URL=https://distribution-xml.booking.com
|
||||
BOOKING_COM_USERNAME=your_booking_username
|
||||
BOOKING_COM_PASSWORD=your_booking_password
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info
|
||||
```
|
||||
|
||||
### 6. Deploy with Docker Compose
|
||||
|
||||
```bash
|
||||
# Create production environment file
|
||||
cp .env.example .env.production
|
||||
# Edit .env.production with your production values
|
||||
|
||||
# Deploy
|
||||
docker-compose -f docker-compose.prod.yml up -d
|
||||
|
||||
# Check status
|
||||
docker-compose -f docker-compose.prod.yml ps
|
||||
|
||||
# View logs
|
||||
docker-compose -f docker-compose.prod.yml logs -f backend
|
||||
```
|
||||
|
||||
## ☁️ Cloud Deployment Options
|
||||
|
||||
### AWS Deployment
|
||||
|
||||
#### Using AWS ECS
|
||||
|
||||
```bash
|
||||
# Install AWS CLI and ECS CLI
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip awscliv2.zip
|
||||
sudo ./aws/install
|
||||
|
||||
# Configure AWS credentials
|
||||
aws configure
|
||||
|
||||
# Create ECS cluster
|
||||
aws ecs create-cluster --cluster-name old-vine-hotel
|
||||
|
||||
# Create task definition
|
||||
aws ecs register-task-definition --cli-input-json file://ecs-task-definition.json
|
||||
|
||||
# Create service
|
||||
aws ecs create-service \
|
||||
--cluster old-vine-hotel \
|
||||
--service-name hotel-website \
|
||||
--task-definition hotel-website:1 \
|
||||
--desired-count 2
|
||||
```
|
||||
|
||||
#### ECS Task Definition
|
||||
|
||||
```json
|
||||
{
|
||||
"family": "hotel-website",
|
||||
"networkMode": "awsvpc",
|
||||
"requiresCompatibilities": ["FARGATE"],
|
||||
"cpu": "512",
|
||||
"memory": "1024",
|
||||
"executionRoleArn": "arn:aws:iam::account:role/ecsTaskExecutionRole",
|
||||
"containerDefinitions": [
|
||||
{
|
||||
"name": "hotel-backend",
|
||||
"image": "your-account.dkr.ecr.us-east-1.amazonaws.com/hotel-backend:latest",
|
||||
"portMappings": [
|
||||
{
|
||||
"containerPort": 5000,
|
||||
"protocol": "tcp"
|
||||
}
|
||||
],
|
||||
"environment": [
|
||||
{
|
||||
"name": "NODE_ENV",
|
||||
"value": "production"
|
||||
}
|
||||
],
|
||||
"secrets": [
|
||||
{
|
||||
"name": "MONGODB_URI",
|
||||
"valueFrom": "arn:aws:secretsmanager:us-east-1:account:secret:hotel/mongodb-uri"
|
||||
}
|
||||
],
|
||||
"logConfiguration": {
|
||||
"logDriver": "awslogs",
|
||||
"options": {
|
||||
"awslogs-group": "/ecs/hotel-website",
|
||||
"awslogs-region": "us-east-1",
|
||||
"awslogs-stream-prefix": "ecs"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Google Cloud Platform
|
||||
|
||||
```bash
|
||||
# Install gcloud CLI
|
||||
curl https://sdk.cloud.google.com | bash
|
||||
exec -l $SHELL
|
||||
gcloud init
|
||||
|
||||
# Create GKE cluster
|
||||
gcloud container clusters create hotel-cluster \
|
||||
--num-nodes=3 \
|
||||
--zone=us-central1-a
|
||||
|
||||
# Build and push images
|
||||
gcloud builds submit --tag gcr.io/PROJECT_ID/hotel-backend ./server
|
||||
gcloud builds submit --tag gcr.io/PROJECT_ID/hotel-frontend ./client
|
||||
|
||||
# Deploy to GKE
|
||||
kubectl apply -f k8s/
|
||||
```
|
||||
|
||||
### DigitalOcean App Platform
|
||||
|
||||
```yaml
|
||||
# .do/app.yaml
|
||||
name: old-vine-hotel
|
||||
services:
|
||||
- name: backend
|
||||
source_dir: /server
|
||||
github:
|
||||
repo: your-username/old-vine-hotel
|
||||
branch: main
|
||||
run_command: npm start
|
||||
environment_slug: node-js
|
||||
instance_count: 2
|
||||
instance_size_slug: basic-xxs
|
||||
env:
|
||||
- key: NODE_ENV
|
||||
value: production
|
||||
- key: MONGODB_URI
|
||||
value: ${DB_CONNECTION_STRING}
|
||||
type: SECRET
|
||||
|
||||
- name: frontend
|
||||
source_dir: /client
|
||||
github:
|
||||
repo: your-username/old-vine-hotel
|
||||
branch: main
|
||||
build_command: npm run build
|
||||
environment_slug: node-js
|
||||
instance_count: 1
|
||||
instance_size_slug: basic-xxs
|
||||
routes:
|
||||
- path: /
|
||||
|
||||
databases:
|
||||
- name: hotel-db
|
||||
engine: MONGODB
|
||||
version: "5"
|
||||
```
|
||||
|
||||
## 🔧 Manual Server Deployment
|
||||
|
||||
### 1. Server Setup
|
||||
|
||||
```bash
|
||||
# Update system
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# Install Node.js
|
||||
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Install MongoDB
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y mongodb-org
|
||||
|
||||
# Install PM2 for process management
|
||||
npm install -g pm2
|
||||
|
||||
# Install nginx
|
||||
sudo apt install nginx
|
||||
```
|
||||
|
||||
### 2. Application Deployment
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <your-repo-url> /var/www/hotel
|
||||
cd /var/www/hotel
|
||||
|
||||
# Install backend dependencies
|
||||
cd server
|
||||
npm install --production
|
||||
|
||||
# Install frontend dependencies and build
|
||||
cd ../client
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
# Copy build to nginx directory
|
||||
sudo cp -r build/* /var/www/html/
|
||||
```
|
||||
|
||||
### 3. PM2 Configuration
|
||||
|
||||
```javascript
|
||||
// ecosystem.config.js
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'hotel-backend',
|
||||
script: './server/index.js',
|
||||
instances: 'max',
|
||||
exec_mode: 'cluster',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 5000
|
||||
},
|
||||
error_file: './logs/err.log',
|
||||
out_file: './logs/out.log',
|
||||
log_file: './logs/combined.log',
|
||||
time: true
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
# Start application with PM2
|
||||
pm2 start ecosystem.config.js
|
||||
|
||||
# Save PM2 configuration
|
||||
pm2 save
|
||||
|
||||
# Setup PM2 startup
|
||||
pm2 startup
|
||||
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u $USER --hp $HOME
|
||||
```
|
||||
|
||||
### 4. Nginx Configuration
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/sites-available/hotel
|
||||
server {
|
||||
listen 80;
|
||||
server_name oldvinehotel.com www.oldvinehotel.com;
|
||||
root /var/www/html;
|
||||
index index.html;
|
||||
|
||||
# API proxy
|
||||
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;
|
||||
}
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Static files
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, no-transform";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# Enable site
|
||||
sudo ln -s /etc/nginx/sites-available/hotel /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
## 🔐 SSL Certificate Setup
|
||||
|
||||
### Using Let's Encrypt (Certbot)
|
||||
|
||||
```bash
|
||||
# Install Certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Get SSL certificate
|
||||
sudo certbot --nginx -d oldvinehotel.com -d www.oldvinehotel.com
|
||||
|
||||
# Test automatic renewal
|
||||
sudo certbot renew --dry-run
|
||||
```
|
||||
|
||||
### Manual SSL Certificate
|
||||
|
||||
```nginx
|
||||
# Add to nginx configuration
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name oldvinehotel.com;
|
||||
|
||||
ssl_certificate /etc/ssl/certs/oldvinehotel.com.crt;
|
||||
ssl_certificate_key /etc/ssl/private/oldvinehotel.com.key;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# ... rest of configuration
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Monitoring Setup
|
||||
|
||||
### Application Monitoring
|
||||
|
||||
```bash
|
||||
# Install monitoring tools
|
||||
npm install -g clinic
|
||||
npm install newrelic
|
||||
|
||||
# Setup log rotation
|
||||
sudo nano /etc/logrotate.d/hotel
|
||||
```
|
||||
|
||||
```
|
||||
# /etc/logrotate.d/hotel
|
||||
/var/www/hotel/logs/*.log {
|
||||
daily
|
||||
missingok
|
||||
rotate 52
|
||||
compress
|
||||
delaycompress
|
||||
notifempty
|
||||
create 644 www-data www-data
|
||||
postrotate
|
||||
pm2 reload hotel-backend
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
### Database Monitoring
|
||||
|
||||
```bash
|
||||
# Enable MongoDB profiler
|
||||
mongo
|
||||
use oldvinehotel
|
||||
db.setProfilingLevel(1, { slowms: 100 })
|
||||
|
||||
# Setup monitoring script
|
||||
crontab -e
|
||||
```
|
||||
|
||||
```bash
|
||||
# Monitor disk space every hour
|
||||
0 * * * * /usr/local/bin/check-disk-space.sh
|
||||
|
||||
# Backup database daily at 2 AM
|
||||
0 2 * * * /usr/local/bin/backup-mongodb.sh
|
||||
```
|
||||
|
||||
## 🔄 Backup Strategy
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup-mongodb.sh
|
||||
|
||||
DATE=$(date +"%Y%m%d_%H%M%S")
|
||||
BACKUP_DIR="/backups/mongodb"
|
||||
DB_NAME="oldvinehotel"
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Create backup
|
||||
mongodump --db $DB_NAME --out $BACKUP_DIR/$DATE
|
||||
|
||||
# Compress backup
|
||||
tar -czf $BACKUP_DIR/mongodb_backup_$DATE.tar.gz -C $BACKUP_DIR $DATE
|
||||
|
||||
# Remove uncompressed backup
|
||||
rm -rf $BACKUP_DIR/$DATE
|
||||
|
||||
# Keep only last 7 days of backups
|
||||
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
|
||||
|
||||
# Upload to cloud storage (optional)
|
||||
# aws s3 cp $BACKUP_DIR/mongodb_backup_$DATE.tar.gz s3://hotel-backups/
|
||||
```
|
||||
|
||||
### Application Backup
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup-application.sh
|
||||
|
||||
DATE=$(date +"%Y%m%d_%H%M%S")
|
||||
APP_DIR="/var/www/hotel"
|
||||
BACKUP_DIR="/backups/application"
|
||||
|
||||
# Create backup
|
||||
tar -czf $BACKUP_DIR/app_backup_$DATE.tar.gz \
|
||||
--exclude="node_modules" \
|
||||
--exclude=".git" \
|
||||
--exclude="logs" \
|
||||
$APP_DIR
|
||||
|
||||
# Keep only last 3 backups
|
||||
ls -t $BACKUP_DIR/app_backup_*.tar.gz | tail -n +4 | xargs rm -f
|
||||
```
|
||||
|
||||
## 🚨 Disaster Recovery
|
||||
|
||||
### Recovery Procedures
|
||||
|
||||
```bash
|
||||
# Database Recovery
|
||||
mongorestore --db oldvinehotel /path/to/backup/oldvinehotel
|
||||
|
||||
# Application Recovery
|
||||
cd /var/www
|
||||
tar -xzf /backups/application/app_backup_YYYYMMDD_HHMMSS.tar.gz
|
||||
sudo chown -R www-data:www-data hotel
|
||||
|
||||
# Restart services
|
||||
pm2 restart all
|
||||
sudo systemctl restart nginx
|
||||
```
|
||||
|
||||
### Health Check Automation
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# health-check.sh
|
||||
|
||||
# Check API health
|
||||
if ! curl -f http://localhost:5000/health > /dev/null 2>&1; then
|
||||
echo "API health check failed" | mail -s "Hotel API Down" admin@oldvinehotel.com
|
||||
pm2 restart hotel-backend
|
||||
fi
|
||||
|
||||
# Check database connection
|
||||
if ! mongo --eval "db.adminCommand('ismaster')" > /dev/null 2>&1; then
|
||||
echo "Database connection failed" | mail -s "Hotel DB Down" admin@oldvinehotel.com
|
||||
sudo systemctl restart mongod
|
||||
fi
|
||||
|
||||
# Check disk space
|
||||
USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||
if [ $USAGE -gt 80 ]; then
|
||||
echo "Disk usage is at $USAGE%" | mail -s "High Disk Usage" admin@oldvinehotel.com
|
||||
fi
|
||||
```
|
||||
|
||||
## 📈 Performance Optimization
|
||||
|
||||
### Database Optimization
|
||||
|
||||
```bash
|
||||
# MongoDB configuration
|
||||
# /etc/mongod.conf
|
||||
storage:
|
||||
wiredTiger:
|
||||
engineConfig:
|
||||
cacheSizeGB: 2
|
||||
collectionConfig:
|
||||
blockCompressor: snappy
|
||||
|
||||
net:
|
||||
bindIp: 127.0.0.1
|
||||
port: 27017
|
||||
maxIncomingConnections: 100
|
||||
|
||||
operationProfiling:
|
||||
slowOpThresholdMs: 100
|
||||
mode: slowOp
|
||||
```
|
||||
|
||||
### Application Optimization
|
||||
|
||||
```javascript
|
||||
// Add to server configuration
|
||||
const compression = require('compression');
|
||||
const helmet = require('helmet');
|
||||
|
||||
app.use(compression());
|
||||
app.use(helmet());
|
||||
|
||||
// Connection pooling
|
||||
mongoose.connect(process.env.MONGODB_URI, {
|
||||
maxPoolSize: 10,
|
||||
serverSelectionTimeoutMS: 5000,
|
||||
socketTimeoutMS: 45000,
|
||||
});
|
||||
```
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
```bash
|
||||
# Check application logs
|
||||
pm2 logs hotel-backend
|
||||
|
||||
# Check nginx logs
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# Check MongoDB logs
|
||||
sudo tail -f /var/log/mongodb/mongod.log
|
||||
|
||||
# Check system resources
|
||||
top
|
||||
df -h
|
||||
free -h
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
```bash
|
||||
# Monitor database performance
|
||||
mongo
|
||||
db.currentOp()
|
||||
db.serverStatus()
|
||||
|
||||
# Check slow queries
|
||||
db.system.profile.find().limit(5).sort({ts:-1}).pretty()
|
||||
|
||||
# Monitor Node.js performance
|
||||
node --inspect server/index.js
|
||||
# Then open chrome://inspect
|
||||
```
|
||||
|
||||
This deployment guide provides comprehensive instructions for deploying The Old Vine Hotel website in production environments. Choose the deployment method that best fits your infrastructure and requirements.
|
||||
799
docs/DEVELOPMENT.md
Normal file
799
docs/DEVELOPMENT.md
Normal file
@@ -0,0 +1,799 @@
|
||||
# The Old Vine Hotel - Development Documentation
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
This hotel management system follows a modern, scalable architecture designed for enterprise-level operations.
|
||||
|
||||
### System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ React Client │────│ Express API │────│ MongoDB │
|
||||
│ (Port 3000) │ │ (Port 5000) │ │ Database │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Integrations │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────────┐ │
|
||||
└──────────────│ External APIs │──────────────┘
|
||||
│ • Opera PMS │
|
||||
│ • Booking.com │
|
||||
│ • Expedia │
|
||||
│ • Trip.com │
|
||||
│ • Stripe │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## 🛠️ Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# Install Node.js (v16+)
|
||||
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# Install MongoDB
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | sudo apt-key add -
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-5.0.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y mongodb-org
|
||||
sudo systemctl start mongod
|
||||
sudo systemctl enable mongod
|
||||
```
|
||||
|
||||
### Environment Setup
|
||||
|
||||
#### Development Database
|
||||
```bash
|
||||
# Create development database
|
||||
mongo
|
||||
use oldvinehotel_dev
|
||||
db.createUser({
|
||||
user: "hotel_admin",
|
||||
pwd: "secure_password",
|
||||
roles: ["readWrite"]
|
||||
})
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
**Server (.env)**
|
||||
```bash
|
||||
# Development configuration
|
||||
NODE_ENV=development
|
||||
PORT=5000
|
||||
CLIENT_URL=http://localhost:3000
|
||||
|
||||
# Database
|
||||
MONGODB_URI=mongodb://hotel_admin:secure_password@localhost:27017/oldvinehotel_dev
|
||||
|
||||
# Security
|
||||
JWT_SECRET=your_super_secret_jwt_key_for_development
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# External APIs (Development keys)
|
||||
STRIPE_SECRET_KEY=sk_test_your_development_key
|
||||
GOOGLE_MAPS_API_KEY=your_development_maps_key
|
||||
|
||||
# Email (Development)
|
||||
EMAIL_HOST=smtp.mailtrap.io
|
||||
EMAIL_PORT=2525
|
||||
EMAIL_USER=your_mailtrap_user
|
||||
EMAIL_PASS=your_mailtrap_pass
|
||||
```
|
||||
|
||||
**Client (.env)**
|
||||
```bash
|
||||
REACT_APP_API_URL=http://localhost:5000
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=pk_test_your_development_key
|
||||
REACT_APP_GOOGLE_MAPS_API_KEY=your_development_maps_key
|
||||
```
|
||||
|
||||
## 📊 Database Schema
|
||||
|
||||
### Core Models
|
||||
|
||||
#### Room Model
|
||||
```javascript
|
||||
{
|
||||
name: String, // "Deluxe Ocean Suite"
|
||||
type: String, // "Suite", "Deluxe", etc.
|
||||
roomNumber: String, // "101", "201A"
|
||||
basePrice: Number, // 299.99
|
||||
maxOccupancy: Number, // 4
|
||||
amenities: [String], // ["WiFi", "Ocean View"]
|
||||
status: String, // "Available", "Occupied"
|
||||
operaRoomId: String, // PMS integration ID
|
||||
images: [{
|
||||
url: String,
|
||||
alt: String,
|
||||
isPrimary: Boolean
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
#### Booking Model
|
||||
```javascript
|
||||
{
|
||||
bookingNumber: String, // "OVH202512345"
|
||||
confirmationCode: String, // "ABC12345"
|
||||
guest: ObjectId, // Reference to Guest
|
||||
room: ObjectId, // Reference to Room
|
||||
checkInDate: Date,
|
||||
checkOutDate: Date,
|
||||
numberOfGuests: {
|
||||
adults: Number,
|
||||
children: Number
|
||||
},
|
||||
totalAmount: Number,
|
||||
paymentStatus: String, // "Paid", "Pending"
|
||||
status: String, // "Confirmed", "Cancelled"
|
||||
stripePaymentIntentId: String
|
||||
}
|
||||
```
|
||||
|
||||
#### Guest Model
|
||||
```javascript
|
||||
{
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
email: String,
|
||||
phone: String,
|
||||
password: String, // Hashed
|
||||
loyaltyProgram: {
|
||||
tier: String, // "Bronze", "Silver", "Gold"
|
||||
points: Number
|
||||
},
|
||||
preferences: {
|
||||
roomType: String,
|
||||
language: String
|
||||
},
|
||||
totalStays: Number,
|
||||
isVIP: Boolean
|
||||
}
|
||||
```
|
||||
|
||||
### Database Indexes
|
||||
|
||||
```javascript
|
||||
// Room indexes for performance
|
||||
db.rooms.createIndex({ "roomNumber": 1 }, { unique: true })
|
||||
db.rooms.createIndex({ "type": 1, "status": 1 })
|
||||
db.rooms.createIndex({ "operaRoomId": 1 })
|
||||
|
||||
// Booking indexes
|
||||
db.bookings.createIndex({ "bookingNumber": 1 }, { unique: true })
|
||||
db.bookings.createIndex({ "confirmationCode": 1 })
|
||||
db.bookings.createIndex({ "checkInDate": 1, "checkOutDate": 1 })
|
||||
db.bookings.createIndex({ "guest": 1 })
|
||||
db.bookings.createIndex({ "room": 1 })
|
||||
|
||||
// Guest indexes
|
||||
db.guests.createIndex({ "email": 1 }, { unique: true })
|
||||
db.guests.createIndex({ "loyaltyProgram.tier": 1 })
|
||||
```
|
||||
|
||||
## 🔌 API Design
|
||||
|
||||
### RESTful API Structure
|
||||
|
||||
```
|
||||
GET /api/rooms # List rooms with filters
|
||||
GET /api/rooms/:id # Get specific room
|
||||
POST /api/rooms/:id/availability # Check availability
|
||||
|
||||
POST /api/bookings # Create booking
|
||||
GET /api/bookings/:bookingNumber # Get booking details
|
||||
PUT /api/bookings/:id/cancel # Cancel booking
|
||||
|
||||
POST /api/auth/register # Guest registration
|
||||
POST /api/auth/login # Guest login
|
||||
GET /api/auth/me # Current user info
|
||||
|
||||
POST /api/contact # Send contact message
|
||||
GET /api/contact/info # Hotel information
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
```javascript
|
||||
// Success Response
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
// Response data
|
||||
},
|
||||
"message": "Operation successful"
|
||||
}
|
||||
|
||||
// Error Response
|
||||
{
|
||||
"success": false,
|
||||
"message": "Error description",
|
||||
"errors": [
|
||||
{
|
||||
"field": "email",
|
||||
"message": "Invalid email format"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Paginated Response
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"items": [...],
|
||||
"pagination": {
|
||||
"currentPage": 1,
|
||||
"totalPages": 10,
|
||||
"totalCount": 100,
|
||||
"hasNextPage": true,
|
||||
"hasPrevPage": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Authentication Flow
|
||||
|
||||
### JWT Implementation
|
||||
|
||||
```javascript
|
||||
// Token Generation
|
||||
const generateToken = (payload) => {
|
||||
return jwt.sign(payload, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '7d'
|
||||
});
|
||||
};
|
||||
|
||||
// Token Verification Middleware
|
||||
const auth = async (req, res, next) => {
|
||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ success: false, message: 'Access denied' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = await User.findById(decoded.id);
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).json({ success: false, message: 'Invalid token' });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Password Security
|
||||
|
||||
```javascript
|
||||
// Password Hashing (in model)
|
||||
guestSchema.pre('save', async function(next) {
|
||||
if (!this.isModified('password')) return next();
|
||||
|
||||
this.password = await bcrypt.hash(this.password, 12);
|
||||
next();
|
||||
});
|
||||
|
||||
// Password Comparison
|
||||
guestSchema.methods.comparePassword = async function(candidatePassword) {
|
||||
return bcrypt.compare(candidatePassword, this.password);
|
||||
};
|
||||
```
|
||||
|
||||
## 💳 Payment Integration
|
||||
|
||||
### Stripe Implementation
|
||||
|
||||
```javascript
|
||||
// Create Payment Intent
|
||||
const createPayment = async (amount, bookingData) => {
|
||||
const paymentIntent = await stripe.paymentIntents.create({
|
||||
amount: Math.round(amount * 100), // Convert to cents
|
||||
currency: 'usd',
|
||||
metadata: {
|
||||
bookingNumber: bookingData.bookingNumber,
|
||||
guestEmail: bookingData.guest.email
|
||||
}
|
||||
});
|
||||
|
||||
return paymentIntent;
|
||||
};
|
||||
|
||||
// Handle Webhook
|
||||
const handleStripeWebhook = (req, res) => {
|
||||
const sig = req.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
req.body,
|
||||
sig,
|
||||
process.env.STRIPE_WEBHOOK_SECRET
|
||||
);
|
||||
} catch (err) {
|
||||
return res.status(400).send(`Webhook signature verification failed.`);
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case 'payment_intent.succeeded':
|
||||
// Handle successful payment
|
||||
break;
|
||||
case 'payment_intent.payment_failed':
|
||||
// Handle failed payment
|
||||
break;
|
||||
}
|
||||
|
||||
res.json({ received: true });
|
||||
};
|
||||
```
|
||||
|
||||
## 🔗 Integration Services
|
||||
|
||||
### Opera PMS Integration
|
||||
|
||||
```javascript
|
||||
class OperaPMSService {
|
||||
async createReservation(booking) {
|
||||
const xmlRequest = this.generateXMLRequest('OTA_HotelResRQ', {
|
||||
// XML structure for Opera PMS
|
||||
});
|
||||
|
||||
const response = await this.client.post('/reservations', xmlRequest);
|
||||
return this.parseXMLResponse(response.data);
|
||||
}
|
||||
|
||||
async syncAvailability() {
|
||||
// Sync room availability with Opera PMS
|
||||
}
|
||||
|
||||
async updateRates(roomType, rates) {
|
||||
// Update room rates in Opera PMS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Booking Platform APIs
|
||||
|
||||
```javascript
|
||||
// Booking.com Integration
|
||||
class BookingComService {
|
||||
async updateAvailability(roomType, dates, availability) {
|
||||
const xmlRequest = this.buildAvailabilityXML(roomType, dates, availability);
|
||||
return this.sendRequest(xmlRequest);
|
||||
}
|
||||
|
||||
async processWebhook(webhookData) {
|
||||
switch (webhookData.event_type) {
|
||||
case 'booking_created':
|
||||
await this.importBooking(webhookData);
|
||||
break;
|
||||
case 'booking_cancelled':
|
||||
await this.cancelBooking(webhookData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 Frontend Architecture
|
||||
|
||||
### React Component Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── common/ # Shared components
|
||||
│ │ ├── Button/
|
||||
│ │ ├── Modal/
|
||||
│ │ └── Loading/
|
||||
│ ├── layout/ # Layout components
|
||||
│ │ ├── Header/
|
||||
│ │ ├── Footer/
|
||||
│ │ └── Sidebar/
|
||||
│ └── booking/ # Booking-specific components
|
||||
│ ├── RoomCard/
|
||||
│ ├── BookingForm/
|
||||
│ └── PaymentForm/
|
||||
├── pages/ # Page components
|
||||
├── services/ # API services
|
||||
├── hooks/ # Custom React hooks
|
||||
├── utils/ # Utility functions
|
||||
└── context/ # React contexts
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```javascript
|
||||
// React Query for server state
|
||||
const { data: rooms, isLoading } = useQuery(
|
||||
['rooms', filters],
|
||||
() => roomsAPI.getRooms(filters),
|
||||
{
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
cacheTime: 10 * 60 * 1000, // 10 minutes
|
||||
}
|
||||
);
|
||||
|
||||
// Context for global state
|
||||
const AuthContext = createContext();
|
||||
|
||||
export const AuthProvider = ({ children }) => {
|
||||
const [user, setUser] = useState(null);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isAuthenticated, setUser }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### API Service Layer
|
||||
|
||||
```javascript
|
||||
// API service structure
|
||||
class RoomsAPI {
|
||||
async getRooms(filters = {}) {
|
||||
const params = new URLSearchParams(filters);
|
||||
const response = await api.get(`/rooms?${params}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async checkAvailability(roomId, dates) {
|
||||
const response = await api.post(`/rooms/${roomId}/availability`, dates);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async createBooking(bookingData) {
|
||||
const response = await api.post('/bookings', bookingData);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const roomsAPI = new RoomsAPI();
|
||||
```
|
||||
|
||||
## 🧪 Testing Strategy
|
||||
|
||||
### Backend Testing
|
||||
|
||||
```javascript
|
||||
// Unit Tests (Jest)
|
||||
describe('Room Model', () => {
|
||||
test('should calculate current price with seasonal pricing', () => {
|
||||
const room = new Room({
|
||||
basePrice: 200,
|
||||
seasonalPricing: [{
|
||||
season: 'summer',
|
||||
startDate: new Date('2024-06-01'),
|
||||
endDate: new Date('2024-08-31'),
|
||||
priceMultiplier: 1.5
|
||||
}]
|
||||
});
|
||||
|
||||
expect(room.currentPrice).toBe(300);
|
||||
});
|
||||
});
|
||||
|
||||
// Integration Tests
|
||||
describe('Booking API', () => {
|
||||
test('POST /api/bookings should create booking', async () => {
|
||||
const bookingData = {
|
||||
roomId: 'room123',
|
||||
checkInDate: '2024-06-01',
|
||||
checkOutDate: '2024-06-03',
|
||||
guestInfo: { /* guest data */ }
|
||||
};
|
||||
|
||||
const response = await request(app)
|
||||
.post('/api/bookings')
|
||||
.send(bookingData)
|
||||
.expect(201);
|
||||
|
||||
expect(response.body.success).toBe(true);
|
||||
expect(response.body.data.booking).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Frontend Testing
|
||||
|
||||
```javascript
|
||||
// Component Tests (React Testing Library)
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import BookingForm from '../BookingForm';
|
||||
|
||||
test('should submit booking form with valid data', async () => {
|
||||
render(<BookingForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/check-in date/i), {
|
||||
target: { value: '2024-06-01' }
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /book now/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/booking confirmed/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 📊 Performance Optimization
|
||||
|
||||
### Database Optimization
|
||||
|
||||
```javascript
|
||||
// Efficient aggregation for room availability
|
||||
const getAvailableRooms = async (checkIn, checkOut) => {
|
||||
return Room.aggregate([
|
||||
{
|
||||
$match: {
|
||||
status: 'Available',
|
||||
isActive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'bookings',
|
||||
let: { roomId: '$_id' },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ['$room', '$$roomId'] },
|
||||
status: { $in: ['Confirmed', 'Checked In'] },
|
||||
$or: [
|
||||
{
|
||||
checkInDate: { $lt: checkOut },
|
||||
checkOutDate: { $gt: checkIn }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
as: 'conflictingBookings'
|
||||
}
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
conflictingBookings: { $size: 0 }
|
||||
}
|
||||
}
|
||||
]);
|
||||
};
|
||||
```
|
||||
|
||||
### Frontend Optimization
|
||||
|
||||
```javascript
|
||||
// Code splitting and lazy loading
|
||||
const Booking = lazy(() => import('./pages/Booking'));
|
||||
const AdminDashboard = lazy(() => import('./pages/admin/Dashboard'));
|
||||
|
||||
// Memoization for expensive calculations
|
||||
const ExpensiveComponent = memo(({ data }) => {
|
||||
const processedData = useMemo(() => {
|
||||
return expensiveCalculation(data);
|
||||
}, [data]);
|
||||
|
||||
return <div>{processedData}</div>;
|
||||
});
|
||||
|
||||
// Image optimization
|
||||
const OptimizedImage = ({ src, alt, ...props }) => {
|
||||
const [imageSrc, setImageSrc] = useState(src + '?w=50&q=10'); // Placeholder
|
||||
|
||||
useEffect(() => {
|
||||
const img = new Image();
|
||||
img.onload = () => setImageSrc(src);
|
||||
img.src = src;
|
||||
}, [src]);
|
||||
|
||||
return <img src={imageSrc} alt={alt} {...props} />;
|
||||
};
|
||||
```
|
||||
|
||||
## 🔍 Monitoring & Logging
|
||||
|
||||
### Application Logging
|
||||
|
||||
```javascript
|
||||
// Winston logger configuration
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
||||
new winston.transports.File({ filename: 'logs/combined.log' }),
|
||||
new winston.transports.File({
|
||||
filename: 'logs/bookings.log',
|
||||
format: winston.format((info) => {
|
||||
return info.type === 'booking' ? info : false;
|
||||
})()
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// Usage in application
|
||||
logger.info('Booking created', {
|
||||
type: 'booking',
|
||||
bookingId: booking._id,
|
||||
guestEmail: booking.guest.email,
|
||||
amount: booking.totalAmount
|
||||
});
|
||||
```
|
||||
|
||||
### Health Monitoring
|
||||
|
||||
```javascript
|
||||
// Health check endpoint
|
||||
app.get('/health', async (req, res) => {
|
||||
const healthCheck = {
|
||||
status: 'OK',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
checks: {
|
||||
database: await checkDatabaseHealth(),
|
||||
redis: await checkRedisHealth(),
|
||||
external_apis: await checkExternalAPIs()
|
||||
}
|
||||
};
|
||||
|
||||
const isHealthy = Object.values(healthCheck.checks).every(check => check.status === 'OK');
|
||||
|
||||
res.status(isHealthy ? 200 : 503).json(healthCheck);
|
||||
});
|
||||
```
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Docker Configuration
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile for backend
|
||||
FROM node:16-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
build: ./client
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- REACT_APP_API_URL=http://backend:5000
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
backend:
|
||||
build: ./server
|
||||
ports:
|
||||
- "5000:5000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- MONGODB_URI=mongodb://mongodb:27017/oldvinehotel
|
||||
depends_on:
|
||||
- mongodb
|
||||
|
||||
mongodb:
|
||||
image: mongo:5.0
|
||||
ports:
|
||||
- "27017:27017"
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```bash
|
||||
# Build and deploy script
|
||||
#!/bin/bash
|
||||
|
||||
# Build frontend
|
||||
cd client
|
||||
npm run build
|
||||
|
||||
# Deploy to CDN (e.g., AWS S3 + CloudFront)
|
||||
aws s3 sync build/ s3://hotel-website-bucket --delete
|
||||
aws cloudfront create-invalidation --distribution-id ABCD1234 --paths "/*"
|
||||
|
||||
# Deploy backend
|
||||
cd ../server
|
||||
|
||||
# Build Docker image
|
||||
docker build -t hotel-api:latest .
|
||||
|
||||
# Deploy to container registry
|
||||
docker tag hotel-api:latest your-registry/hotel-api:latest
|
||||
docker push your-registry/hotel-api:latest
|
||||
|
||||
# Deploy to production environment
|
||||
kubectl apply -f k8s/
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Database Connection
|
||||
```bash
|
||||
# Check MongoDB status
|
||||
sudo systemctl status mongod
|
||||
|
||||
# View MongoDB logs
|
||||
sudo journalctl -u mongod
|
||||
|
||||
# Test connection
|
||||
mongo --eval "db.adminCommand('ismaster')"
|
||||
```
|
||||
|
||||
#### API Issues
|
||||
```bash
|
||||
# Check API health
|
||||
curl http://localhost:5000/health
|
||||
|
||||
# Test specific endpoint
|
||||
curl -X POST http://localhost:5000/api/rooms/availability \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"checkIn":"2024-06-01","checkOut":"2024-06-03"}'
|
||||
```
|
||||
|
||||
#### Frontend Issues
|
||||
```bash
|
||||
# Clear React cache
|
||||
npm start -- --reset-cache
|
||||
|
||||
# Check bundle size
|
||||
npm run build -- --analyze
|
||||
|
||||
# Test production build locally
|
||||
npx serve -s build
|
||||
```
|
||||
|
||||
### Performance Debugging
|
||||
|
||||
```javascript
|
||||
// Database query profiling
|
||||
db.setProfilingLevel(2); // Profile all operations
|
||||
db.system.profile.find().limit(5).sort({ts:-1}).pretty();
|
||||
|
||||
// API response time monitoring
|
||||
const responseTime = require('response-time');
|
||||
app.use(responseTime((req, res, time) => {
|
||||
logger.info('API Response Time', {
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
responseTime: time,
|
||||
statusCode: res.statusCode
|
||||
});
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This documentation provides a comprehensive guide for developers working on The Old Vine Hotel project. For specific implementation details, refer to the code comments and individual component documentation.
|
||||
Reference in New Issue
Block a user