Harden dev environment configuration
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
# Global defaults
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=info
|
||||
|
||||
# Backend service
|
||||
BACKEND_HOST=0.0.0.0
|
||||
BACKEND_PORT=3001
|
||||
DATABASE_URL=postgresql://merchantsofhope_user:merchantsofhope_password@merchantsofhope-supplyanddemandportal-database:5432/merchantsofhope_supplyanddemandportal
|
||||
JWT_SECRET=merchantsofhope_jwt_secret_key_2024
|
||||
PORT=3001
|
||||
CORS_ORIGIN=http://localhost:12000
|
||||
|
||||
# Frontend service
|
||||
FRONTEND_HOST=0.0.0.0
|
||||
FRONTEND_PORT=12000
|
||||
REACT_APP_API_URL=http://localhost:3001
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ node_modules/
|
||||
dist/
|
||||
.env
|
||||
.env.*
|
||||
!.env.development
|
||||
!.env.example
|
||||
*.log
|
||||
tmp/
|
||||
|
||||
@@ -76,9 +76,9 @@ A comprehensive SAAS application for managing recruiter workflows, built with mo
|
||||
```
|
||||
|
||||
5. **Access the application**
|
||||
- Frontend: http://localhost:3000
|
||||
- Backend API: http://localhost:3001
|
||||
- Database: localhost:5432
|
||||
- Frontend: http://localhost:12000
|
||||
- Backend API: http://merchantsofhope-supplyanddemandportal-backend:3001 (inside Docker network) or http://localhost:3001 when running natively
|
||||
- Database: merchantsofhope-supplyanddemandportal-database:5432 (inside Docker network)
|
||||
|
||||
### Alternative: Native Node.js workflow
|
||||
|
||||
@@ -98,7 +98,7 @@ cd ../frontend
|
||||
npm start
|
||||
```
|
||||
|
||||
Ensure a PostgreSQL instance is running and the `DATABASE_URL` in `.env` points to it.
|
||||
Ensure a PostgreSQL instance is running and the `DATABASE_URL` in `.env` points to it. The frontend `.env.development` file pins the dev server to `0.0.0.0:12000` so it matches the Docker behaviour.
|
||||
|
||||
### Demo Accounts
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@ FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
ENV HOST=0.0.0.0 \
|
||||
PORT=3001
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
|
||||
5563
backend/package-lock.json
generated
Normal file
5563
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,8 @@
|
||||
"description": "Backend for MerchantsOfHope-SupplyANdDemandPortal recruiter workflow SAAS",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "node src/server.js",
|
||||
"dev": "nodemon src/server.js",
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"migrate": "node src/database/migrate.js",
|
||||
|
||||
27
backend/src/config/index.js
Normal file
27
backend/src/config/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const optionalNumber = (value, fallback) => {
|
||||
const parsed = parseInt(value, 10);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
};
|
||||
|
||||
const config = {
|
||||
env: process.env.NODE_ENV || 'development',
|
||||
host: process.env.HOST || process.env.BACKEND_HOST || '0.0.0.0',
|
||||
port: optionalNumber(process.env.PORT || process.env.BACKEND_PORT, 3001),
|
||||
databaseUrl: process.env.DATABASE_URL,
|
||||
jwtSecret: process.env.JWT_SECRET,
|
||||
corsOrigin: process.env.CORS_ORIGIN || '*',
|
||||
logLevel: process.env.LOG_LEVEL || 'info'
|
||||
};
|
||||
|
||||
const required = ['databaseUrl', 'jwtSecret'];
|
||||
const missingKeys = required.filter((key) => !config[key]);
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
throw new Error(`Missing required environment variables: ${missingKeys.join(', ')}`);
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,14 +1,16 @@
|
||||
const { Pool } = require('pg');
|
||||
require('dotenv').config();
|
||||
const config = require('../config');
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
connectionString: config.databaseUrl,
|
||||
ssl: config.env === 'production' ? { rejectUnauthorized: false } : false
|
||||
});
|
||||
|
||||
// Test database connection
|
||||
pool.on('connect', () => {
|
||||
if (config.logLevel !== 'silent') {
|
||||
console.log('Connected to MerchantsOfHope-SupplyANdDemandPortal database');
|
||||
}
|
||||
});
|
||||
|
||||
pool.on('error', (err) => {
|
||||
|
||||
6
backend/src/index.js
Normal file
6
backend/src/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const app = require('./server');
|
||||
const config = require('./config');
|
||||
|
||||
app.listen(config.port, config.host, () => {
|
||||
console.log(`MerchantsOfHope-SupplyANdDemandPortal backend server running on ${config.host}:${config.port}`);
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const pool = require('../database/connection');
|
||||
const config = require('../config');
|
||||
|
||||
const authenticateToken = async (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
@@ -10,7 +11,7 @@ const authenticateToken = async (req, res, next) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const decoded = jwt.verify(token, config.jwtSecret);
|
||||
|
||||
// Get user details from database
|
||||
const userResult = await pool.query(
|
||||
|
||||
@@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const pool = require('../database/connection');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const config = require('../config');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -47,7 +48,7 @@ router.post('/register', [
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
config.jwtSecret,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
@@ -106,7 +107,7 @@ router.post('/login', [
|
||||
// Generate JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
config.jwtSecret,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const morgan = require('morgan');
|
||||
require('dotenv').config();
|
||||
const config = require('./config');
|
||||
|
||||
const authRoutes = require('./routes/auth');
|
||||
const userRoutes = require('./routes/users');
|
||||
@@ -13,16 +13,19 @@ const applicationRoutes = require('./routes/applications');
|
||||
const resumeRoutes = require('./routes/resumes');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.disable('x-powered-by');
|
||||
|
||||
const corsOrigins = config.corsOrigin === '*'
|
||||
? undefined
|
||||
: config.corsOrigin.split(',').map((origin) => origin.trim());
|
||||
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
app.use(morgan('combined'));
|
||||
app.use(cors(corsOrigins ? { origin: corsOrigins, credentials: true } : {}));
|
||||
app.use(morgan(config.env === 'production' ? 'combined' : 'dev'));
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Routes
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/api/employers', employerRoutes);
|
||||
@@ -31,27 +34,21 @@ app.use('/api/jobs', jobRoutes);
|
||||
app.use('/api/applications', applicationRoutes);
|
||||
app.use('/api/resumes', resumeRoutes);
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'OK', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Error handling middleware
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({
|
||||
error: 'Something went wrong!',
|
||||
message: process.env.NODE_ENV === 'development' ? err.message : 'Internal server error'
|
||||
message: config.env === 'development' ? err.message : 'Internal server error'
|
||||
});
|
||||
});
|
||||
|
||||
// 404 handler
|
||||
app.use('*', (req, res) => {
|
||||
res.status(404).json({ error: 'Route not found' });
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`MerchantsOfHope-SupplyANdDemandPortal backend server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -36,10 +36,11 @@ services:
|
||||
depends_on:
|
||||
- merchantsofhope-supplyanddemandportal-backend
|
||||
environment:
|
||||
PORT: 3000
|
||||
HOST: 0.0.0.0
|
||||
PORT: 12000
|
||||
REACT_APP_API_URL: ${REACT_APP_API_URL:-http://merchantsofhope-supplyanddemandportal-backend:3001}
|
||||
ports:
|
||||
- "0.0.0.0:3000:3000"
|
||||
- "0.0.0.0:12000:12000"
|
||||
|
||||
volumes:
|
||||
merchantsofhope-supplyanddemandportal-postgres-data:
|
||||
|
||||
@@ -6,8 +6,8 @@ services:
|
||||
POSTGRES_DB: merchantsofhope_supplyanddemandportal
|
||||
POSTGRES_USER: merchantsofhope_user
|
||||
POSTGRES_PASSWORD: merchantsofhope_password
|
||||
ports:
|
||||
- "0.0.0.0:5432:5432"
|
||||
expose:
|
||||
- "5432"
|
||||
volumes:
|
||||
- merchantsofhope-supplyanddemandportal-postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
@@ -22,9 +22,10 @@ services:
|
||||
NODE_ENV: development
|
||||
DATABASE_URL: postgresql://merchantsofhope_user:merchantsofhope_password@merchantsofhope-supplyanddemandportal-database:5432/merchantsofhope_supplyanddemandportal
|
||||
JWT_SECRET: merchantsofhope_jwt_secret_key_2024
|
||||
PORT: 3001
|
||||
ports:
|
||||
- "0.0.0.0:3001:3001"
|
||||
HOST: ${BACKEND_HOST:-0.0.0.0}
|
||||
PORT: ${BACKEND_PORT:-3001}
|
||||
expose:
|
||||
- "3001"
|
||||
depends_on:
|
||||
- merchantsofhope-supplyanddemandportal-database
|
||||
volumes:
|
||||
@@ -39,9 +40,11 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
container_name: merchantsofhope-supplyanddemandportal-frontend
|
||||
environment:
|
||||
REACT_APP_API_URL: http://localhost:3001
|
||||
HOST: ${FRONTEND_HOST:-0.0.0.0}
|
||||
PORT: ${FRONTEND_PORT:-12000}
|
||||
REACT_APP_API_URL: http://merchantsofhope-supplyanddemandportal-backend:3001
|
||||
ports:
|
||||
- "0.0.0.0:12000:3000"
|
||||
- "0.0.0.0:12000:12000"
|
||||
depends_on:
|
||||
- merchantsofhope-supplyanddemandportal-backend
|
||||
volumes:
|
||||
|
||||
@@ -57,7 +57,7 @@ Expose the tag you want Coolify to deploy by either:
|
||||
Configure any additional secrets used by your environment (mail providers, analytics, etc.).
|
||||
|
||||
3. **Networking and ports**
|
||||
- Expose port `3000` externally for the frontend.
|
||||
- Expose port `12000` externally for the frontend (or map to 80/443 via Coolify's proxy).
|
||||
- Optionally expose `3001` if you want direct API access; otherwise rely on the frontend or internal services.
|
||||
- Attach the application to an HTTPS domain using Coolify's built-in proxy configuration.
|
||||
|
||||
|
||||
3
frontend/.env.development
Normal file
3
frontend/.env.development
Normal file
@@ -0,0 +1,3 @@
|
||||
HOST=0.0.0.0
|
||||
PORT=12000
|
||||
REACT_APP_API_URL=http://localhost:3001
|
||||
@@ -3,10 +3,13 @@ FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
ENV HOST=0.0.0.0 \
|
||||
PORT=12000
|
||||
|
||||
EXPOSE 12000
|
||||
|
||||
CMD ["npm", "start"]
|
||||
|
||||
20671
frontend/package-lock.json
generated
Normal file
20671
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import './lib/configureAxios';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
|
||||
11
frontend/src/lib/configureAxios.js
Normal file
11
frontend/src/lib/configureAxios.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = process.env.REACT_APP_API_URL || '';
|
||||
|
||||
if (baseURL) {
|
||||
axios.defaults.baseURL = baseURL;
|
||||
}
|
||||
|
||||
axios.defaults.headers.common['Content-Type'] = 'application/json';
|
||||
|
||||
export default axios;
|
||||
Reference in New Issue
Block a user