← back to Cypress Awards

backend/src/server.js

120 lines

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const path = require('path');
const routes = require('./api/routes');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      connectSrc: ["'self'", "http://45.61.58.125:3010", "https://cypresawards.com", "http://localhost:3000", "http://localhost:3010"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'", "https:"],
      imgSrc: ["'self'", "data:", "https:", "http:"],
      fontSrc: ["'self'", "https:", "data:"],
      objectSrc: ["'none'"],
      upgradeInsecureRequests: null
    }
  }
})); // Security headers
app.use(cors()); // Enable CORS
app.use(compression()); // Compress responses
app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));

// Serve static frontend files with cache control
const frontendPath = path.join(__dirname, '../../frontend/src');
app.use(express.static(frontendPath, {
  etag: false,
  lastModified: false,
  setHeaders: (res, path) => {
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
  }
}));

// Logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next();
});

// API routes
app.use('/api', routes);

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime()
  });
});

// Serve index.html for root path
app.get('/', (req, res) => {
  res.sendFile(path.join(frontendPath, 'index.html'));
});

// API info endpoint
app.get('/api', (req, res) => {
  res.json({
    name: 'CyPresAwards API',
    version: '1.0.0',
    description: 'API for searching non-profit organizations with cy pres statements',
    endpoints: {
      organizations: '/api/organizations',
      categories: '/api/categories',
      legalTopics: '/api/legal-topics',
      stats: '/api/stats',
      health: '/health'
    }
  });
});

// 404 handler - serve index.html for client-side routing
app.use((req, res) => {
  // If it's an API request, return JSON error
  if (req.path.startsWith('/api/')) {
    return res.status(404).json({
      success: false,
      error: 'Endpoint not found'
    });
  }
  // Otherwise serve index.html for SPA routing
  res.sendFile(path.join(frontendPath, 'index.html'));
});

// Error handler
app.use((err, req, res, next) => {
  console.error('Server error:', err);
  res.status(500).json({
    success: false,
    error: process.env.NODE_ENV === 'development' ? err.message : 'Internal server error'
  });
});

// Start server
app.listen(PORT, () => {
  console.log(`\n🚀 CyPresAwards API Server`);
  console.log(`📡 Server running on http://localhost:${PORT}`);
  console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
  console.log(`\nAvailable endpoints:`);
  console.log(`  GET  /api/organizations`);
  console.log(`  GET  /api/organizations/:id`);
  console.log(`  GET  /api/categories`);
  console.log(`  GET  /api/legal-topics`);
  console.log(`  GET  /api/stats`);
  console.log(`  GET  /health\n`);
});

module.exports = app;