env file and yml file consistency
This commit is contained in:
parent
a41858427b
commit
b5268a0fe8
@ -1,26 +1,42 @@
|
|||||||
|
# ─── O*NET ───────────────────────────────
|
||||||
ONET_USERNAME=aptivaai
|
ONET_USERNAME=aptivaai
|
||||||
ONET_PASSWORD=2296ahq
|
ONET_PASSWORD=2296ahq
|
||||||
|
|
||||||
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
# ─── Public‐facing React build ───────────
|
||||||
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
NODE_ENV=development
|
||||||
|
REACT_APP_ENV=production
|
||||||
|
APTIVA_API_BASE=https://dev1.aptivaai.com/api
|
||||||
|
REACT_APP_API_URL=${APTIVA_API_BASE}
|
||||||
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||||
COLLEGE_SCORECARD_KEY = BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
||||||
|
REACT_APP_OPENAI_API_KEY=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
|
||||||
|
|
||||||
|
# ─── Back-end services ───────────────────
|
||||||
|
OPENAI_API_KEY=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
|
||||||
|
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||||
|
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
||||||
|
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
||||||
|
|
||||||
|
# ─── Database (premium server) ───────────
|
||||||
DB_HOST=34.67.180.54
|
DB_HOST=34.67.180.54
|
||||||
DB_PORT=3306
|
DB_PORT=3306
|
||||||
DB_USER=sqluser
|
DB_USER=sqluser
|
||||||
DB_NAME=user_profile_db
|
|
||||||
DB_PASSWORD=ps<g+2DO-eTb2mb5
|
DB_PASSWORD=ps<g+2DO-eTb2mb5
|
||||||
|
DB_NAME=user_profile_db
|
||||||
APTIVA_API_BASE=https://dev1.aptivaai.com/api
|
|
||||||
REACT_APP_API_URL=https://dev1.aptivaai.com/api
|
|
||||||
REACT_APP_ENV=production
|
|
||||||
REACT_APP_OPENAI_API_KEY=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
|
|
||||||
OPENAI_API_KEY=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
|
|
||||||
GCP_CLOUD_SQL_PASSWORD=q2O}1PU-R:|l57S0
|
GCP_CLOUD_SQL_PASSWORD=q2O}1PU-R:|l57S0
|
||||||
|
|
||||||
|
# ── Twilio (needed only by server3) ─────────────────────────
|
||||||
TWILIO_ACCOUNT_SID=ACd700c6fb9f691ccd9ccab73f2dd4173d
|
TWILIO_ACCOUNT_SID=ACd700c6fb9f691ccd9ccab73f2dd4173d
|
||||||
TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
|
TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
|
||||||
TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
|
TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
|
||||||
|
|
||||||
JWT_SECRET=gW4QsOu4AJA4MooIUC9ld2i71VbBovzV1INsaU6ftxYPrxLIeMq6/OY61j0X2RV7
|
# ─── Anything new goes here ──────────────
|
||||||
|
JWT_SECRET=a35F0iFAkkdWvSjnaLzepAl/JIxPRUh4NpcGptJgry2Z3KVLX4ZcYY5KaTf7kJY0
|
||||||
|
|
||||||
|
# ------------ CORS ------------
|
||||||
|
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://34.16.120.118:3000,https://dev1.aptivaai.com
|
||||||
|
SERVER1_PORT=5000
|
||||||
|
SERVER2_PORT=5001
|
||||||
|
SERVER3_PORT=5002
|
||||||
|
|
||||||
|
IMG_TAG=20250716
|
42
.env.staging
Normal file
42
.env.staging
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# ─── O*NET ───────────────────────────────
|
||||||
|
ONET_USERNAME=aptivaai
|
||||||
|
ONET_PASSWORD=2296ahq
|
||||||
|
|
||||||
|
# ─── Public‐facing React build ───────────
|
||||||
|
NODE_ENV=staging
|
||||||
|
REACT_APP_ENV=production
|
||||||
|
APTIVA_API_BASE=https://staging.aptivaai.com/api
|
||||||
|
REACT_APP_API_URL=${APTIVA_API_BASE}
|
||||||
|
REACT_APP_GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||||
|
REACT_APP_BLS_API_KEY=80d7a65a809a43f3a306a41ec874d231
|
||||||
|
REACT_APP_OPENAI_API_KEY=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
|
||||||
|
|
||||||
|
# ─── Back-end services ───────────────────
|
||||||
|
OPENAI_API_KEY=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
|
||||||
|
GOOGLE_MAPS_API_KEY=AIzaSyCTMgjiHUF2Vl3QriQu2kDEuZWz39ZAR20
|
||||||
|
COLLEGE_SCORECARD_KEY=BlZ0tIdmXVGI4G8NxJ9e6dXEiGUfAfnQJyw8bumj
|
||||||
|
SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
||||||
|
|
||||||
|
# ─── Database (premium server) ───────────
|
||||||
|
DB_HOST=34.67.180.54
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=sqluser
|
||||||
|
DB_PASSWORD=ps<g+2DO-eTb2mb5
|
||||||
|
DB_NAME=user_profile_db
|
||||||
|
GCP_CLOUD_SQL_PASSWORD=q2O}1PU-R:|l57S0
|
||||||
|
|
||||||
|
# ── Twilio (needed only by server3) ─────────────────────────
|
||||||
|
TWILIO_ACCOUNT_SID=ACd700c6fb9f691ccd9ccab73f2dd4173d
|
||||||
|
TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
|
||||||
|
TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
|
||||||
|
|
||||||
|
# ─── Anything new goes here ──────────────
|
||||||
|
JWT_SECRET=a35F0iFAkkdWvSjnaLzepAl/JIxPRUh4NpcGptJgry2Z3KVLX4ZcYY5KaTf7kJY0
|
||||||
|
|
||||||
|
# ------------ env/staging.env ------------
|
||||||
|
CORS_ALLOWED_ORIGINS=https://staging.aptivaai.com,http://34.61.84.49:3000,http://localhost:3000
|
||||||
|
SERVER1_PORT=5000
|
||||||
|
SERVER2_PORT=5001
|
||||||
|
SERVER3_PORT=5002
|
||||||
|
|
||||||
|
IMG_TAG=20250716
|
@ -55,14 +55,24 @@ pool.query('SELECT 1', (err) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 5000;
|
const PORT = process.env.SERVER1_PORT || 5000;
|
||||||
|
|
||||||
|
/* ─── Require critical env vars ───────────────────────────────── */
|
||||||
|
if (!process.env.CORS_ALLOWED_ORIGINS) {
|
||||||
|
console.error('FATAL CORS_ALLOWED_ORIGINS is not set'); // eslint-disable-line
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!process.env.APTIVA_API_BASE) {
|
||||||
|
console.error('FATAL APTIVA_API_BASE is not set'); // eslint-disable-line
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Allowed origins for CORS (comma-separated in env) ──────── */
|
||||||
|
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
|
||||||
|
.split(',')
|
||||||
|
.map(o => o.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
// Allowed origins for CORS
|
|
||||||
const allowedOrigins = [
|
|
||||||
'http://localhost:3000',
|
|
||||||
'http://34.16.120.118:3000',
|
|
||||||
'https://dev1.aptivaai.com',
|
|
||||||
];
|
|
||||||
|
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
@ -101,7 +111,7 @@ app.use(
|
|||||||
|
|
||||||
// Handle preflight requests explicitly
|
// Handle preflight requests explicitly
|
||||||
app.options('*', (req, res) => {
|
app.options('*', (req, res) => {
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '');
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Access-Control-Allow-Headers',
|
'Access-Control-Allow-Headers',
|
||||||
|
@ -38,22 +38,20 @@ const chatLimiter = rateLimit({
|
|||||||
keyGenerator: req => req.user?.id || req.ip
|
keyGenerator: req => req.user?.id || req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
// Whitelist CORS
|
if (!process.env.APTIVA_API_BASE) {
|
||||||
const allowedOrigins = [
|
console.error('FATAL APTIVA_API_BASE is not set');
|
||||||
'http://localhost:3000',
|
process.exit(1);
|
||||||
'http://34.16.120.118:3000',
|
}
|
||||||
'https://dev1.aptivaai.com',
|
|
||||||
];
|
|
||||||
|
|
||||||
// CIP->SOC mapping file
|
// CIP->SOC mapping file
|
||||||
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
|
const mappingFilePath = '/home/jcoakley/aptiva-dev1-app/public/CIP_to_ONET_SOC.xlsx';
|
||||||
|
|
||||||
// Institution data
|
// Institution data
|
||||||
const institutionFilePath = path.resolve(rootPath, 'public', 'Institution_data.json');
|
const institutionFilePath = 'home/jcoakley/aptiva-dev1-app/public/Institution_data.json';
|
||||||
|
|
||||||
// Create Express app
|
// Create Express app
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 5001;
|
const PORT = process.env.SERVER2_PORT || 5001;
|
||||||
|
|
||||||
// at top of backend/server.js (do once per server codebase)
|
// at top of backend/server.js (do once per server codebase)
|
||||||
app.get('/healthz', (req, res) => res.sendStatus(204)); // 204 No Content
|
app.get('/healthz', (req, res) => res.sendStatus(204)); // 204 No Content
|
||||||
@ -86,9 +84,23 @@ async function initDatabases() {
|
|||||||
|
|
||||||
await initDatabases();
|
await initDatabases();
|
||||||
|
|
||||||
/**************************************************
|
/* ──────────────────────────────────────────────────────────────
|
||||||
* Security, CORS, JSON Body
|
* SECURITY, CORS, JSON Body
|
||||||
**************************************************/
|
* ────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* 1 — Require critical env var up-front */
|
||||||
|
if (!process.env.CORS_ALLOWED_ORIGINS) {
|
||||||
|
console.error('FATAL CORS_ALLOWED_ORIGINS is not set');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2 — Build allow-list from env (comma-separated) */
|
||||||
|
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
|
||||||
|
.split(',')
|
||||||
|
.map(o => o.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
/* 3 — Security headers */
|
||||||
app.use(
|
app.use(
|
||||||
helmet({
|
helmet({
|
||||||
contentSecurityPolicy: false,
|
contentSecurityPolicy: false,
|
||||||
@ -96,8 +108,11 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* 4 — Dynamic CORS / pre-flight handling */
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
const origin = req.headers.origin;
|
const origin = req.headers.origin;
|
||||||
|
|
||||||
|
/* 4a — Whitelisted origins (credentials allowed) */
|
||||||
if (origin && allowedOrigins.includes(origin)) {
|
if (origin && allowedOrigins.includes(origin)) {
|
||||||
res.setHeader('Access-Control-Allow-Origin', origin);
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||||
@ -105,17 +120,22 @@ app.use((req, res, next) => {
|
|||||||
'Access-Control-Allow-Headers',
|
'Access-Control-Allow-Headers',
|
||||||
'Authorization, Content-Type, Accept, Origin, X-Requested-With, Access-Control-Allow-Methods'
|
'Authorization, Content-Type, Accept, Origin, X-Requested-With, Access-Control-Allow-Methods'
|
||||||
);
|
);
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
res.setHeader(
|
||||||
|
'Access-Control-Allow-Methods',
|
||||||
|
'GET, POST, OPTIONS'
|
||||||
|
);
|
||||||
|
|
||||||
|
/* 4b — Public JSON exception */
|
||||||
} else if (req.path.includes('Institution_data')) {
|
} else if (req.path.includes('Institution_data')) {
|
||||||
// For that JSON
|
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
'Access-Control-Allow-Headers',
|
'Access-Control-Allow-Headers',
|
||||||
'Content-Type, Accept, Origin, X-Requested-With'
|
'Content-Type, Accept, Origin, X-Requested-With'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* 4c — Default permissive fallback (same as your original) */
|
||||||
} else {
|
} else {
|
||||||
// default
|
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
@ -123,21 +143,22 @@ app.use((req, res, next) => {
|
|||||||
'Authorization, Content-Type, Accept, Origin, X-Requested-With'
|
'Authorization, Content-Type, Accept, Origin, X-Requested-With'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 4d — Short-circuit pre-flight requests */
|
||||||
if (req.method === 'OPTIONS') {
|
if (req.method === 'OPTIONS') {
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
res.status(204).end();
|
||||||
res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type');
|
return;
|
||||||
return res.status(204).end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* 5 — JSON parsing & static assets */
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
// For completeness
|
/* 6 — No-op pass-through (kept for completeness) */
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => next());
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**************************************************
|
/**************************************************
|
||||||
* Load CIP->SOC mapping
|
* Load CIP->SOC mapping
|
||||||
@ -383,7 +404,7 @@ app.post('/api/onet/submit_answers', async (req, res) => {
|
|||||||
const token = req.headers.authorization?.split(' ')[1];
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${process.env.MAIN_API_URL}/api/user-profile`,
|
await axios.post(`${process.env.APTIVA_API_BASE}/api/user-profile`,
|
||||||
{
|
{
|
||||||
interest_inventory_answers: answers,
|
interest_inventory_answers: answers,
|
||||||
riasec: riasecCode
|
riasec: riasecCode
|
||||||
@ -454,7 +475,7 @@ app.get('/api/onet/career-details/:socCode', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
const response = await axios.get(`https://services.onetcenter.org/ws/mnm/careers/${socCode}`, {
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.ONet_USERNAME,
|
username: process.env.ONET_USERNAME,
|
||||||
password: process.env.ONET_PASSWORD,
|
password: process.env.ONET_PASSWORD,
|
||||||
},
|
},
|
||||||
headers: { Accept: 'application/json' },
|
headers: { Accept: 'application/json' },
|
||||||
@ -1000,5 +1021,5 @@ chatFreeEndpoint(app, {
|
|||||||
* Start the Express server
|
* Start the Express server
|
||||||
**************************************************/
|
**************************************************/
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`Server running on https://34.16.120.118:${PORT}`);
|
console.log(`Server running on port ${PORT}`);
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,6 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors';
|
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
@ -30,7 +29,7 @@ dotenv.config({ path: envPath });
|
|||||||
const apiBase = process.env.APTIVA_API_BASE || "http://localhost:5002/api";
|
const apiBase = process.env.APTIVA_API_BASE || "http://localhost:5002/api";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PREMIUM_PORT || 5002;
|
const PORT = process.env.SERVER3_PORT || 5002;
|
||||||
const { getDocument } = pkg;
|
const { getDocument } = pkg;
|
||||||
const bt = "`".repeat(3);
|
const bt = "`".repeat(3);
|
||||||
|
|
||||||
@ -45,14 +44,55 @@ function internalFetch(req, url, opts = {}) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 2) Basic middlewares
|
// 2) Basic middlewares
|
||||||
app.use(helmet());
|
app.use(helmet({ contentSecurityPolicy:false, crossOriginEmbedderPolicy:false }));
|
||||||
app.use(express.json({ limit: '5mb' }));
|
app.use(express.json({ limit: '5mb' }));
|
||||||
|
|
||||||
const allowedOrigins = ['https://dev1.aptivaai.com'];
|
/* ─── Require critical env vars ─────────────────────────────── */
|
||||||
app.use(cors({ origin: allowedOrigins, credentials: true }));
|
if (!process.env.CORS_ALLOWED_ORIGINS) {
|
||||||
|
console.error('FATAL CORS_ALLOWED_ORIGINS is not set');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!process.env.APTIVA_API_BASE) {
|
||||||
|
console.error('FATAL APTIVA_API_BASE is not set');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Allowed origins for CORS (comma-separated in env) ─────── */
|
||||||
|
const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS
|
||||||
|
.split(',')
|
||||||
|
.map(o => o.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
/* ─── Dynamic CORS middleware (matches server1 / server2) ────────────── */
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
const origin = req.headers.origin;
|
||||||
|
|
||||||
|
// A) whitelisted origins (credentials allowed)
|
||||||
|
if (origin && allowedOrigins.includes(origin)) {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', origin);
|
||||||
|
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||||
|
res.setHeader(
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
'Authorization, Content-Type, Accept, Origin, X-Requested-With, Access-Control-Allow-Methods'
|
||||||
|
);
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
|
|
||||||
|
// B) default permissive fallback (same as server2’s behaviour)
|
||||||
|
} else {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||||
|
res.setHeader(
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
'Authorization, Content-Type, Accept, Origin, X-Requested-With'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
return res.status(204).end();
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
// 3) Authentication middleware
|
// 3) Authentication middleware
|
||||||
const authenticatePremiumUser = (req, res, next) => {
|
const authenticatePremiumUser = (req, res, next) => {
|
||||||
|
5
docker-compose.dev.yml
Normal file
5
docker-compose.dev.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
services:
|
||||||
|
server1: { env_file: .env.dev }
|
||||||
|
server2: { env_file: .env.dev }
|
||||||
|
server3: { env_file: .env.dev }
|
||||||
|
nginx : { env_file: .env.dev }
|
@ -1,42 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
# ─── server1 ───
|
server1: { env_file: .env.prod }
|
||||||
server1:
|
server2: { env_file: .env.prod }
|
||||||
extends:
|
server3: { env_file: .env.prod }
|
||||||
file: docker-compose.yml
|
nginx : { env_file: .env.prod }
|
||||||
service: server1
|
|
||||||
env_file: [ ./env/prod.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- SALARY_DB=/app/data/salary_info.db
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/salary_info.db:/app/data/salary_info.db:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
|
|
||||||
# ─── server2 ───
|
|
||||||
server2:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.yml
|
|
||||||
service: server2
|
|
||||||
env_file: [ ./env/prod.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/salary_info.db:/home/jcoakley/aptiva-dev1-app/salary_info.db:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
|
|
||||||
# ─── server3 ───
|
|
||||||
server3:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.yml
|
|
||||||
service: server3
|
|
||||||
env_file: [ ./env/prod.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- TWILIO_ACCOUNT_SID=ACd700c6fb9f691ccd9ccab73f2dd4173d
|
|
||||||
- TWILIO_AUTH_TOKEN=fb8979ccb172032a249014c9c30eba80
|
|
||||||
- TWILIO_MESSAGING_SERVICE_SID=MGMGaa07992a9231c841b1bfb879649026d6
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db
|
|
@ -1,39 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
# ─── server1 ───
|
server1: { env_file: .env.staging }
|
||||||
server1:
|
server2: { env_file: .env.staging }
|
||||||
extends:
|
server3: { env_file: .env.staging }
|
||||||
file: docker-compose.yml
|
nginx : { env_file: .env.staging }
|
||||||
service: server1
|
|
||||||
env_file: [ ./env/staging.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- SALARY_DB=/app/data/salary_info.db
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/salary_info.db:/app/data/salary_info.db:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
|
|
||||||
# ─── server2 ───
|
|
||||||
server2:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.yml
|
|
||||||
service: server2
|
|
||||||
env_file: [ ./env/staging.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
- SALARY_DB=/home/jcoakley/aptiva-dev1-app/salary_info.db
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/salary_info.db:/home/jcoakley/aptiva-dev1-app/salary_info.db:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db
|
|
||||||
|
|
||||||
# ─── server3 ───
|
|
||||||
server3:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.yml
|
|
||||||
service: server3
|
|
||||||
env_file: [ ./env/staging.env ]
|
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
volumes:
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/public:/home/jcoakley/aptiva-dev1-app/public:ro
|
|
||||||
- /home/jcoakley/aptiva-dev1-app/user_profile.db:/home/jcoakley/aptiva-dev1-app/user_profile.db
|
|
@ -1,50 +1,45 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
server1:
|
server1:
|
||||||
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:prod-20250710
|
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server1:${IMG_TAG}
|
||||||
|
expose: ["${SERVER1_PORT}"]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
expose: ["5000"]
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:5000/healthz || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER1_PORT}/healthz || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
server2:
|
server2:
|
||||||
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:prod-20250710
|
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server2:${IMG_TAG}
|
||||||
|
expose: ["${SERVER2_PORT}"]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
expose: ["5001"]
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:5001/healthz || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER2_PORT}/healthz || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
server3:
|
server3:
|
||||||
build:
|
image: us-central1-docker.pkg.dev/aptivaai-dev/aptiva-repo/server3:${IMG_TAG}
|
||||||
context: .
|
expose: ["${SERVER3_PORT}"]
|
||||||
dockerfile: Dockerfile.server3
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
expose: ["5002"]
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
JWT_SECRET: gW4QsOu4AJA4MooIUC9ld2i71VbBovzV1INsaU6ftxYPrxLIeMq6/OY61j0X2RV7
|
|
||||||
TWILIO_ACCOUNT_SID: ACd700c6fb9f691ccd9ccab73f2dd4173d
|
|
||||||
TWILIO_AUTH_TOKEN: fb8979ccb172032a249014c9c30eba80
|
|
||||||
TWILIO_MESSAGING_SERVICE_SID: MGMGaa07992a9231c841b1bfb879649026d6
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:5002/healthz || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:${SERVER3_PORT}/healthz || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:1.25-alpine
|
image: nginx:1.25-alpine
|
||||||
command: ["nginx", "-g", "daemon off;"]
|
command: ["nginx","-g","daemon off;"]
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./build:/usr/share/nginx/html:ro # React build
|
- ./build:/usr/share/nginx/html:ro
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro # overwrite default
|
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- /etc/letsencrypt:/etc/letsencrypt:ro # certs
|
- /etc/letsencrypt:/etc/letsencrypt:ro
|
||||||
- ./empty:/etc/nginx/conf.d # hide default.conf
|
- ./empty:/etc/nginx/conf.d
|
||||||
depends_on: [server1, server2, server3]
|
depends_on: [server1, server2, server3]
|
||||||
|
@ -10,6 +10,7 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false);
|
const [showSessionExpiredMsg, setShowSessionExpiredMsg] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const apiUrl = process.env.REACT_APP_API_URL;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if the URL query param has ?session=expired
|
// Check if the URL query param has ?session=expired
|
||||||
@ -42,10 +43,10 @@ function SignIn({ setIsAuthenticated, setUser }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await fetch('https://dev1.aptivaai.com/api/signin', {
|
const resp = await fetch(`${apiUrl}/signin`, {
|
||||||
method : 'POST',
|
method : 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body : JSON.stringify({ username, password })
|
body : JSON.stringify(formData),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await resp.json(); // ← read ONCE
|
const data = await resp.json(); // ← read ONCE
|
||||||
|
Loading…
Reference in New Issue
Block a user