npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scenique24

v2.0.0

Published

> **This app lives in `demo/` of the combined repository.** It is mounted at > `/demo/` by the combined server (`demo/server/site.js`); the marketing site > is served at `/` from the repo root. Deployment files (`Dockerfile`, > `docker-compose.yml`) ar

Readme

Scenique24 — artist management (vanilla JS + PostgreSQL)

This app lives in demo/ of the combined repository. It is mounted at /demo/ by the combined server (demo/server/site.js); the marketing site is served at / from the repo root. Deployment files (Dockerfile, docker-compose.yml) are at the repository root, not here. The instructions below still apply to running the app itself.

End users: the User Guide walks through every feature step by step.

Scenique24 is a client-rendered artist-management application built with vanilla JavaScript, Express, and PostgreSQL. It tracks artist and support-staff rosters with availability calendars, projects with assignments and reusable tasks, customers, a shareable company profile, talent and support-staff categories, compensation details, media uploads, and contacts. It includes reports with PDF and CSV exports, per-project PDF reports with grouped artist/crew/costs totals, public artist and support-staff profiles with token-based invitation responses, cross-workspace search, record counters, an English/French interface (English is the default), a demo-site switch (Administrator → Demo site) that disables mail/phone links site-wide and shows demo credentials on the login page, local username/password authentication, and five color themes (Black & White default plus four alternates, each in light and dark).

The app has no build step: the server serves the API and the static frontend from the same process. Pick one of the two setup paths below — Docker (recommended: everything isolated in containers) or native (Node.js + PostgreSQL installed directly).

Requirements

| Path | Needs | |---|---| | Native | Node.js 22+, PostgreSQL 14+ | | Docker | Docker Engine + Docker Compose (v2+); nothing else — Node and PostgreSQL run in containers |

Setup without Docker (native)

  1. Install dependencies and configure the environment:

    npm install
    cp .env.example .env
  2. Edit .env and set DATABASE_URL (and PORT if needed), then create the tables and start the server:

    npm run migrate   # creates the tables
    npm run dev       # development, with file watching
    # or
    npm start         # production
  3. Optional: fill the database with deterministic demo data (wipes existing rows and uploads/): npm run seed. Sign in with [email protected] / AdminPass1! (admin) or [email protected] / InvoiceMe12! (regular user).

The first registered user becomes the admin; every user after that is a regular user of their own new organization (see Tenants & users below for how teams share one organization).

Running as a service (native)

Run the app under a process manager so it restarts on failure. Example systemd unit (/etc/systemd/system/scenique24.service):

[Unit]
Description=Scenique24 artist-management application
After=network.target postgresql.service

[Service]
Type=simple
User=scenique24
WorkingDirectory=/srv/scenique24
EnvironmentFile=/etc/scenique24/scenique24.env
ExecStart=/usr/bin/node server/index.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now scenique24
sudo journalctl -u scenique24 -f

Put a TLS-terminating reverse proxy (Nginx, Caddy, Traefik) in front and forward all routes to PORT; NODE_ENV=production enables Secure cookies, which requires HTTPS at the proxy.

Setup with Docker

The repository ships a multi-stage Dockerfile and a docker-compose.yml with three services:

| Service | Purpose | |---|---| | db | PostgreSQL 16 with a named data volume and a health check | | migrate | One-shot container that applies db/migrations and exits | | app | The Scenique24 server (runs as a non-root user), with uploads on a named volume |

Quick start

docker compose up --build -d

Then open http://localhost:3000. Migrations run automatically before the app starts. To use a different host port:

APP_PORT=8080 docker compose up --build -d   # http://localhost:8080

(On Windows PowerShell: $env:APP_PORT = "8080"; docker compose up --build -d.)

The first registered user becomes the admin. Stop everything with docker compose down (add -v to also delete the database and uploads volumes — destructive).

Docker without Compose

If you prefer plain Docker commands, run PostgreSQL and the app separately:

# 1. Database
docker network create scenique24
docker run -d --name scenique24-db --network scenique24 \
  -e POSTGRES_USER=scenique24 -e POSTGRES_PASSWORD=scenique24 -e POSTGRES_DB=scenique24 \
  -v scenique24-db-data:/var/lib/postgresql/data \
  postgres:16-alpine

# 2. Build the app image
docker build -t scenique24 .

# 3. Migrate (once per deployment; re-run after pulling schema changes)
docker run --rm --network scenique24 \
  -e DATABASE_URL=postgres://scenique24:scenique24@scenique24-db:5432/scenique24 \
  scenique24 node db/migrate.js

# 4. App
docker run -d --name scenique24-app --network scenique24 \
  -p 3000:3000 \
  -e NODE_ENV=production \
  -e DATABASE_URL=postgres://scenique24:scenique24@scenique24-db:5432/scenique24 \
  -v scenique24-uploads:/app/uploads \
  scenique24

Docker notes

  • Uploads live in the uploads named volume (mounted at /app/uploads); database data lives in db-data. Both persist across docker compose down and are only removed by docker compose down -v. Back both up.
  • Changing database credentials: edit the db.environment block in docker-compose.yml and the DATABASE_URL values to match. The compose defaults (scenique24/scenique24) are for local use only — change the password for anything exposed.
  • Applying new migrations: after pulling schema changes, run docker compose build app migrate (or docker compose build) and docker compose up -d. The rebuild recreates the one-shot migrate container, so it re-runs before the app starts; already-applied migrations are skipped. Note that docker compose up -d without a rebuild does not re-run an already-succeeded migrate container.
  • Demo data: run docker compose exec app node db/seed.js to wipe and re-seed the database with demo data; this also clears the contents of the uploads volume. Dev credentials: [email protected] / AdminPass1!.
  • Secrets: never bake .env files or credentials into the image — pass them at runtime via environment, --env-file, or an orchestration secret.

Tenants & users

What an organization (tenant) is

Every record in the app belongs to an organization. Data is isolated per organization at the database level (Postgres row-level security) — users of one organization never see another's artists, events, or customers. The organization's name shows read-only on Settings → Defaults.

There are two ways a tenant comes into existence:

  1. Self-serve signup (shared database). Anyone who completes the sign-up form (/signup) automatically creates a new organization named by the Organization name field on that form (falling back to <your name> workspace), and becomes its administrator. Records are private to that organization from the first row.
  2. One instance per customer (recommended for real tenants). Each customer gets their own deployment: own database, own uploads volume, own subdomain. Deploy the same image with SEED_DEMO=off (skips the demo-data seed — clean database) and DEMO_MODE_DEFAULT=off (the fresh instance stays out of demo mode); migrations run automatically on first boot. See docs/tenant-setup.md for the full per-tenant checklist (database, environment, volume, domain).

Creating and inviting users

How accounts are created depends on the Demo site switch (Administrator page):

| Demo site | Who can create an account | How | |---|---|---| | On (demo installs) | Nobody self-serves | Sign in with the demo credentials shown on the sign-in card | | Off (real deployments) | Anyone via the sign-up form | /signup — but every signup creates a new organization; the first account on a fresh database becomes the administrator, later signups are regular users of their own new org |

To bring teammates into an existing organization, do not sign up again — an administrator invites them:

  1. Sign in as an administrator and open the Administrator page.
  2. In Users, fill in the teammate's name, email, a starting password (min 8 characters), and a role (User or Administrator), then Add user. The account is created inside your organization.
  3. Send the teammate their email + password: they sign in at /login and immediately see your organization's records.
  4. Remove takes a user out of the organization and ends their sessions immediately (you cannot remove yourself). Duplicate emails are rejected globally — an email address can belong to only one organization.

Roles: Administrator can open the Administrator page (demo switch, user management); User is a regular workspace member.

Accessing the Administrator page

  • In the app: the sidebar's last entry, Administrator (below Activity). It is only usable by admin-role accounts — other roles see an "Administrator access required" notice.
  • By URL: /admin on the app base, e.g. http://localhost:3000/demo/admin in the combined deployment.
  • Who is an administrator? The first account on a fresh database, the seeded [email protected] on demo installs, and anyone invited with the Administrator role (or promoted later by another administrator).

Billing (Stripe, per-seat)

Workspaces are billed by SEATS through Stripe subscriptions, with a per-seat price that follows the band:

| Seats | Price per seat / month | Stripe price env | |---|---|---| | 1–2 | $150 | STRIPE_PRICE_SEATS_1_2 | | 3–5 | $125 | STRIPE_PRICE_SEATS_3_5 | | 6+ | $100 | STRIPE_PRICE_SEATS_6_PLUS |

Create the three recurring per-seat Prices in Stripe, wire their ids as env vars (plus STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET), and point a Stripe webhook endpoint at POST /api/billing/webhook covering checkout.session.completed, customer.subscription.updated and customer.subscription.deleted. The workspace admin opens Administrator → Billing, buys seats via Stripe Checkout, and manages payment details through the Stripe customer portal; every seat change flows back through the webhook. Seat limits are enforced on the invite path (members + open invitations count) — at the limit, invites are disabled until the subscription is upgraded. Without Stripe env vars the rest of the app is unaffected (billing routes answer 503).

Deploying updates

The repository includes a GitHub Actions workflow (.github/workflows/deploy.yml) that runs the full test suite against a real PostgreSQL service and, when the tests pass, deploys to the server over SSH.

One-time server setup:

  1. Install Docker Engine + the Compose plugin, and create a deploy user with SSH key access and docker group membership.
  2. Clone the repository on the server (e.g. /opt/scenique24).
  3. In the GitHub repository, add the action secrets: SSH_HOST, SSH_USER, SSH_KEY (the deploy user's private key), and DEPLOY_PATH (e.g. /opt/scenique24).

Updating — push to main (or run the workflow manually). The workflow tests the commit, then SSHes in and runs:

git pull --ff-only
docker compose up -d --build
docker image prune -f

The rebuild re-runs migrations before the app restarts; the db-data and uploads volumes persist across deploys. Rollback: git checkout <previous-tag-or-sha> && docker compose up -d --build.

Manual update (no CI, or when the workflow's secrets are not configured): on the server, run the same git pull + docker compose up -d --build yourself.

Moving existing uploads to a fresh server: a new uploads volume starts empty. After the first deploy, copy your local uploads/ contents into it once:

docker compose cp uploads/. app:/app/uploads/

Hardening before exposing beyond localhost: move the Postgres password out of docker-compose.yml into environment variables, and put a TLS-terminating reverse proxy (Nginx, Caddy, Traefik) in front of the app port.

Environment

| Variable | Required | Default | Description | |---|---|---|---| | DATABASE_URL | yes | — | PostgreSQL connection string, e.g. postgres://postgres:postgres@localhost:5432/scenique24 | | PORT | no | 3000 | HTTP port; if it is busy the server retries on PORT + 1 | | NODE_ENV | no | development | Set to production to enable Secure cookies (requires HTTPS) | | SEED_DEMO | no | — | Set to off on tenant deployments to skip the demo-data seed on boot (clean database; first signup becomes the admin) | | DEMO_MODE_DEFAULT | no | — | Set to off on tenant deployments so a fresh instance starts out of demo mode (no credentials on the sign-in card, mail/phone links active) | | JWT_SECRET | yes in production | dev fallback | Signs the auth access tokens (min 32 chars). Required before exposing beyond localhost | | STRIPE_SECRET_KEY | no | — | Enables subscription billing (per-seat tiers); without it billing routes answer 503 | | STRIPE_WEBHOOK_SECRET | no | — | Required to verify Stripe webhook signatures once billing is live | | STRIPE_PRICE_SEATS_1_2 / _3_5 / _6_PLUS | no | — | Stripe Price ids for the three seat bands ($150 / $125 / $100 per seat) |

Native setups read them from a .env file in the project root (see .env.example); Docker setups pass them via environment / --env-file.

Uploads

Uploaded files (headshots, portfolio media, project documents) are stored on local disk in ./uploads/. Set UPLOAD_DIR to override the location (native) or mount a volume at /app/uploads (Docker). Back this directory up alongside the database — the database only stores file metadata and keys, not the file contents.

Tests

The test suite requires a local PostgreSQL server. npx vitest run recreates the scenique24_test database (created automatically) using the admin connection in TEST_ADMIN_URL (default postgres://postgres:postgres@localhost:5432/postgres). Override TEST_DATABASE_URL and TEST_ADMIN_URL if your local PostgreSQL uses different credentials.

An end-to-end API smoke test is also available; it boots the server against its own scratch database and exits 0 when every step passes:

node scripts/smoke.mjs

Project layout

| Path | Contents | |---|---| | server/ | Express app: HTTP server, auth, sessions, validation, error handling, file storage | | server/routes/ | One module per resource (artists, projects, assignments, customers, media, …) | | public/ | Static frontend: HTML entry point and CSS | | public/js/ | Browser JavaScript: API client, UI components, page controllers, i18n dictionaries (English/French) | | db/ | SQL migrations and the migration runner | | tests/ | Vitest suites for the API and shared libraries | | scripts/ | Maintenance and verification scripts (smoke.mjs) | | docs/ | Design docs: specs and implementation plans | | design-system/ | Atelier design-system reference, derived from the shipped CSS | | .github/workflows/ | CI: test suite on every push, SSH deploy to production | | Dockerfile, docker-compose.yml | Container build and the db/migrate/app stack |