Convert to template with dynamic configuration
Add comprehensive environment-based configuration system: - Created config.js module to centralize all environment variables - Updated server.js to use config module for all settings - Added /api/config endpoint to expose company info to frontend - Created init.js to dynamically inject config into HTML pages - Updated .env.example with comprehensive configuration options - Added data attributes to index.html for dynamic content - Updated Dockerfile to include config.js This allows users to customize: - Company name, tagline, and description - Branding colors (primary, accent, dark) - Contact information (email, phone, address, hours) - Social media links - About page content - Services offered 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
110
.env.example
110
.env.example
@@ -1,16 +1,110 @@
|
|||||||
|
# ===================================
|
||||||
|
# AI Recruitment Site - Configuration
|
||||||
|
# ===================================
|
||||||
|
# Copy this file to .env and customize for your company
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Company Information
|
||||||
|
# ===================================
|
||||||
|
COMPANY_NAME=Your Recruitment Firm
|
||||||
|
COMPANY_TAGLINE=Finding the Perfect Match for Your Career
|
||||||
|
COMPANY_DESCRIPTION=We specialize in connecting talented professionals with exceptional opportunities across various industries.
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Company Branding (Colors)
|
||||||
|
# ===================================
|
||||||
|
# Primary brand color (e.g., #2563EB for blue)
|
||||||
|
PRIMARY_COLOR=#2563EB
|
||||||
|
# Accent/success color (e.g., #059669 for green)
|
||||||
|
ACCENT_COLOR=#059669
|
||||||
|
# Dark/secondary color (e.g., #1E293B)
|
||||||
|
DARK_COLOR=#1E293B
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Contact Information
|
||||||
|
# ===================================
|
||||||
|
CONTACT_EMAIL=info@yourcompany.com
|
||||||
|
CONTACT_PHONE=+1 (555) 123-4567
|
||||||
|
CONTACT_ADDRESS=123 Business St, Suite 100, City, State 12345
|
||||||
|
|
||||||
|
# Social Media Links (leave empty to hide)
|
||||||
|
SOCIAL_LINKEDIN=https://linkedin.com/company/yourcompany
|
||||||
|
SOCIAL_TWITTER=https://twitter.com/yourcompany
|
||||||
|
SOCIAL_FACEBOOK=
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Deployment Configuration
|
||||||
|
# ===================================
|
||||||
|
# Your custom subdomain (e.g., 'yourname' becomes yourname.recruitai.startanaicompany.com)
|
||||||
|
SUBDOMAIN=yourname
|
||||||
|
|
||||||
|
# Your Gitea username and repository name
|
||||||
|
GITEA_USERNAME=your-gitea-username
|
||||||
|
GITEA_REPO_NAME=ai-recruit-site-template
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Application Settings
|
||||||
|
# ===================================
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# Session secret (will be auto-generated if empty)
|
||||||
|
SESSION_SECRET=
|
||||||
|
|
||||||
|
# ===================================
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
|
# ===================================
|
||||||
|
# PostgreSQL connection details
|
||||||
DB_HOST=postgres
|
DB_HOST=postgres
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=recruitment
|
DB_NAME=recruitment
|
||||||
DB_USER=postgres
|
DB_USER=postgres
|
||||||
DB_PASSWORD=changeme123
|
# Database password (will be auto-generated if empty)
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
# Application Configuration
|
# ===================================
|
||||||
PORT=3000
|
# API Tokens (for deployment script)
|
||||||
NODE_ENV=production
|
# ===================================
|
||||||
|
# These should be set as environment variables, NOT in this file
|
||||||
|
# COOLIFY_API_TOKEN=your-coolify-api-token
|
||||||
|
# GITEA_API_TOKEN=your-gitea-api-token
|
||||||
|
|
||||||
# Session Secret (CHANGE THIS IN PRODUCTION!)
|
# ===================================
|
||||||
SESSION_SECRET=your-very-secret-session-key-change-this-in-production
|
# Feature Configuration
|
||||||
|
# ===================================
|
||||||
|
# Maximum CV file size in MB
|
||||||
|
MAX_CV_SIZE_MB=5
|
||||||
|
|
||||||
# Optional: Application URL
|
# Allowed CV file types (comma-separated)
|
||||||
APP_URL=http://localhost:3000
|
ALLOWED_CV_TYPES=application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# About Page Content
|
||||||
|
# ===================================
|
||||||
|
ABOUT_MISSION=Our mission is to bridge the gap between exceptional talent and outstanding opportunities.
|
||||||
|
ABOUT_VISION=We envision a world where every professional finds their perfect career match.
|
||||||
|
ABOUT_VALUES=Integrity, Excellence, Innovation, Partnership
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Services Offered (comma-separated)
|
||||||
|
# ===================================
|
||||||
|
SERVICES_LIST=Executive Search,Contract Staffing,Permanent Placement,Career Consulting,Talent Assessment,Industry Expertise
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Contact Page Settings
|
||||||
|
# ===================================
|
||||||
|
# Email address where contact form submissions are sent
|
||||||
|
CONTACT_FORM_RECIPIENT=info@yourcompany.com
|
||||||
|
|
||||||
|
# Business hours
|
||||||
|
BUSINESS_HOURS=Monday - Friday: 9:00 AM - 6:00 PM
|
||||||
|
|
||||||
|
# ===================================
|
||||||
|
# Email Configuration (Optional)
|
||||||
|
# ===================================
|
||||||
|
# If you want to send email notifications
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM=noreply@yourcompany.com
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ RUN npm install --production
|
|||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY server.js ./
|
COPY server.js ./
|
||||||
|
COPY config.js ./
|
||||||
COPY migrations ./migrations
|
COPY migrations ./migrations
|
||||||
|
|
||||||
# Copy public directory explicitly
|
# Copy public directory explicitly
|
||||||
|
|||||||
96
config.js
Normal file
96
config.js
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// Configuration module for AI Recruitment Site Template
|
||||||
|
// Centralizes all environment variable loading with sensible defaults
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Helper function to generate secure random strings
|
||||||
|
function generateSecret(length = 64) {
|
||||||
|
return crypto.randomBytes(length).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Company Information
|
||||||
|
const company = {
|
||||||
|
name: process.env.COMPANY_NAME || 'Your Recruitment Firm',
|
||||||
|
tagline: process.env.COMPANY_TAGLINE || 'Finding the Perfect Match for Your Career',
|
||||||
|
description: process.env.COMPANY_DESCRIPTION || 'We specialize in connecting talented professionals with exceptional opportunities across various industries.'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Branding Colors
|
||||||
|
const branding = {
|
||||||
|
primaryColor: process.env.PRIMARY_COLOR || '#2563EB',
|
||||||
|
accentColor: process.env.ACCENT_COLOR || '#059669',
|
||||||
|
darkColor: process.env.DARK_COLOR || '#1E293B'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contact Information
|
||||||
|
const contact = {
|
||||||
|
email: process.env.CONTACT_EMAIL || 'info@yourcompany.com',
|
||||||
|
phone: process.env.CONTACT_PHONE || '+1 (555) 123-4567',
|
||||||
|
address: process.env.CONTACT_ADDRESS || '123 Business St, Suite 100, City, State 12345',
|
||||||
|
businessHours: process.env.BUSINESS_HOURS || 'Monday - Friday: 9:00 AM - 6:00 PM'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Social Media Links
|
||||||
|
const social = {
|
||||||
|
linkedin: process.env.SOCIAL_LINKEDIN || '',
|
||||||
|
twitter: process.env.SOCIAL_TWITTER || '',
|
||||||
|
facebook: process.env.SOCIAL_FACEBOOK || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Application Settings
|
||||||
|
const app = {
|
||||||
|
nodeEnv: process.env.NODE_ENV || 'development',
|
||||||
|
port: parseInt(process.env.PORT || '3000', 10),
|
||||||
|
sessionSecret: process.env.SESSION_SECRET || generateSecret()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Database Configuration
|
||||||
|
const database = {
|
||||||
|
host: process.env.DB_HOST || 'localhost',
|
||||||
|
port: parseInt(process.env.DB_PORT || '5432', 10),
|
||||||
|
name: process.env.DB_NAME || 'recruitment',
|
||||||
|
user: process.env.DB_USER || 'postgres',
|
||||||
|
password: process.env.DB_PASSWORD || 'changeme123'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Feature Configuration
|
||||||
|
const features = {
|
||||||
|
maxCvSizeMB: parseInt(process.env.MAX_CV_SIZE_MB || '5', 10),
|
||||||
|
allowedCvTypes: (process.env.ALLOWED_CV_TYPES || 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document').split(',')
|
||||||
|
};
|
||||||
|
|
||||||
|
// About Page Content
|
||||||
|
const about = {
|
||||||
|
mission: process.env.ABOUT_MISSION || 'Our mission is to bridge the gap between exceptional talent and outstanding opportunities.',
|
||||||
|
vision: process.env.ABOUT_VISION || 'We envision a world where every professional finds their perfect career match.',
|
||||||
|
values: process.env.ABOUT_VALUES || 'Integrity, Excellence, Innovation, Partnership'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Services
|
||||||
|
const services = {
|
||||||
|
list: (process.env.SERVICES_LIST || 'Executive Search,Contract Staffing,Permanent Placement,Career Consulting,Talent Assessment,Industry Expertise').split(',')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Email Configuration
|
||||||
|
const email = {
|
||||||
|
smtpHost: process.env.SMTP_HOST || '',
|
||||||
|
smtpPort: parseInt(process.env.SMTP_PORT || '587', 10),
|
||||||
|
smtpUser: process.env.SMTP_USER || '',
|
||||||
|
smtpPassword: process.env.SMTP_PASSWORD || '',
|
||||||
|
smtpFrom: process.env.SMTP_FROM || 'noreply@yourcompany.com',
|
||||||
|
contactFormRecipient: process.env.CONTACT_FORM_RECIPIENT || contact.email
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export configuration object
|
||||||
|
module.exports = {
|
||||||
|
company,
|
||||||
|
branding,
|
||||||
|
contact,
|
||||||
|
social,
|
||||||
|
app,
|
||||||
|
database,
|
||||||
|
features,
|
||||||
|
about,
|
||||||
|
services,
|
||||||
|
email
|
||||||
|
};
|
||||||
@@ -5,11 +5,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Ryans Recruit Firm - Find Your Dream Job</title>
|
<title>Ryans Recruit Firm - Find Your Dream Job</title>
|
||||||
<link rel="stylesheet" href="/css/styles.css">
|
<link rel="stylesheet" href="/css/styles.css">
|
||||||
|
<script src="/js/init.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<nav class="container">
|
<nav class="container">
|
||||||
<div class="logo">Ryans Recruit Firm</div>
|
<div class="logo" data-company-name>Ryans Recruit Firm</div>
|
||||||
<ul class="nav-links">
|
<ul class="nav-links">
|
||||||
<li><a href="/" class="active">Home</a></li>
|
<li><a href="/" class="active">Home</a></li>
|
||||||
<li><a href="/about">About</a></li>
|
<li><a href="/about">About</a></li>
|
||||||
@@ -23,8 +24,8 @@
|
|||||||
|
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Your Career Success Is Our Mission</h1>
|
<h1 data-company-tagline>Your Career Success Is Our Mission</h1>
|
||||||
<p>Connecting talented professionals with leading companies worldwide</p>
|
<p data-company-description>Connecting talented professionals with leading companies worldwide</p>
|
||||||
<div style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem;">
|
<div style="display: flex; gap: 1rem; justify-content: center; margin-top: 2rem;">
|
||||||
<a href="/jobs" class="btn btn-primary btn-lg">Browse Jobs</a>
|
<a href="/jobs" class="btn btn-primary btn-lg">Browse Jobs</a>
|
||||||
<a href="/contact" class="btn btn-secondary btn-lg">Get in Touch</a>
|
<a href="/contact" class="btn btn-secondary btn-lg">Get in Touch</a>
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Why Choose Ryans Recruit Firm?</h2>
|
<h2>Why Choose <span data-company-name>Ryans Recruit Firm</span>?</h2>
|
||||||
<p>We're committed to finding the perfect match for both candidates and employers</p>
|
<p>We're committed to finding the perfect match for both candidates and employers</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -198,8 +199,8 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
<h3>Ryans Recruit Firm</h3>
|
<h3 data-company-name>Ryans Recruit Firm</h3>
|
||||||
<p style="color: rgba(255, 255, 255, 0.8);">
|
<p style="color: rgba(255, 255, 255, 0.8);" data-company-description>
|
||||||
Your trusted partner in career advancement and talent acquisition.
|
Your trusted partner in career advancement and talent acquisition.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,9 +228,9 @@
|
|||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
<h3>Contact</h3>
|
<h3>Contact</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Email: info@ryansrecruit.com</li>
|
<li>Email: <a href="mailto:info@ryansrecruit.com" data-contact-email>info@ryansrecruit.com</a></li>
|
||||||
<li>Phone: +1 (555) 123-4567</li>
|
<li>Phone: <a href="tel:+15551234567" data-contact-phone>+1 (555) 123-4567</a></li>
|
||||||
<li>Hours: Mon-Fri 9AM-6PM EST</li>
|
<li>Hours: <span data-business-hours>Mon-Fri 9AM-6PM EST</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
135
public/js/init.js
Normal file
135
public/js/init.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Initialize company configuration
|
||||||
|
// This file loads company configuration from the API and updates the page
|
||||||
|
|
||||||
|
let siteConfig = {};
|
||||||
|
|
||||||
|
async function loadSiteConfig() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/config');
|
||||||
|
siteConfig = await response.json();
|
||||||
|
|
||||||
|
// Update company information
|
||||||
|
updateCompanyInfo();
|
||||||
|
|
||||||
|
// Update branding (colors)
|
||||||
|
updateBranding();
|
||||||
|
|
||||||
|
// Update contact information
|
||||||
|
updateContactInfo();
|
||||||
|
|
||||||
|
// Update social media links
|
||||||
|
updateSocialLinks();
|
||||||
|
|
||||||
|
console.log('Site configuration loaded successfully');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load site configuration:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCompanyInfo() {
|
||||||
|
// Update company name
|
||||||
|
const companyNameElements = document.querySelectorAll('[data-company-name]');
|
||||||
|
companyNameElements.forEach(el => {
|
||||||
|
el.textContent = siteConfig.company.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update company tagline
|
||||||
|
const taglineElements = document.querySelectorAll('[data-company-tagline]');
|
||||||
|
taglineElements.forEach(el => {
|
||||||
|
el.textContent = siteConfig.company.tagline;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update company description
|
||||||
|
const descElements = document.querySelectorAll('[data-company-description]');
|
||||||
|
descElements.forEach(el => {
|
||||||
|
el.textContent = siteConfig.company.description;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update page title
|
||||||
|
const titleElement = document.querySelector('title');
|
||||||
|
if (titleElement && titleElement.textContent.includes('Ryans Recruit')) {
|
||||||
|
titleElement.textContent = titleElement.textContent.replace(/Ryans Recruit Firm|Ryans Recruit/g, siteConfig.company.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBranding() {
|
||||||
|
// Set CSS custom properties for branding colors
|
||||||
|
const root = document.documentElement;
|
||||||
|
root.style.setProperty('--primary-color', siteConfig.branding.primaryColor);
|
||||||
|
root.style.setProperty('--accent-color', siteConfig.branding.accentColor);
|
||||||
|
root.style.setProperty('--secondary-color', siteConfig.branding.darkColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContactInfo() {
|
||||||
|
// Update email
|
||||||
|
const emailElements = document.querySelectorAll('[data-contact-email]');
|
||||||
|
emailElements.forEach(el => {
|
||||||
|
if (el.tagName === 'A') {
|
||||||
|
el.href = `mailto:${siteConfig.contact.email}`;
|
||||||
|
}
|
||||||
|
el.textContent = siteConfig.contact.email;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update phone
|
||||||
|
const phoneElements = document.querySelectorAll('[data-contact-phone]');
|
||||||
|
phoneElements.forEach(el => {
|
||||||
|
if (el.tagName === 'A') {
|
||||||
|
el.href = `tel:${siteConfig.contact.phone}`;
|
||||||
|
}
|
||||||
|
el.textContent = siteConfig.contact.phone;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update address
|
||||||
|
const addressElements = document.querySelectorAll('[data-contact-address]');
|
||||||
|
addressElements.forEach(el => {
|
||||||
|
el.textContent = siteConfig.contact.address;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update business hours
|
||||||
|
const hoursElements = document.querySelectorAll('[data-business-hours]');
|
||||||
|
hoursElements.forEach(el => {
|
||||||
|
el.textContent = siteConfig.contact.businessHours;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSocialLinks() {
|
||||||
|
// Update LinkedIn
|
||||||
|
const linkedinElements = document.querySelectorAll('[data-social-linkedin]');
|
||||||
|
linkedinElements.forEach(el => {
|
||||||
|
if (siteConfig.social.linkedin) {
|
||||||
|
el.href = siteConfig.social.linkedin;
|
||||||
|
el.style.display = '';
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Twitter
|
||||||
|
const twitterElements = document.querySelectorAll('[data-social-twitter]');
|
||||||
|
twitterElements.forEach(el => {
|
||||||
|
if (siteConfig.social.twitter) {
|
||||||
|
el.href = siteConfig.social.twitter;
|
||||||
|
el.style.display = '';
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update Facebook
|
||||||
|
const facebookElements = document.querySelectorAll('[data-social-facebook]');
|
||||||
|
facebookElements.forEach(el => {
|
||||||
|
if (siteConfig.social.facebook) {
|
||||||
|
el.href = siteConfig.social.facebook;
|
||||||
|
el.style.display = '';
|
||||||
|
} else {
|
||||||
|
el.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configuration when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', loadSiteConfig);
|
||||||
|
} else {
|
||||||
|
loadSiteConfig();
|
||||||
|
}
|
||||||
42
server.js
42
server.js
@@ -6,17 +6,18 @@ const multer = require('multer');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = config.app.port;
|
||||||
|
|
||||||
// PostgreSQL connection
|
// PostgreSQL connection
|
||||||
const pool = new Pool({
|
const pool = new Pool({
|
||||||
host: process.env.DB_HOST || 'postgres',
|
host: config.database.host,
|
||||||
port: process.env.DB_PORT || 5432,
|
port: config.database.port,
|
||||||
database: process.env.DB_NAME || 'recruitment',
|
database: config.database.name,
|
||||||
user: process.env.DB_USER || 'postgres',
|
user: config.database.user,
|
||||||
password: process.env.DB_PASSWORD || 'postgres',
|
password: config.database.password,
|
||||||
max: 20,
|
max: 20,
|
||||||
idleTimeoutMillis: 30000,
|
idleTimeoutMillis: 30000,
|
||||||
connectionTimeoutMillis: 2000,
|
connectionTimeoutMillis: 2000,
|
||||||
@@ -64,11 +65,11 @@ app.use(express.json({ limit: '10mb' }));
|
|||||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(session({
|
app.use(session({
|
||||||
secret: process.env.SESSION_SECRET || 'recruitment-site-secret-key-change-in-production',
|
secret: config.app.sessionSecret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
cookie: {
|
cookie: {
|
||||||
secure: false, // Set to true if using HTTPS
|
secure: config.app.nodeEnv === 'production', // Automatically enable for production
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||||
}
|
}
|
||||||
@@ -106,19 +107,16 @@ app.use((req, res, next) => {
|
|||||||
const upload = multer({
|
const upload = multer({
|
||||||
storage: multer.memoryStorage(),
|
storage: multer.memoryStorage(),
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 5 * 1024 * 1024, // 5MB limit
|
fileSize: config.features.maxCvSizeMB * 1024 * 1024,
|
||||||
},
|
},
|
||||||
fileFilter: (req, file, cb) => {
|
fileFilter: (req, file, cb) => {
|
||||||
const allowedTypes = /pdf|doc|docx/;
|
const allowedTypes = config.features.allowedCvTypes;
|
||||||
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
|
const isAllowed = allowedTypes.includes(file.mimetype);
|
||||||
const mimetype = allowedTypes.test(file.mimetype) ||
|
|
||||||
file.mimetype === 'application/msword' ||
|
|
||||||
file.mimetype === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
||||||
|
|
||||||
if (mimetype && extname) {
|
if (isAllowed) {
|
||||||
return cb(null, true);
|
return cb(null, true);
|
||||||
}
|
}
|
||||||
cb(new Error('Only PDF, DOC, and DOCX files are allowed'));
|
cb(new Error('File type not allowed. Only PDF, DOC, and DOCX files are accepted.'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -135,6 +133,18 @@ const requireAuth = (req, res, next) => {
|
|||||||
// PUBLIC API ENDPOINTS
|
// PUBLIC API ENDPOINTS
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
|
// Get company configuration for frontend
|
||||||
|
app.get('/api/config', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
company: config.company,
|
||||||
|
branding: config.branding,
|
||||||
|
contact: config.contact,
|
||||||
|
social: config.social,
|
||||||
|
about: config.about,
|
||||||
|
services: config.services
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Get all active job postings
|
// Get all active job postings
|
||||||
app.get('/api/jobs', async (req, res) => {
|
app.get('/api/jobs', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user