A lightweight zero‑dependency static file server that powers the AI Awareness for the Workplace interactive course. It serves public assets, protects a private folder, provides a health‑check endpoint, basic security headers, environment‑driven configuration, and simple in‑memory rate limiting.
- Prerequisites
- Running locally
- Docker
- Testing
- CI/CD (GitHub Actions)
- Security & Rate limiting
- Architecture diagram
- Exam navigation guard
- Contributing
- License
- Node.js >= 16 (LTS recommended)
- npm (bundled with Node)
- Optional: Docker & Docker‑Compose for containerised workflow
# Install dependencies
npm ci
# Start the server (development mode)
npm start
# The server listens on http://127.0.0.1:3000 by default.
# Override with environment variables (see .env.example).To run the server in test mode (exposes it for Jest/Supertest):
NODE_ENV=test npm testA multi‑stage Dockerfile is provided for production builds.
# Build the image
docker build -t ai-awareness-course .
# Run the container
docker run -p 3000:3000 -e PORT=3000 ai-awareness-courseOr use Docker‑Compose for a one‑command startup:
docker-compose up --buildThe project uses Jest with two projects:
- node – runs
test/server.test.jsandtest/rate.test.js. - jsdom – runs UI‑related tests such as
test/app.test.js.
npm testAll test suites should pass (2 suites, 4 tests).
The workflow .github/workflows/ci.yml automatically:
- Checks out the repository.
- Sets up Node.js (v20.x).
- Installs dependencies (
npm ci). - Runs the full test suite (
npm test). - Builds a Docker image (
docker build).
- Headers:
X-Content-Type-Options,X-Frame-Options,X-XSS-Protection,Content‑Security‑Policy. - Rate limiting: Simple in‑memory limit of 100 requests per minute per IP. Exceeding the limit returns
429 Too Many Requests. - Environment variables can be loaded from a
.envfile (no external dependencies).
flowchart TD
A[Client Request] --> B{Method}
B -->|GET/HEAD| C[Parse URL]
C --> D{Path}
D -->|/health| E[Health‑check JSON]
D -->|/private/*| F[PRIVATE_ROOT]
D -->|/*| G[PUBLIC_ROOT]
F --> H[safeJoin()]
G --> H
H --> I[fs.stat & readFile]
I --> J{Exists?}
J -->|file| K[Send file with correct MIME]
J -->|dir| L[Try index.html or SPA fallback]
J -->|no| M[404 Not Found]
style A fill:#f9f,stroke:#333,stroke-width:2px
style E fill:#bbf,stroke:#333,stroke-width:2px
The client‑side guard (guardExamAccess) ensures a learner cannot access the exam page unless all modules in the current track are completed. This logic is covered by unit tests in test/app.test.js.
The Continue button now points to the first unfinished module in the current track. When all modules are completed the continue bar is hidden. This replaces the previous behavior that used state.lastModule. The UI updates dynamically via updateContinueBar() in public/js/app.js.
The client‑side guard (guardExamAccess) ensures a learner cannot access the exam page unless all modules in the current track are completed. The logic lives in public/js/app.js and is covered by unit tests in test/app.test.js.
- Fork the repository
- Create a feature branch
- Write tests for your changes
- Ensure
npm testpasses - Open a Pull Request
This project is UNLICENSED – private use only.