Spam Shield is a Node.js application designed to provide a REST API for spam detection and IP reputation management. The application ships a static HTML front-end that consumes the same cookie-authenticated API endpoints.
- REST API: Submit messages for scoring, retrieve spam metrics, check IP address reputation.
- Web Interface (Static HTML): Lightweight static pages (no heavy SPA) for basic admin and stats.
- Role-Based Access:
userandadministratorroles (seeded) with groundwork for expansion; first user auto-promoted toadministratorin development. - Licence Management: Per-user licence (one-to-one) supporting
unmeteredordaily-meteredtypes with UTC daily reset time. - Authentication Workflow: Email verification required before login; password reset flow; API key issuance. Verification emails use Handlebars templates in
src/email-templates. - Unified Data Layer: All models use Knex for queries; no direct driver calls scattered through code.
- Auto Migrate & Seed (dev): On startup in development, pending migrations are applied and baseline roles ensured.
- Database: MySQL/MariaDB supported through
mysql2driver. - Tooling: ESLint (flat config) enforcing brace style & quality rules; Prettier for formatting; Swagger UI at
/doc/apiwith raw spec at/api-docs.json(alias/doc/api-docs.json).
The project is organized into several key directories:
src: Contains the main application code, including server setup, routes, controllers, services, models, and static front-end assets.test: Contains unit tests for the API and services.docs: Documentation for the API and other components..env.sample: Template for environment variables.package.json: Configuration for npm dependencies and scripts.
-
Clone the Repository:
git clone <repository-url> cd spam-shield -
Install Dependencies:
npm install -
Run Migrations & Seeds (optional manual run): In development, startup will attempt to create the database if missing, apply migrations, and ensure baseline roles automatically. To run manually:
npm run migrate:latest npm run seed:run -
Configure Environment Variables: Copy
.env.sampleto.envand update the values as needed. -
Run in Development:
# Starts the server with nodemon and watches assets (JS/CSS) with esbuild npm run dev- Dev mode auto-runs pending DB migrations and seeds baseline data if needed.
- Asset sourcemaps are enabled while watching.
- Default port is
8080unless overridden byLISTEN_PORTin.env.
-
Access the Web Interface: Open your browser and navigate to
http://localhost:8080(or your configured port) to access the web UI.
For production, use PM2 in cluster mode to run multiple instances with load balancing:
-
Copy the PM2 config template:
cp ecosystem.config.js.sample ecosystem.config.js
-
Edit
ecosystem.config.jsand update:- Database credentials (
DB_HOST,DB_USER,DB_PASS,DB_NAME) - Session secret (
SESSION_SECRET) - SMTP settings for email verification
APP_BASE_URLfor your domain
- Database credentials (
-
Build production assets:
npm run build:dist
-
Start with PM2:
pm2 start ecosystem.config.js --env production
-
Useful PM2 commands:
pm2 status # Check cluster status pm2 logs spam-shield # View logs pm2 reload spam-shield # Zero-downtime reload pm2 stop spam-shield # Stop all instances pm2 restart spam-shield # Restart all instances
Note: ecosystem.config.js is gitignored. Use ecosystem.config.js.sample as a template for each deployment environment.
| Action | Command |
|---|---|
| Create new migration | npm run migrate:make -- <name> |
| Apply latest migrations | npm run migrate:latest |
| Roll back last batch | npm run migrate:rollback |
| Run seeds (manual) | npm run seed:run |
Current key tables:
roles(id, name, created_at)users(id, email, password_hash, status_slug, created_at, updated_at)user_statuses(status_slug, description)(seeded:pending,active)user_roles(user_id, role_id, created_at)(composite PK: user_id + role_id)licences(id, user_id UNIQUE, licence_type ENUM('unmetered','daily-metered'), daily_reset_time_utc TIME NULL, created_at, updated_at)messages(id, content, created_at, updated_at)api_keys(id, user_id, label, api_key_hash, created_at)password_resets(id, user_id, reset_token_hash, expires_at, used_at, created_at)user_email_verifications(id, user_id, token_hash, expires_at, created_at)web_sessions(express-session store, created automatically if missing)
Licence rules:
unmetered:daily_reset_time_utcmust be NULL.daily-metered: must providedaily_reset_time_utc(HH:MM or HH:MM:SS UTC). Validation lives insrc/utils/validators.js(seevalidateLicence).
During development startup the server runs migrations, ensures baseline role rows (user, administrator), and leaves existing user-role assignments untouched.
All models (messages, users, roles, licences) use Knex (src/db/knex.js). User-role association helpers live in userModel (assignRole, removeRole, getRoles).
For detailed information on the API endpoints, please refer to the REST API Documentation. All endpoints are versioned under the /api/v3 namespace (e.g. /api/v3/messages, /api/v3/ip-reputation, /api/v3/auth/*).
Registration does not log a user in; they remain in pending status until the email verification link is consumed. Attempts to login before verification return a specific error code.
Contributions are welcome! Please submit a pull request or open an issue for any enhancements or bug fixes.
See CHANGELOG.md for notable changes. The 0.1.0 release marks the initial authentication workflow (email verification gating), API scaffolding, and tooling baseline.
This project is licensed under the MIT License. See the LICENSE file for more details.
The build pipeline is now unified under build.js with an optional --dist flag. It uses esbuild for JS/CSS bundling and (when --dist is present) packages a production-ready dist/ directory with minified HTML.
| Type | Source | Dev/Build Output | Dist Output |
|---|---|---|---|
| Global CSS | public/css/index.css |
public/build/bundle.css |
dist/build/bundle.css |
| Global JS | public/js/index.js |
public/build/bundle.js |
dist/build/bundle.js |
| Page JS Bundles | public/js/*.js |
public/build/*.bundle.js |
dist/build/*.bundle.js |
| HTML | public/**/*.html |
served directly | Minified in dist/**/*.html |
| Fonts/Icons | referenced via CSS | copied by esbuild file loader | copied into dist/build/ |
| Manifest | generated by build | public/build/manifest.json |
dist/manifest.json (hashed assets) |
# Clean all build artifacts (dist and public/build)
npm run clean
# Build JS/CSS bundles into public/build (no HTML minification)
npm run build # now uses scripts/build.js
# Full production build: bundles + minified HTML packaged into dist/
npm run build:dist # passes --dist to scripts/build.js
# Development watch: nodemon server + esbuild watch with sourcemaps
npm run dev
# Optional sanity check for HTML script order
npm run verify:htmlnpm run devenables esbuild watch mode (--watch) with sourcemaps.npm run build(viascripts/build.js) minifies bundles (no sourcemaps) and writespublic/build/manifest.json.npm run build:dist(same script with--dist) performs a clean build then invokes dist packaging, producing hasheddist/manifest.jsonand minified HTML.- The dist step excludes source directories (
public/js,public/css) and only copies already-built assets plus selective root files (favicon, flags, etc.). Theme is applied server-side: we setdata-bs-themeon the<html>element based on thethemecookie (light/dark/auto) to avoid any flash-of-incorrect-theme before CSS loads.
| Scenario | Command |
|---|---|
| Local iteration | npm run dev |
| CI build (artifacts only) | npm run build |
| Release packaging | npm run build:dist |
| Reset build artifacts | npm run clean |
- Add a
--no-minifyflag for fast benchmarking builds. - Emit content-hashed filenames (e.g.
bundle.<hash>.js) for longer-term caching—current approach uses manifest hashes only. - Integrate a lightweight image optimization step (none performed presently).
Email templates are rendered with Handlebars:
- Templates live in
src/email-templates/*.hbs(e.g.verify-email.hbs). - Rendering and caching handled by
src/services/emailTemplates.js. - To add a template:
- Create
src/email-templates/<name>.hbs. - Call
mailerService.sendTemplate(to, subject, '<name>', locals). - Provide any required dynamic values in
locals.
- Create
Handlebars escapes values by default; if you need raw HTML, use the triple-stash syntax ({{{rawHtml}}}) cautiously.
-
Build production assets and HTML:
npm run build:dist
This creates
dist/with:- Minified HTML (mirrors
public/structure) dist/build/*optimized JS/CSS and fontsdist/manifest.jsonwith simple content hashes
- Minified HTML (mirrors
-
Start the app (production mode):
npm start
The server serves static files from
dist/whenNODE_ENV=productionanddist/exists; otherwise it servespublic/.