Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5101a05bca | |||
| c82caf6539 | |||
| ae1b508726 | |||
| 26c05912fc | |||
| d1033cb83a | |||
| b7458b5a37 |
@@ -0,0 +1,100 @@
|
|||||||
|
# Production Deployment Fixes
|
||||||
|
|
||||||
|
This document describes the fixes applied to resolve production deployment issues in Docker.
|
||||||
|
|
||||||
|
## Issues Fixed
|
||||||
|
|
||||||
|
### 1. Database Connection Error: "getaddrinfo ENOTFOUND postgres"
|
||||||
|
**Problem**: The backend was trying to connect to hostname "postgres" but the database service is named "db" in docker-compose.
|
||||||
|
**Solution**:
|
||||||
|
- Updated `knexfile.js` to use correct default host "db" instead of "postgres"
|
||||||
|
- Added `depends_on: db` to backend service in docker-compose.prod.yml
|
||||||
|
|
||||||
|
### 2. Backend Starting Before Database Ready
|
||||||
|
**Problem**: Backend service started before PostgreSQL was ready, causing connection failures.
|
||||||
|
**Solution**:
|
||||||
|
- Created `wait-for-db.sh` script that waits for PostgreSQL to be ready
|
||||||
|
- Updated Dockerfile to install postgresql-client and use the wait script
|
||||||
|
- Script also runs migrations automatically on startup
|
||||||
|
|
||||||
|
### 3. Email Processor Initialization Failure
|
||||||
|
**Problem**: Email processor tried to initialize on module load before database was available.
|
||||||
|
**Solution**:
|
||||||
|
- Modified `emailProcessor.js` to export initialization functions
|
||||||
|
- Updated `server.js` to call initialization after database is ready
|
||||||
|
- Added proper error handling for email service initialization
|
||||||
|
|
||||||
|
### 4. Missing Environment Variables
|
||||||
|
**Problem**: Critical storage path environment variables were missing.
|
||||||
|
**Solution**:
|
||||||
|
- Added STORAGE_PATH, EVENTS_PATH, and ARCHIVE_PATH to docker-compose.prod.yml
|
||||||
|
- Created `.env.example` documenting all required environment variables
|
||||||
|
|
||||||
|
### 5. Enhanced Health Check
|
||||||
|
**Problem**: Basic health check didn't verify database connectivity.
|
||||||
|
**Solution**:
|
||||||
|
- Updated `/api/health` endpoint to check database connection
|
||||||
|
- Returns proper HTTP 503 status when unhealthy
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
1. **backend/knexfile.js** - Fixed production database defaults
|
||||||
|
2. **backend/wait-for-db.sh** - Created database wait script
|
||||||
|
3. **backend/Dockerfile** - Added postgresql-client and wait script
|
||||||
|
4. **docker-compose.prod.yml** - Added dependencies and environment variables
|
||||||
|
5. **backend/src/services/emailProcessor.js** - Disabled auto-initialization
|
||||||
|
6. **backend/server.js** - Added email initialization and improved health check
|
||||||
|
7. **backend/.env.example** - Created environment variable documentation
|
||||||
|
|
||||||
|
## Deployment Steps
|
||||||
|
|
||||||
|
1. Ensure all environment variables are set according to `.env.example`
|
||||||
|
2. Build and deploy with docker-compose:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.prod.yml build
|
||||||
|
docker-compose -f docker-compose.prod.yml up -d
|
||||||
|
```
|
||||||
|
3. The backend will now:
|
||||||
|
- Wait for PostgreSQL to be ready
|
||||||
|
- Run migrations automatically
|
||||||
|
- Initialize all services in proper order
|
||||||
|
- Provide health status at `/api/health`
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Check deployment health:
|
||||||
|
```bash
|
||||||
|
curl http://localhost/api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"database": "connected",
|
||||||
|
"timestamp": "2025-07-13T20:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Email Configuration
|
||||||
|
|
||||||
|
Email service requires configuration in the database. If email is not configured:
|
||||||
|
- The service will log a warning but continue running
|
||||||
|
- Emails will be queued but not sent
|
||||||
|
- Configure email settings in the admin panel after deployment
|
||||||
|
|
||||||
|
## PostgreSQL Connection Fix
|
||||||
|
|
||||||
|
### Issue: "no pg_hba.conf entry for host"
|
||||||
|
This error occurs when PostgreSQL requires SSL but the client connects without encryption.
|
||||||
|
|
||||||
|
### Solution:
|
||||||
|
- Disabled SSL requirement for PostgreSQL in Docker environment (`ssl=off`)
|
||||||
|
- Added proper authentication method (`scram-sha-256`)
|
||||||
|
- This is acceptable for internal Docker networks where all traffic is isolated
|
||||||
|
|
||||||
|
### Security Note:
|
||||||
|
For production deployments exposed to the internet:
|
||||||
|
1. Use SSL certificates for PostgreSQL
|
||||||
|
2. Or ensure the database is only accessible within the Docker network
|
||||||
|
3. Never expose PostgreSQL port (5432) directly to the internet
|
||||||
+31
-32
@@ -1,42 +1,41 @@
|
|||||||
NODE_ENV=development
|
# Backend Environment Variables Example
|
||||||
|
# Copy this file to .env and update with your values
|
||||||
|
|
||||||
|
# Application
|
||||||
|
NODE_ENV=production
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# URLs
|
|
||||||
ADMIN_URL=http://localhost:3000
|
|
||||||
FRONTEND_URL=http://localhost:3001
|
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
JWT_SECRET=dev-secret-key
|
JWT_SECRET=your-very-secure-jwt-secret-at-least-32-characters-long
|
||||||
|
|
||||||
|
# URLs
|
||||||
|
ADMIN_URL=https://yourdomain.com
|
||||||
|
FRONTEND_URL=https://yourdomain.com
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DATABASE_CLIENT=pg
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_USER=picpeak
|
||||||
|
DB_PASSWORD=your-secure-database-password
|
||||||
|
DB_NAME=picpeak
|
||||||
|
|
||||||
# Email Configuration
|
# Email Configuration
|
||||||
SMTP_HOST=mailhog
|
SMTP_HOST=smtp.example.com
|
||||||
SMTP_PORT=1025
|
SMTP_PORT=587
|
||||||
SMTP_SECURE=false
|
SMTP_SECURE=false
|
||||||
SMTP_USER=
|
SMTP_USER=your-smtp-username
|
||||||
SMTP_PASS=
|
SMTP_PASS=your-smtp-password
|
||||||
EMAIL_FROM=noreply@localhost
|
EMAIL_FROM=noreply@yourdomain.com
|
||||||
|
|
||||||
# Storage Paths (relative to project root)
|
# Storage Paths (Docker)
|
||||||
STORAGE_PATH=./storage
|
STORAGE_PATH=/app/storage
|
||||||
EVENTS_PATH=./storage/events
|
EVENTS_PATH=/app/storage/events
|
||||||
ARCHIVE_PATH=./storage/events/archived
|
ARCHIVE_PATH=/app/storage/events/archived
|
||||||
|
|
||||||
|
# Analytics (Optional)
|
||||||
|
UMAMI_URL=https://analytics.yourdomain.com
|
||||||
|
UMAMI_WEBSITE_ID=your-website-id
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
# Database Configuration
|
|
||||||
# Development: Use SQLite
|
|
||||||
DATABASE_CLIENT=sqlite3
|
|
||||||
DATABASE_PATH=./data/photo_sharing.db
|
|
||||||
|
|
||||||
# Production: Use PostgreSQL
|
|
||||||
# DATABASE_CLIENT=pg
|
|
||||||
# DB_HOST=localhost
|
|
||||||
# DB_PORT=5432
|
|
||||||
# DB_USER=picpeak
|
|
||||||
# DB_PASSWORD=your-secure-password
|
|
||||||
# DB_NAME=picpeak
|
|
||||||
|
|
||||||
# Umami Analytics (optional)
|
|
||||||
UMAMI_URL=
|
|
||||||
UMAMI_WEBSITE_ID=
|
|
||||||
|
|||||||
+6
-3
@@ -16,8 +16,8 @@ FROM node:18-alpine
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dumb-init for proper signal handling
|
# Install dumb-init for proper signal handling and postgresql-client for database checks
|
||||||
RUN apk add --no-cache dumb-init
|
RUN apk add --no-cache dumb-init postgresql-client
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
|
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
|
||||||
@@ -26,6 +26,9 @@ RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
|
|||||||
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
|
||||||
COPY --chown=nodejs:nodejs . .
|
COPY --chown=nodejs:nodejs . .
|
||||||
|
|
||||||
|
# Make wait script executable
|
||||||
|
RUN chmod +x wait-for-db.sh
|
||||||
|
|
||||||
# Create necessary directories
|
# Create necessary directories
|
||||||
RUN mkdir -p storage/events/active storage/events/archived storage/thumbnails data logs && \
|
RUN mkdir -p storage/events/active storage/events/archived storage/thumbnails data logs && \
|
||||||
chown -R nodejs:nodejs storage data logs
|
chown -R nodejs:nodejs storage data logs
|
||||||
@@ -35,4 +38,4 @@ USER nodejs
|
|||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENTRYPOINT ["dumb-init", "--"]
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
CMD ["node", "server.js"]
|
CMD ["./wait-for-db.sh", "node", "server.js"]
|
||||||
|
|||||||
+5
-4
@@ -27,11 +27,12 @@ const config = {
|
|||||||
production: {
|
production: {
|
||||||
client: process.env.DATABASE_CLIENT || 'pg',
|
client: process.env.DATABASE_CLIENT || 'pg',
|
||||||
connection: {
|
connection: {
|
||||||
host: process.env.DB_HOST || 'postgres',
|
host: process.env.DB_HOST || 'db',
|
||||||
port: process.env.DB_PORT || 5432,
|
port: process.env.DB_PORT || 5432,
|
||||||
user: process.env.DB_USER || 'postgres',
|
user: process.env.DB_USER || 'picpeak',
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: process.env.DB_PASSWORD,
|
||||||
database: process.env.DB_NAME || 'photo_sharing'
|
database: process.env.DB_NAME || 'picpeak',
|
||||||
|
ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false
|
||||||
},
|
},
|
||||||
pool: {
|
pool: {
|
||||||
min: 2,
|
min: 2,
|
||||||
|
|||||||
Generated
+140
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"multer": "^2.0.1",
|
"multer": "^2.0.1",
|
||||||
"node-cron": "^3.0.2",
|
"node-cron": "^3.0.2",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.1",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"react-i18next": "^15.6.0",
|
"react-i18next": "^15.6.0",
|
||||||
"sharp": "^0.32.0",
|
"sharp": "^0.32.0",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
@@ -6471,12 +6472,101 @@
|
|||||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.16.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-connection-string": "^2.9.1",
|
||||||
|
"pg-pool": "^3.10.1",
|
||||||
|
"pg-protocol": "^1.10.3",
|
||||||
|
"pg-types": "2.2.0",
|
||||||
|
"pgpass": "1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"pg-cloudflare": "^1.2.7"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-cloudflare": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/pg-connection-string": {
|
"node_modules/pg-connection-string": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz",
|
||||||
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==",
|
"integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
|
||||||
|
"integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.10.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz",
|
||||||
|
"integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg/node_modules/pg-connection-string": {
|
||||||
|
"version": "2.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
|
||||||
|
"integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -6575,6 +6665,45 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prebuild-install": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
@@ -7494,6 +7623,15 @@
|
|||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sprintf-js": {
|
"node_modules/sprintf-js": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-backend",
|
"name": "picpeak-backend",
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"description": "Backend for PicPeak event photo sharing platform",
|
"description": "Backend for PicPeak event photo sharing platform",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
"multer": "^2.0.1",
|
"multer": "^2.0.1",
|
||||||
"node-cron": "^3.0.2",
|
"node-cron": "^3.0.2",
|
||||||
"nodemailer": "^6.9.1",
|
"nodemailer": "^6.9.1",
|
||||||
|
"pg": "^8.16.3",
|
||||||
"react-i18next": "^15.6.0",
|
"react-i18next": "^15.6.0",
|
||||||
"sharp": "^0.32.0",
|
"sharp": "^0.32.0",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
|
|||||||
+22
-4
@@ -13,7 +13,7 @@ const path = require('path');
|
|||||||
const { initializeDatabase } = require('./src/database/db');
|
const { initializeDatabase } = require('./src/database/db');
|
||||||
const { startFileWatcher } = require('./src/services/fileWatcher');
|
const { startFileWatcher } = require('./src/services/fileWatcher');
|
||||||
const { startExpirationChecker } = require('./src/services/expirationChecker');
|
const { startExpirationChecker } = require('./src/services/expirationChecker');
|
||||||
const { startEmailQueueProcessor } = require('./src/services/emailProcessor');
|
const { initializeTransporter, startEmailQueueProcessor } = require('./src/services/emailProcessor');
|
||||||
const { maintenanceMiddleware } = require('./src/middleware/maintenance');
|
const { maintenanceMiddleware } = require('./src/middleware/maintenance');
|
||||||
const { sessionTimeoutMiddleware } = require('./src/middleware/sessionTimeout');
|
const { sessionTimeoutMiddleware } = require('./src/middleware/sessionTimeout');
|
||||||
const logger = require('./src/utils/logger');
|
const logger = require('./src/utils/logger');
|
||||||
@@ -152,8 +152,25 @@ app.use('/thumbnails', require('./src/middleware/photoAuth'), setCorsHeaders, se
|
|||||||
app.use('/uploads', setCorsHeaders, secureStatic(path.join(__dirname, 'storage/uploads')));
|
app.use('/uploads', setCorsHeaders, secureStatic(path.join(__dirname, 'storage/uploads')));
|
||||||
|
|
||||||
// Health check endpoint
|
// Health check endpoint
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', async (req, res) => {
|
||||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
try {
|
||||||
|
// Check database connectivity
|
||||||
|
await db.raw('SELECT 1');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
database: 'connected',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Health check failed:', error);
|
||||||
|
res.status(503).json({
|
||||||
|
status: 'error',
|
||||||
|
database: 'disconnected',
|
||||||
|
error: error.message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
@@ -189,7 +206,8 @@ async function startServer() {
|
|||||||
// Start expiration checker
|
// Start expiration checker
|
||||||
startExpirationChecker();
|
startExpirationChecker();
|
||||||
|
|
||||||
// Start email queue processor
|
// Initialize email transporter and start queue processor
|
||||||
|
await initializeTransporter();
|
||||||
startEmailQueueProcessor();
|
startEmailQueueProcessor();
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
|
|||||||
@@ -395,12 +395,15 @@ function stopEmailQueueProcessor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize on module load
|
// Initialize on module load - DISABLED for production startup
|
||||||
initializeTransporter().then(() => {
|
// This will be called from server.js after database is ready
|
||||||
startEmailQueueProcessor();
|
// initializeTransporter().then(() => {
|
||||||
});
|
// startEmailQueueProcessor();
|
||||||
|
// });
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
initializeTransporter,
|
||||||
|
startEmailQueueProcessor,
|
||||||
sendTemplateEmail,
|
sendTemplateEmail,
|
||||||
processEmailQueue,
|
processEmailQueue,
|
||||||
queueEmail,
|
queueEmail,
|
||||||
|
|||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# wait-for-db.sh - Wait for PostgreSQL to be ready before starting the application
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
host="$DB_HOST"
|
||||||
|
port="${DB_PORT:-5432}"
|
||||||
|
user="${DB_USER:-picpeak}"
|
||||||
|
|
||||||
|
echo "Waiting for PostgreSQL at $host:$port..."
|
||||||
|
|
||||||
|
# Wait for PostgreSQL to be ready
|
||||||
|
until PGPASSWORD=$DB_PASSWORD psql -h "$host" -p "$port" -U "$user" -d "${DB_NAME:-picpeak}" -c '\q' 2>/dev/null; do
|
||||||
|
>&2 echo "PostgreSQL is unavailable - sleeping"
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
>&2 echo "PostgreSQL is up - executing command"
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
echo "Running database migrations..."
|
||||||
|
npm run migrate
|
||||||
|
|
||||||
|
# Execute the main command
|
||||||
|
exec "$@"
|
||||||
@@ -8,6 +8,8 @@ services:
|
|||||||
context: ./backend
|
context: ./backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
@@ -31,6 +33,10 @@ services:
|
|||||||
# Analytics
|
# Analytics
|
||||||
- UMAMI_URL=${UMAMI_URL}
|
- UMAMI_URL=${UMAMI_URL}
|
||||||
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
|
- UMAMI_WEBSITE_ID=${UMAMI_WEBSITE_ID}
|
||||||
|
# Storage paths
|
||||||
|
- STORAGE_PATH=/app/storage
|
||||||
|
- EVENTS_PATH=/app/storage/events
|
||||||
|
- ARCHIVE_PATH=/app/storage/events/archived
|
||||||
volumes:
|
volumes:
|
||||||
- ./storage:/app/storage
|
- ./storage:/app/storage
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
@@ -82,10 +88,15 @@ services:
|
|||||||
- POSTGRES_USER=${DB_USER:-picpeak}
|
- POSTGRES_USER=${DB_USER:-picpeak}
|
||||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||||
- POSTGRES_DB=${DB_NAME:-picpeak}
|
- POSTGRES_DB=${DB_NAME:-picpeak}
|
||||||
|
# Allow connections from any host with password authentication
|
||||||
|
- POSTGRES_HOST_AUTH_METHOD=scram-sha-256
|
||||||
|
- POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256 --auth-local=trust
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- picpeak
|
- picpeak
|
||||||
|
# Allow connections without SSL requirement from Docker network
|
||||||
|
command: postgres -c ssl=off
|
||||||
|
|
||||||
umami:
|
umami:
|
||||||
image: ghcr.io/umami-software/umami:postgresql-latest
|
image: ghcr.io/umami-software/umami:postgresql-latest
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.0.0",
|
||||||
"@tiptap/extension-link": "^2.25.0",
|
"@tiptap/extension-link": "^2.25.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "picpeak-frontend",
|
"name": "picpeak-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.5",
|
"version": "1.0.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
Reference in New Issue
Block a user