Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd31141d4e | ||
| 8073094760 | |||
|
|
33a1e146ab | ||
| 4f8216db77 | |||
|
|
42d605d19f | ||
| 749350df7f | |||
|
|
ac085100fe | ||
| ce4ecd1268 | |||
|
|
a57cfc396b | ||
| 987badbf8d |
@@ -94,7 +94,8 @@
|
||||
"mcp__filesystem__edit_file",
|
||||
"Bash(timeout 300 tail:*)",
|
||||
"mcp__filesystem__list_allowed_directories",
|
||||
"mcp__memory__add_observations"
|
||||
"mcp__memory__add_observations",
|
||||
"Bash(ssh:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,3 +37,4 @@ test-output.txt
|
||||
Thumbs.db
|
||||
.claude
|
||||
nul
|
||||
tmpclaude*
|
||||
|
||||
@@ -71,7 +71,8 @@ module.exports = {
|
||||
exp_backoff_restart_delay: 100,
|
||||
min_uptime: '10s',
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
NODE_ENV: 'staging',
|
||||
PORT: 3002,
|
||||
WORKER_LOCK_DURATION: '120000',
|
||||
...sharedEnv,
|
||||
},
|
||||
@@ -89,7 +90,7 @@ module.exports = {
|
||||
exp_backoff_restart_delay: 100,
|
||||
min_uptime: '10s',
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
NODE_ENV: 'staging',
|
||||
...sharedEnv,
|
||||
},
|
||||
},
|
||||
@@ -106,7 +107,7 @@ module.exports = {
|
||||
exp_backoff_restart_delay: 100,
|
||||
min_uptime: '10s',
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
NODE_ENV: 'staging',
|
||||
...sharedEnv,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# HTTPS Server Block (main)
|
||||
server {
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
server_name flyer-crawler-test.projectium.com;
|
||||
|
||||
# SSL Configuration (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/flyer-crawler-test.projectium.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/flyer-crawler-test.projectium.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Allow large file uploads (e.g., for flyers)
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Root directory for built application files
|
||||
root /var/www/flyer-crawler-test.projectium.com;
|
||||
index index.html;
|
||||
|
||||
# Deny access to all dotfiles
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Coverage report (must come before generic location /)
|
||||
location /coverage/ {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
# SPA fallback for React Router
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Reverse proxy for backend API
|
||||
location /api/ {
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
proxy_pass http://localhost:3002;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
# Correct MIME type for .mjs files
|
||||
location ~ \.mjs$ {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/javascript;
|
||||
}
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
|
||||
# HTTP to HTTPS Redirect
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name flyer-crawler-test.projectium.com;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.9.104",
|
||||
"version": "0.9.109",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.9.104",
|
||||
"version": "0.9.109",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.9.104",
|
||||
"version": "0.9.109",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -128,7 +128,7 @@ const workerSchema = z.object({
|
||||
* Server configuration schema.
|
||||
*/
|
||||
const serverSchema = z.object({
|
||||
nodeEnv: z.enum(['development', 'production', 'test']).default('development'),
|
||||
nodeEnv: z.enum(['development', 'production', 'test', 'staging']).default('development'),
|
||||
port: intWithDefault(3001),
|
||||
frontendUrl: z.string().url().optional(),
|
||||
baseUrl: z.string().optional(),
|
||||
@@ -262,8 +262,9 @@ function parseConfig(): EnvConfig {
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
// In test environment, throw instead of exiting to allow test frameworks to catch
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
// In test/staging environment, throw instead of exiting to allow test frameworks to catch
|
||||
// and to provide better visibility into config errors during staging deployments
|
||||
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
@@ -318,6 +319,24 @@ export const isTest = config.server.nodeEnv === 'test';
|
||||
*/
|
||||
export const isDevelopment = config.server.nodeEnv === 'development';
|
||||
|
||||
/**
|
||||
* Returns true if running in staging environment.
|
||||
*/
|
||||
export const isStaging = config.server.nodeEnv === 'staging';
|
||||
|
||||
/**
|
||||
* Returns true if running in a test-like environment (test or staging).
|
||||
* Use this for behaviors that should be shared between unit/integration tests
|
||||
* and the staging deployment server, such as:
|
||||
* - Using mock AI services (no GEMINI_API_KEY required)
|
||||
* - Verbose error logging
|
||||
* - Fallback URL handling
|
||||
*
|
||||
* Do NOT use this for security bypasses (auth, rate limiting) - those should
|
||||
* only be active in NODE_ENV=test, not staging.
|
||||
*/
|
||||
export const isTestLikeEnvironment = isTest || isStaging;
|
||||
|
||||
/**
|
||||
* Returns true if SMTP is configured (all required fields present).
|
||||
*/
|
||||
|
||||
@@ -161,9 +161,12 @@ export const errorHandler = (err: Error, req: Request, res: Response, next: Next
|
||||
`Unhandled API Error (ID: ${errorId})`,
|
||||
);
|
||||
|
||||
// Also log to console in test environment for visibility in test runners
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
console.error(`--- [TEST] UNHANDLED ERROR (ID: ${errorId}) ---`, err);
|
||||
// Also log to console in test/staging environments for visibility in test runners
|
||||
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') {
|
||||
console.error(
|
||||
`--- [${process.env.NODE_ENV?.toUpperCase()}] UNHANDLED ERROR (ID: ${errorId}) ---`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
|
||||
// In production, send a generic message to avoid leaking implementation details.
|
||||
|
||||
@@ -239,10 +239,13 @@ router.post(
|
||||
'Handling /upload-and-process',
|
||||
);
|
||||
|
||||
// Fix: Explicitly clear userProfile if no auth header is present in test env
|
||||
// Fix: Explicitly clear userProfile if no auth header is present in test/staging env
|
||||
// This prevents mockAuth from injecting a non-existent user ID for anonymous requests.
|
||||
let userProfile = req.user as UserProfile | undefined;
|
||||
if (process.env.NODE_ENV === 'test' && !req.headers['authorization']) {
|
||||
if (
|
||||
(process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') &&
|
||||
!req.headers['authorization']
|
||||
) {
|
||||
userProfile = undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,11 @@ export class AIService {
|
||||
this.logger = logger;
|
||||
this.logger.info('---------------- [AIService] Constructor Start ----------------');
|
||||
|
||||
const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.VITEST_POOL_ID;
|
||||
// Use mock AI in test and staging environments (no real API calls, no GEMINI_API_KEY needed)
|
||||
const isTestEnvironment =
|
||||
process.env.NODE_ENV === 'test' ||
|
||||
process.env.NODE_ENV === 'staging' ||
|
||||
!!process.env.VITEST_POOL_ID;
|
||||
|
||||
if (aiClient) {
|
||||
this.logger.info(
|
||||
|
||||
@@ -19,7 +19,8 @@ import path from 'path';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isTest = process.env.NODE_ENV === 'test';
|
||||
const isDevelopment = !isProduction && !isTest;
|
||||
const isStaging = process.env.NODE_ENV === 'staging';
|
||||
const isDevelopment = !isProduction && !isTest && !isStaging;
|
||||
|
||||
// Determine log directory based on environment
|
||||
// In production/test, use the application directory's logs folder
|
||||
|
||||
@@ -15,9 +15,9 @@ export function getBaseUrl(logger: Logger): string {
|
||||
let baseUrl = (process.env.FRONTEND_URL || process.env.BASE_URL || '').trim();
|
||||
if (!baseUrl || !baseUrl.startsWith('http')) {
|
||||
const port = process.env.PORT || 3000;
|
||||
// In test/development, use http://localhost. In production, this should never be reached.
|
||||
// In test/staging/development, use http://localhost. In production, this should never be reached.
|
||||
const fallbackUrl =
|
||||
process.env.NODE_ENV === 'test'
|
||||
process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging'
|
||||
? `http://localhost:${port}`
|
||||
: `http://example.com:${port}`;
|
||||
if (baseUrl) {
|
||||
@@ -39,4 +39,4 @@ export function getBaseUrl(logger: Logger): string {
|
||||
}
|
||||
|
||||
return finalUrl;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user