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:
Mikael Westöö
2026-01-23 23:26:57 +01:00
parent d6523bc4b1
commit ac21e428a5
6 changed files with 370 additions and 33 deletions

View File

@@ -6,17 +6,18 @@ const multer = require('multer');
const path = require('path');
const cookieParser = require('cookie-parser');
const fs = require('fs');
const config = require('./config');
const app = express();
const PORT = process.env.PORT || 3000;
const PORT = config.app.port;
// PostgreSQL connection
const pool = new Pool({
host: process.env.DB_HOST || 'postgres',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'recruitment',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
host: config.database.host,
port: config.database.port,
database: config.database.name,
user: config.database.user,
password: config.database.password,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
@@ -64,11 +65,11 @@ app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(cookieParser());
app.use(session({
secret: process.env.SESSION_SECRET || 'recruitment-site-secret-key-change-in-production',
secret: config.app.sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set to true if using HTTPS
secure: config.app.nodeEnv === 'production', // Automatically enable for production
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
@@ -106,19 +107,16 @@ app.use((req, res, next) => {
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
fileSize: config.features.maxCvSizeMB * 1024 * 1024,
},
fileFilter: (req, file, cb) => {
const allowedTypes = /pdf|doc|docx/;
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = allowedTypes.test(file.mimetype) ||
file.mimetype === 'application/msword' ||
file.mimetype === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
const allowedTypes = config.features.allowedCvTypes;
const isAllowed = allowedTypes.includes(file.mimetype);
if (mimetype && extname) {
if (isAllowed) {
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
// ============================================
// 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
app.get('/api/jobs', async (req, res) => {
try {