Development¶
Stack¶
Node.js 22 (ES modules), Express 5, React 19 + Vite + Mantine for the GUI, Sequelize 6 on Postgres, Redis (node-redis), node-cron, yargs + table for the CLI, pino logging, Sentry, Jest + supertest.
config/ node-config defaults, env mapping, Sequelize migrations
scripts/ client scripts (tis-pull/push/cron), SQLite→Postgres migration, .env generator
src/api/ Express app: routes/, middleware/ (auth, acl, cache, error), public/ (landing page, /get scripts)
src/cli/ interactive CLI: index.js (shell, sign-in), commands/, ui.js (tables, messages), oidc.js
src/models/ Sequelize models: Source, Indicator, Tag, Token, Group (+ join tables)
src/services/ fetcher (fetch + parse + ingest), exporter, scheduler, groups, indicators, resync
src/helpers/ pure helpers (value detection, whitelist matching, netmask, tags, formatting)
src/worker.js daemon entry: scheduler + API; src/server.js API only
tests/ Jest tests
web/ web GUI: React + Vite + Mantine, own package.json; builds into src/api/public/app
Local setup¶
Needs Node.js 22 and Docker. Postgres and Redis run in containers (docker-compose.dev.yml, localhost only); the worker, CLI and GUI run from the checkout.
npm ci && npm --prefix web ci
npm run dev:setup # Postgres + Redis, migrations, demo data (_since v1.33.0_)
dev:setup seeds the admin token admin / dev-secret (or CONFIG_SEED_ADMIN_TOKEN if set) and npm run dev:seed loads demo data: a manual, a private import-only (demo-honeypot, own retention) and a feed source (demo-urlhaus, the public URLhaus host file, fetched on demand), tagged indicators spread over the last days, the group demo-customer (tag and age filters, whitelist source) and the guest token demo-guest scoped to it (secret printed once). It skips what already exists and refuses to run with NODE_ENV=production.
Then, in two terminals:
npm run dev:worker # scheduler + API on :3000, using the local Redis
npm run dev:web # GUI with hot reload: http://localhost:5173/app/ (proxies /api to :3000, or TIS_API_URL)
| Command | What it does |
|---|---|
npm run dev:services |
Starts Postgres (:5432) and Redis (:6379); TIS_DEV_PG_PORT / TIS_DEV_REDIS_PORT move them. |
npm run dev:services:down |
Stops them; the database stays in the tis-dev_tis-dev-db volume (docker volume rm to reset). |
npm run dev:seed |
Demo data, see above. |
npm run dev:worker |
The worker with CONFIG_REDIS_URL=redis://127.0.0.1:6379 (unless set). |
npm run dev |
API only, restarted on changes (pm2 watch) – no scheduler; without Redis sessions stay in memory. |
npm run cli |
The interactive CLI; NODE_ENV defaults to development, so no sign-in. |
npm run build:web |
Production build of the GUI into src/api/public/app (also done by the Dockerfile and CI). |
The API browser is at http://localhost:3000/api/. The defaults (postgres://tis:tis@127.0.0.1:5432/tis) match the dev services; with other ports set CONFIG_DB_URL / CONFIG_REDIS_URL. The production docker-compose.yml runs the registry image and needs the external proxy_public network – not meant for development.
Tests¶
- API tests mock the models (
jest.mock('../src/models/index.js')) and run the Express app via supertest. - Model and service tests use real models on in-memory SQLite:
setupTestDb()fromtests/helpers.js. - Integration tests in
tests/integration/run the real models, SQL and API against Postgres – whitelisting, exports, ingest, pruning, search. Run them withscripts/test-integration.sh(starts and removes a throwaway Postgres container; extra arguments go to Jest), ornpm run test:integrationwithCONFIG_DB_URLpointing to a database whose name containstest– they truncate every table and refuse other databases. CI runs them with a Postgres service.
Git hooks (husky): pre-commit runs npm run lint (prettier), pre-push runs npm test.
Migrations¶
Migrations are ES modules in config/migrations/ named YYYYMMDDHHMMSS-description.js, exporting up(queryInterface, Sequelize) and down(...). They run automatically when the container starts. Keep model definitions in src/models/ in sync, and say in the commit message when a migration is irreversible.
Commits, branches and releases¶
- Work on a branch (
fix/…,feat/…), open a merge request intodev;devis merged intomaster. - Commit messages follow Conventional Commits – semantic-release derives the version from them on
master:
| Type | Release |
|---|---|
fix(scope): … |
patch |
feat(scope): … |
minor |
BREAKING CHANGE: footer or feat!: |
major |
refactor, test, docs, chore, … |
none |
Reference issues in the footer (Closes #12). Issues, commits and code comments are in English.
CI/CD¶
.gitlab-ci.yml:
- test – every MR and push to
master:npm ci,npm test,npm run build:web; integration-tests runsnpm run test:integrationagainst a Postgres service. - release – on
master: semantic-release bumpspackage.json, writes thechore(release): x.y.zcommit, tagsvx.y.zand creates the GitLab release with notes. - build-image – on tags (and manual web pipelines): builds the Docker image and pushes
:<tag>and:latest(:dev-<sha>/:devfor manual runs) to the GitLab registry.
GitLab CI is the only pipeline (the former GitHub Actions image build was removed). The Dockerfile builds production dependencies and the GUI in separate stages and copies only runtime files (package.json, config/, scripts/, src/) with --chown, so nothing is duplicated into an extra layer.