Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: CI/CD Pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
# Backend Tests and Linting
backend-test:
name: Backend Tests
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: hydranthub_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: backend/package-lock.json

- name: Install backend dependencies
working-directory: ./backend
run: npm ci

- name: Run ESLint
working-directory: ./backend
run: npm run lint

- name: Run backend tests
working-directory: ./backend
run: npm test
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/hydranthub_test
JWT_SECRET: test_secret_key
NODE_ENV: test

# Frontend Tests and Linting
frontend-test:
name: Frontend Tests
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install frontend dependencies
working-directory: ./frontend
run: npm ci

- name: Run ESLint
working-directory: ./frontend
run: npm run lint

- name: Build frontend
working-directory: ./frontend
run: npm run build
env:
VITE_API_URL: http://localhost:5000

# Security Audit
security-audit:
name: Security Audit
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Audit backend dependencies
working-directory: ./backend
run: npm audit --audit-level=moderate
continue-on-error: true

- name: Audit frontend dependencies
working-directory: ./frontend
run: npm audit --audit-level=moderate
continue-on-error: true

# Deploy to Railway (Backend) - Only on main branch
deploy-backend:
name: Deploy Backend to Railway
needs: [backend-test, security-audit]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Deploy to Railway
run: |
echo "Backend deployment to Railway would happen here"
echo "Configure Railway CLI or use Railway GitHub integration"
# Uncomment and configure when ready:
# env:
# RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}

# Deploy to Netlify (Frontend) - Only on main branch
deploy-frontend:
name: Deploy Frontend to Netlify
needs: [frontend-test, security-audit]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
working-directory: ./frontend
run: npm ci

- name: Build frontend
working-directory: ./frontend
run: npm run build
env:
VITE_API_URL: ${{ secrets.VITE_API_URL }}

- name: Deploy to Netlify
uses: netlify/actions/cli@master
with:
args: deploy --dir=frontend/dist --prod
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
107 changes: 107 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Changelog

All notable changes to the HydrantHub project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- Organization context middleware (`backend/middleware/orgContext.js`) to eliminate repetitive database queries
- GitHub Actions CI/CD workflow for automated testing, linting, and deployment
- Enhanced Vite configuration with code splitting and production optimizations
- API proxy configuration in Vite for seamless development experience
- Comprehensive error logging with timestamps and request context
- Graceful shutdown handlers for database connections
- Environment variable configuration for CORS origins

### Changed
- **BREAKING**: CORS configuration now uses `ALLOWED_ORIGINS` environment variable instead of hardcoded URLs
- Refactored maintenance routes to use new organization context middleware
- Moved all inline maintenance endpoints from `server.js` to `routes/maintenance.js`
- Improved error handler with detailed server-side logging
- Enhanced startup logging with emoji indicators and health check status
- Updated `.env.example` with `ALLOWED_ORIGINS` configuration and production examples

### Removed
- Duplicate `/api/tests` route (consolidated to `/api/flow-tests` only)
- Inline maintenance route handlers from `server.js` (moved to dedicated routes file)
- Repetitive organization ID queries across maintenance endpoints
- Hardcoded CORS whitelist from server configuration

### Fixed
- Code duplication in maintenance endpoints requiring organization validation
- Security concern with hardcoded production URLs in server code
- Missing error context in production error logs
- Lack of graceful shutdown for database connections

### Security
- Moved CORS origins to environment variables for better security
- Disabled sourcemaps in production builds
- Added security audit step to CI/CD pipeline
- Enhanced error handling to prevent information leakage in production

### Performance
- Implemented manual chunk splitting in Vite for better caching
- Separated vendor, UI, maps, and charts into dedicated bundles
- Optimized dependency pre-bundling in Vite
- Reduced server.js file size by ~43% (11.4KB β†’ 6.6KB)

## Migration Guide

### Environment Variables

If you're updating from a previous version, update your `.env` file:

**Before:**
```bash
CORS_ORIGIN=http://localhost:3000,https://yourdomain.com
```

**After:**
```bash
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
```

### API Routes

The `/api/tests` route has been removed. Use `/api/flow-tests` instead:

**Before:**
```javascript
fetch('/api/tests') // ❌ No longer available
```

**After:**
```javascript
fetch('/api/flow-tests') // βœ… Correct
```

### Custom Middleware

If you have custom routes that need organization context, use the new middleware:

```javascript
const { authenticateToken } = require('./middleware/auth');
const { attachOrgContext } = require('./middleware/orgContext');

router.get('/my-route', authenticateToken, attachOrgContext, (req, res) => {
// Organization ID is now available as req.organizationId
const orgId = req.organizationId;
// No need to query the database for it!
});
```

## [1.0.0] - 2025-11-18

### Initial Release
- Fire hydrant flow testing and management platform
- NFPA 291 compliant flow test calculations
- Multi-tenant organization support
- Maintenance tracking and compliance scheduling
- GIS mapping with Leaflet integration
- User authentication and role-based access control
- RESTful API with PostgreSQL database
- React frontend with Material-UI
- Deployed on Railway (backend) and Netlify (frontend)
8 changes: 7 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ DATABASE_URL=postgresql://username:password@host:port/database
# Server Configuration
PORT=5000
NODE_ENV=development
CORS_ORIGIN=http://localhost:3000,http://localhost:5173,https://yourdomain.com

# CORS Configuration
# Comma-separated list of allowed origins
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://yourdomain.com

# Production example:
# ALLOWED_ORIGINS=https://hydranthub.tridentsys.ca,https://app.tridentsys.ca,https://stunning-cascaron-f49a60.netlify.app

# JWT Configuration
# Generate a secure random string for production:
Expand Down
35 changes: 35 additions & 0 deletions backend/middleware/orgContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { db } = require('../config/database');

/**
* Middleware to attach organization context to request
* Eliminates repetitive database queries across routes
*
* Usage: app.use('/api/protected-route', authenticateToken, attachOrgContext, routeHandler);
*/
async function attachOrgContext(req, res, next) {
try {
if (!req.user || !req.user.userId) {
return res.status(401).json({ error: 'Authentication required' });
}

const result = await db.query(
'SELECT organization_id, role FROM users WHERE id = $1',
[req.user.userId]
);

if (!result.rows[0]) {
return res.status(404).json({ error: 'User not found' });
}

// Attach organization context to request object
req.organizationId = result.rows[0].organization_id;
req.userRole = result.rows[0].role;

next();
} catch (error) {
console.error('Organization context error:', error);
res.status(500).json({ error: 'Failed to retrieve organization context' });
}
}

module.exports = { attachOrgContext };
Loading
Loading