System for handling notifications
  • TypeScript 96.1%
  • CSS 3%
  • JavaScript 0.9%
Find a file
jesper d2f7a4e038 Add rule editor, business areas, BA Leads and recurring rules
- Rules move to the database (rules and rule_changes, migration 0002),
  filled once from src/nudge/rules.ts. Every change is logged with who,
  when, and the rule before and after.
- Rule editor at /rules/new and /rules/<id> with a 90-day preview of the
  rule as edited; on/off switch and New rule in the list. Rules from the
  browser are checked field by field on the server (store.ts).
- Business areas are Cinode teams: a rule can be limited to one.
- BA Leads (everyone managing a Cinode team) as an object type, and
  "the person themself" as a recipient.
- Recurring triggers: weekly on a weekday, monthly on a day, or the last
  weekday of the month.
- Example rule "Administrera kvitton" (BA Leads, last weekday, Slack DM),
  switched off.
- Cinode client treats an empty answer (204) as an empty list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:47:56 +01:00
db initial commit 2026-10-27 17:11:47 +00:00
docs Add rule editor, business areas, BA Leads and recurring rules 2026-10-27 19:47:56 +01:00
drizzle Add rule editor, business areas, BA Leads and recurring rules 2026-10-27 19:47:56 +01:00
src Add rule editor, business areas, BA Leads and recurring rules 2026-10-27 19:47:56 +01:00
.env.example Add send log, dedup, Slack and Cinode task channels, morning run 2026-10-27 19:26:36 +01:00
.gitignore initial commit 2026-10-27 17:11:47 +00:00
.prettierignore initial commit 2026-10-27 17:11:47 +00:00
AGENTS.md Format AGENTS.md, README.md and catalog-info.yaml with Prettier 2026-10-27 18:21:24 +01:00
catalog-info.yaml Format AGENTS.md, README.md and catalog-info.yaml with Prettier 2026-10-27 18:21:24 +01:00
CLAUDE.md initial commit 2026-10-27 17:11:47 +00:00
components.json initial commit 2026-10-27 17:11:47 +00:00
drizzle.config.ts initial commit 2026-10-27 17:11:47 +00:00
eslint.config.js initial commit 2026-10-27 17:11:47 +00:00
mkdocs.yml Add Cinode source, rule engine and 90-day preview 2026-10-27 18:44:06 +01:00
package-lock.json initial commit 2026-10-27 17:11:47 +00:00
package.json initial commit 2026-10-27 17:11:47 +00:00
prettier.config.js initial commit 2026-10-27 17:11:47 +00:00
README.md Format AGENTS.md, README.md and catalog-info.yaml with Prettier 2026-10-27 18:21:24 +01:00
tsconfig.json initial commit 2026-10-27 17:11:47 +00:00
tsr.config.json initial commit 2026-10-27 17:11:47 +00:00
vite.config.ts Add send log, dedup, Slack and Cinode task channels, morning run 2026-10-27 19:26:36 +01:00

logfab-nudge

System for handling notifications

Created from the "Logikfabriken app" template in Developer Hub (logfab-app@3): TanStack Start (React with server-side rendering), TypeScript, Tailwind CSS and shadcn/ui, with its own PostgreSQL database, file storage and login, already set up for Logikfabriken's OpenShift platform. What the template gained since, and how to add it here, is in its CHANGELOG.

Building with an AI agent (Claude Code, Cursor, Copilot and the like)? It reads AGENTS.md (Claude Code through CLAUDE.md), which tells it the stack and the platform's rules. Keep that file up to date as the app grows.

How it goes live

Push to main. A few minutes later it is live at https://logfab-nudge.apps.ocp.poc.logfab.cloud. On the way, every push is checked for leaked passwords (Gitleaks) and unsafe code (Semgrep), built, checked for known security holes (Trivy), signed, deployed, and scanned while it runs (OWASP ZAP). A failed check stops it. Follow each run in Developer Hub, on this app's CI tab.

You need no Dockerfile and no deploy settings: the platform builds it with npm run build and runs it with npm start.

Run it on your computer

npm install
cp .env.example .env    # settings for your computer
npm run dev             # http://localhost:3000

Before pushing: npm run typecheck, npm run lint and npm run build.

The database

The platform gives the app its own PostgreSQL database: two copies on two servers, so one can fail without the app noticing. The app finds it through DATABASE_URL, which the platform sets; nobody needs its password.

  • Tables are in src/db/schema.ts (Drizzle). After changing them, run npm run db:generate and commit the new file in drizzle/. The app applies it to the database when it starts.
  • Read and write in server functions, like the notes on the start page (src/routes/index.tsx).
  • On your computer the app runs without a database until you set DATABASE_URL in .env to a Postgres of your own.
  • Backed up by the platform: every change as it happens and a full backup every night, kept for 7 days; it can be restored to any moment in that time.

Files

The app also gets its own bucket in the platform's S3 storage, for uploads, images and the like (the server's disk is wiped at every deploy). Its address and keys arrive as S3_* settings, set by the platform.

  • The client is in src/storage/ (@aws-sdk/client-s3); the start page has an upload example.
  • The bucket is reachable from the app's server only; the browser gets files through /api/files/<key>.
  • On your computer there is no bucket unless you set S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY in .env to an S3 of your own.

Login

People sign in through Logikfabriken's Keycloak with their Microsoft account; the app never sees a password. Pages and server functions are open to everyone until you protect them:

  • A page: beforeLoad: ({ location }) => requireLogin(location.href) in its route.
  • A server function: const user = await requireUser() at its start.
  • Only one Keycloak group: pass the group as well, e.g. requireUser('/logikfabriken/devex'). Not on the platform yet: it does not pass people's groups to apps, so user.groups is empty there and a group check lets nobody in. Ask the platform team if you need it.

All three come from @/auth/functions; AGENTS.md has examples. The start page has a sign-in and sign-out button. On your computer everyone is "Test User", without Keycloak.

Settings and keys

Keep them out of this repo. On the platform they live in Bitwarden Secrets Manager, project openshift-apps, as one secret named logfab-nudge/staging whose value is JSON:

{ "GREETING": "hello", "API_KEY": "...", "VITE_PUBLIC_MESSAGE": "hi" }
  • Every value reaches the server as an environment variable (process.env.GREETING), for server functions and server routes. Real secrets belong here.
  • Values starting with VITE_ also reach the browser, written in when the app is built (import.meta.env.VITE_PUBLIC_MESSAGE). Every visitor can read those, so only public values.

A changed value reaches the app at its next build: push to main.

Logs and health

  • Server code logs with log from src/lib/log.ts: one JSON line per event, plus one per request. See them in Developer Hub (Topology tab, the app's pod) or the OpenShift console.
  • GET /api/health tells OpenShift the app is up.

Docs

docs/ holds this app's docs, in Markdown, shown on its Docs tab in Developer Hub. A push to main that changes docs/ or mkdocs.yml publishes them. Add a page: a file in docs/ and a line under nav in mkdocs.yml.

Files

  • src/routes/: the pages (index.tsx is the start page) and api/ endpoints.
  • src/components/ui/: shadcn/ui components; add more with npx shadcn@latest add <name>.
  • src/lib/: shared code (logger, helpers).
  • src/start.ts: what runs around every request.
  • docs/ and mkdocs.yml: the docs on the Docs tab.
  • AGENTS.md: instructions for AI agents.
  • catalog-info.yaml: how the app is listed in Developer Hub.