Norbert's Spark - an AI tools CRM - Beta

April 16, 2026 · View on GitHub

Norbert's Spark ( named after Norbert Wiener, the father of cybernetics) is a cutting-edge AI tools CRM designed to help users manage and leverage AI technologies effectively. Built with modern web technologies, it offers a seamless experience for integrating AI capabilities into everyday workflows.

Although there already exist various AI-SDK starter kits, such as the Next.js Open API Starter Kit, Norbert's Spark. goes a step further by providing a comprehensive monorepo structure that includes both frontend and backend components, along with a PostgreSQL database setup.

The aim of Norbert's Spark is to avoid tight coupling between any one technology. On the roadmap there three different ways to deploy Norbert's Spark: the first is to use PaaS services such as Vercel and Supabase; the second is to use Docker containers; and the third is to use Infrastructure as code (IaC) to AWS using either Terraform or Pulumi.

Norbert's Spark currently uses a number of different third-party services, including:

These are planned to be replaced with AWS or self-hosted alternatives in the future.

The other difference between Norbert's Spark and other AI-SDK starter kits is that the backend is where the business logic resides, with the frontend being a thin client. This is in contrast to many AI-SDK starter kits where the frontend contains most of the business logic and directly calls the AI provider APIs. In Norbert's Spark, the frontend calls the backend API, which in turn calls the AI provider APIs. This architecture enhances security, maintainability, and scalability. This is a similar approach to what is called a headless CMS, where the frontend is decoupled from the backend.

In this repo is a frontend that uses Next.js 16 with React 19 and Material UI. The purpose of this frontend is to provide a user interface for interacting with the AI tools CRM. In the frontend, users can manage their AI tools, view analytics, and configure settings.

In the packages/shared is the OpenAPI spec: packages/shared/src/openapi.json. This is used in the frontend but it is intended that the user accesses the OpenAPI spec to build out their own frontend UI.

Roadmap for Norbert's Spark

  • Multi-tenant support with role-based access control (RBAC)
  • Integration with AWS services (S3, SES, Lambda, etc.) using Pulumi for IaC
  • FastAPI and LLaMA for a private, self-hosted alternative to the open models
  • Replacing Drizzle queries with SQL queries for better performance and control
  • Support for multiple AI providers (OpenAI, Google Gemini, Anthropic, etc.) with dynamic provider selection
  • Advanced analytics dashboard with real-time monitoring of AI interactions
  • Plugin system for extending functionality and integrating with third-party services
  • Replace Material UI with shadcn/ui

Table of Contents

The architecture in both the backend and frontend follows the principles of Clean Architecture, ensuring a clear separation of concerns and maintainability. The layers are organized as follows: - Domain Layer: Contains the core business logic and entities. - Application Layer: Manages use cases and application-specific logic. - Infrastructure Layer: Handles data access, external APIs, and other infrastructure concerns. - View Layer (Frontend only): Manages UI components and user interactions.

The pattern promotes testability, scalability, and ease of understanding, making it easier to adapt to changing requirements over time. The architecture follows the Hexagonal Architecture (Ports and Adapters) principles, allowing for flexibility in integrating different technologies and services. It also aligns with Domain-Driven Design (DDD) concepts, focusing on the core domain and its complexities.

For ease of working with AI tools, several practices have been adopted in the codebase:

  • The architecture is written in text files throughout the codebase. As an example, the hexagonal architecture is explained in the root of the backend in this document: apps/backend/src/HEXAGONAL_ARCHITECTURE.txt. These descriptions are optimized for consumption by AI agents, making it easier for them to understand and navigate the system architecture.
  • There are extensive JSDocs comments. Previously code comments were considered bad practice due to the drift between code and comments. However, with the advent of AI tools like GPT-4, well-written comments can be invaluable for understanding code. AI models can use these comments to generate explanations, documentation, and even assist in code generation. Therefore, the codebase includes comprehensive JSDocs comments to facilitate better understanding and collaboration.
  • There is a highly opinionated code quality pipeline including ESLint, Prettier, and TypeScript configurations to ensure consistent code style and quality across the project.
  • There is a strong emphasis on testing, with unit tests using Vitest and end-to-end tests using Playwright to ensure the reliability and stability of the application.
  • Eval testing is a key part of the development process: apps/backend/evals. Eval testing provides a means to validate the functionality and performance of AI models integrated into the application. By running eval tests, developers and business owners can assess how well the AI models perform in real-world scenarios, identify potential issues, and make necessary improvements. This ensures that the AI components meet the desired quality standards and deliver accurate results to users.
  • The E2E tests are designed to cover critical user journeys and interactions within the application. They simulate real-world scenarios to ensure that the application behaves as expected from the user's perspective. By automating these tests, developers can quickly identify regressions or issues introduced during development, ensuring a smooth and reliable user experience.
  • The E2E tests use a temporal testing environment that is spun up and torn down for each test run, ensuring a clean state for accurate testing. This approach helps maintain the integrity of the tests and provides confidence in the application's functionality.
  • The E2E tests are not run in the CI but are executed as part of the Husky pre-push hook. To bypass the E2E tests in your workflow, use the SKIP_E2E=1 variable on the command line.

As an example, instead of this git push command:

git push origin ui/new-page-pdf-extract-data

Use this to bypass the E2E tests:

SKIP_E2E=1 git push origin ui/new-page-pdf-extract-data

It is recommended to regularly run the E2E tests, but they may not be required with every git push. Due to slow NextJS, the E2E tests use a production build rather than a development build.

The tech stack choices are listed as below.

PNPM and Turborepo

This repo uses PNPM as the package manager and Turborepo as the build system. PNPM is a fast, disk space-efficient package manager that uses a unique symlink-based approach to manage dependencies. It ensures consistent dependency resolution across all packages in the monorepo and integrates seamlessly with Turborepo for managing dependencies and scripts. Turborepo is a powerful build system that optimizes the build process by caching and parallelizing tasks, making it faster and more efficient than traditional build tools.

This repo uses PNPM version 10.33.0.

To use PNPM version 10.33.0, you can run the following command:

corepack enable pnpm
corepack use pnpm@10.33.0

Husky

There are two husky hooks configured:

  • pre-commit: Runs pnpm lint:staged to lint only the staged files before committing. This ensures that only code that passes linting is committed to the repository.
  • pre-push: Runs pnpm test to execute the test suite before pushing changes to the remote repository. This helps catch any failing tests before code is pushed, maintaining code quality

These hooks help enforce code quality standards and prevent potential issues from being introduced into the codebase.

Using husky hooks are particularly important today as not just a means of helping developers maintain code quality, but also as a way to ensure that AI-generated code adheres to the project's standards. As AI tools become more prevalent in code generation, husky hooks can serve as a safeguard to catch any issues or inconsistencies introduced by AI-generated code before it is committed or pushed to the repository.

Conventional Commits

This project enforces Conventional Commits specification for all commit messages.

Format

<type>(<scope>): <subject>

<body>

<footer>

Minimum required format:

<type>: <subject>

Types

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing tests or correcting existing tests
  • build: Changes that affect the build system or external dependencies
  • ci: Changes to our CI configuration files and scripts
  • chore: Other changes that don't modify src or test files
  • revert: Reverts a previous commit

Examples

Simple commits

git commit -m "feat: add user authentication"
git commit -m "fix: resolve navigation bug"
git commit -m "docs: update README with setup instructions"

With scope

git commit -m "feat(auth): add login functionality"
git commit -m "fix(api): handle null responses"
git commit -m "test(components): add unit tests for Button"

With breaking changes

git commit -m "feat(api): change response format

BREAKING CHANGE: API responses now return data in camelCase instead of snake_case"

Enforcement

Commit messages are validated using commitlint via a Husky commit-msg hook. Invalid commits will be rejected with an error message explaining what's wrong.

Tips

  • Keep the subject line under 72 characters
  • Use imperative mood ("add feature" not "added feature")
  • Don't capitalize the first letter of the subject
  • Don't end the subject line with a period

Code Quality intialisers

This project uses both ts-reset and modern-normalize to improve code quality and ensure consistency across different environments, for TypeScript and CSS respectively.

ESLint and Prettier

The choice of ESlint plugins is as follows:

@eslint/js

Core ESLint JavaScript rules providing foundational linting for JavaScript code. Uses the recommended configuration as the base for all ESLint setups.

@typescript-eslint/eslint-plugin

TypeScript-specific linting rules that understand TypeScript syntax and semantics. Provides rules for type checking, async best practices, and TypeScript idioms. Used across all workspaces.

Rules:

  • @typescript-eslint/no-unused-vars: Warn - Allows unused variables prefixed with _
  • @typescript-eslint/triple-slash-reference: Off (frontend only) - Allows triple-slash references for Next.js types
@typescript-eslint/parser

Parser that allows ESLint to understand TypeScript syntax. Required for all TypeScript linting rules to function properly.

eslint-plugin-codegen

Manages code generation tasks and ensures generated code follows project conventions. Used in root configuration.

eslint-plugin-import

Manages import/export syntax and prevents issues like duplicate imports, missing imports, and incorrect import ordering.

Rules:

  • import/first: Error - Ensures imports come first
  • import/newline-after-import: Error - Enforces blank line after imports
  • import/no-duplicates: Error - Prevents duplicate imports
eslint-plugin-promise

Enforces best practices for JavaScript Promises. Uses the recommended preset to catch common mistakes like missing returns in .then(), unhandled rejections, and incorrect Promise construction.

Configuration: Uses recommended rules via promisePlugin.configs.recommended.rules

Used in: Root (inherited by all workspaces)

eslint-plugin-simple-import-sort

Automatically sorts import statements in a consistent order. Enforces alphabetical ordering of imports and exports.

Rules:

  • simple-import-sort/imports: Error - Enforces sorted imports
  • simple-import-sort/exports: Error - Enforces sorted exports

Used in: Root, Shared package

eslint-plugin-sort-destructure-keys

Sorts destructured object keys alphabetically for consistency.

Rules:

  • sort-destructure-keys/sort-destructure-keys: Warn - Suggests sorting destructured keys

Used in: Root, Shared package

eslint-plugin-jsdoc

Enforces proper JSDoc comment format and completeness. Ensures documentation is clear and consistent.

Rules:

  • jsdoc/check-alignment: Warn - Checks JSDoc alignment
  • jsdoc/check-param-names: Warn - Validates parameter names
  • jsdoc/check-tag-names: Warn - Ensures valid JSDoc tags
  • jsdoc/check-types: Warn - Validates type annotations
  • jsdoc/require-param-description: Warn - Requires parameter descriptions
  • jsdoc/require-returns-description: Warn - Requires return descriptions

Used in: Root, Frontend

eslint-plugin-security

Identifies potential security vulnerabilities in the code including unsafe regular expressions, eval usage, and timing attacks.

Rules:

  • security/detect-object-injection: Error - Detects potential object injection vulnerabilities
  • security/detect-non-literal-regexp: Warn - Warns about non-literal RegExp constructors
  • security/detect-unsafe-regex: Error - Detects regex vulnerabilities
  • security/detect-buffer-noassert: Error - Prevents buffer vulnerabilities
  • security/detect-eval-with-expression: Error - Prevents eval usage
  • security/detect-no-csrf-before-method-override: Error - CSRF protection
  • security/detect-possible-timing-attacks: Warn - Detects timing attack vulnerabilities

Used in: Root, Frontend

@vitest/eslint-plugin

Provides linting rules for Vitest test files. Enforces best practices for test writing, proper test structure, avoiding duplicate test names, and ensuring proper assertions.

Configuration: Applied to **/*.test.ts, **/*.test.tsx, **/*.spec.ts, **/*.spec.tsx files

Rules:

  • Uses recommended Vitest rules (backend, frontend)
  • Shared package uses a subset: vitest/expect-expect, vitest/no-identical-title, vitest/no-focused-tests, vitest/valid-expect
  • vitest/no-conditional-expect: Off (shared package) - Allows conditional expects for type narrowing

Used in: Backend, Frontend, Shared package

@next/eslint-plugin-next

Next.js-specific linting rules that catch common mistakes and enforce best practices for Next.js applications.

Rules:

  • @next/next/no-html-link-for-pages: Error - Use Next.js <Link> component instead of <a> tags
  • @next/next/no-img-element: Warn - Use Next.js <Image> component for optimized images
  • @next/next/no-sync-scripts: Error - Prevents synchronous scripts that block rendering
  • @next/next/no-duplicate-head: Error - Avoids duplicate <Head> components

Used in: Frontend only

@tanstack/eslint-plugin-query

React Query (TanStack Query) specific rules for proper query usage and cache management. Ensures best practices with React Query hooks.

Configuration: Uses flat/recommended preset

Used in: Frontend only

@eslint-react/eslint-plugin

Modern React linting plugin that replaces both eslint-plugin-react and eslint-plugin-react-hooks. Provides comprehensive React, React Hooks, React DOM, React Server Components, and Web API rules with full ESLint 10 and TypeScript support.

Configuration: Uses recommended-typescript preset, which includes:

  • Core React rules (component patterns, JSX best practices)
  • Hooks rules (rules of hooks, exhaustive deps)
  • DOM rules (no dangerouslySetInnerHTML, no script URLs, etc.)
  • React Server Components rules
  • Web API rules

Used in: Frontend only

eslint-plugin-jsx-a11y

Accessibility (a11y) rules for JSX elements. Catches accessibility violations like missing alt text, improper ARIA attributes, and keyboard navigation issues.

Configuration: Uses flatConfigs.recommended for comprehensive accessibility checking

Used in: Frontend only

eslint-plugin-drizzle

Drizzle ORM-specific rules to prevent unsafe database operations.

Applied to: src/db/**/*.{ts,tsx} files only

Rules:

  • drizzle/enforce-delete-with-where: Error - Requires WHERE clause in DELETE statements to prevent accidental mass deletions
  • drizzle/enforce-update-with-where: Error - Requires WHERE clause in UPDATE statements to prevent accidental mass updates

Used in: Frontend only

eslint-plugin-playwright

Playwright-specific rules for E2E test files. Enforces best practices for Playwright tests.

Applied to: e2e/**/*.{ts,js} files only

Configuration: Uses flat/recommended preset

Used in: Frontend only

typescript-eslint (package)

Unified TypeScript ESLint tooling package that provides both the plugin and parser. Used for TypeScript-specific configurations.

Configuration: Uses recommended preset from typescript-eslint package

Used in: Frontend only

Common Custom Rules Across Workspaces

  • no-console: Varies by workspace - Warn in root (with exceptions), warn in shared, off in backend, off in frontend
  • no-restricted-syntax: Error - Disallows TypeScript enums in frontend, backend, and shared workspaces, enforcing const objects with "as const" instead for better type safety and runtime behaviour
  • no-unused-vars: Off - Disabled in favour of TypeScript-specific rule
  • no-redeclare / @typescript-eslint/no-redeclare: Off for TypeScript files - The TypeScript compiler already enforces redeclaration errors, and the const Foo = ... as const + type Foo = ... idiom (value/type namespace separation) is valid TypeScript but triggers both ESLint rules
  • semi: Error - Never use semicolons (root config only)
  • max-lines: Error - Maximum 600 lines per file (skips blank lines and comments). Disabled for test/spec files

Prettier

Prettier is configured to ensure consistent code formatting across the entire codebase. The configuration is defined in the .prettierrc file at the root of the monorepo. Key Prettier settings include:

  • Print Width: 100 characters - Keeps lines reasonably short for better readability
  • Tab Width: 2 spaces - Standard indentation size for JavaScript/TypeScript
  • Use Tabs: false - Uses spaces for indentation instead of tabs
  • Semi: false - Omits semicolons at the end of statements
  • Single Quote: true - Uses single quotes for strings instead of double quotes
  • Trailing Comma: "all" - Adds trailing commas where valid in ES5

Run prettier in the root of the project with:

pnpm run format

The --cache option is used to speed up formatting by only processing changed files.'

Tubrorepo and PNPM

Turborepo is used as the build system and task runner for this monorepo. It provides efficient caching, parallel execution, and dependency graph management to optimize build times and developer productivity. Turborepo allows defining tasks in each package's package.json and orchestrates their execution based on dependencies.

PNPM is used as the package manager for this monorepo. PNPM provides fast and efficient package management with a unique disk space-saving approach using symlinks. It ensures consistent dependency resolution across all packages in the monorepo and integrates seamlessly with Turborepo for managing dependencies and scripts.

Drizzle ORM and PostgreSQL

This apps use PostgreSQL 18.1 as the database, managed via Docker Compose for easy setup and deployment. Drizzle ORM is used as the database ORM, providing a type-safe and modern way to interact with the PostgreSQL database using TypeScript.

The use of both PostgreSQL and Drizzle is as follows:

  • SQL migration = source of truth
  • Drizzle schema = typed access layer

This avoids ORM drift and keeps invariants enforceable even if someone bypasses the app.

Mermaid

Mermaid is used in the backend for generating diagrams and visualizations from text-based descriptions. It allows developers to create flowcharts, sequence diagrams, class diagrams, and more using a simple markdown-like syntax. Mermaid is integrated into the backend to help visualize architecture, workflows, and other concepts directly from code comments or markdown files.

For example, the dependency injection container diagram is generated using Mermaid syntax in apps/backend/src/infrastructure/di/container.md. You can render this diagram by the following command:

cd apps/backend
pnpm mermaid src/infrastructure/di/container.md

Sentry

Sentry is integrated into both the frontend and backend applications for error tracking and performance monitoring. It provides real-time insights into application errors, crashes, and performance bottlenecks, allowing developers to quickly identify and resolve issues. It also now supports monitoring AI interactions, helping to track and analyze the performance of AI models integrated into the applications. Sentry will:

  • Track token usage, costs, and latency across all your LLM calls
  • Monitor agent conversations, tool usage, and decision-making processes
  • Debug failed requests and optimize prompt performance with detailed traces

In the environment variables you will need to configure the Sentry-related settings:

  • SENTRY_ACCOUNT (required): Set to "true" to enable Sentry, or "false" to disable it. When "false", all other Sentry-specific variables are ignored.
  • SENTRY_DSN (required when SENTRY_ENABLED is "true"): The DSN for your Sentry project. This is needed at runtime so the backend and/or frontend can send events to Sentry.
  • SENTRY_PROJECT (optional for basic runtime, recommended for releases/CI): The Sentry project slug. This is typically used by Sentry CLI or CI pipelines for tasks like release creation and source map uploads.
  • SENTRY_ORG (optional for basic runtime, recommended for releases/CI): The Sentry organization slug. Like SENTRY_PROJECT, this is used by tooling that integrates with Sentry (e.g., releases, deployments).
  • SENTRY_AUTH_TOKEN (optional for local dev, required for authenticated Sentry tooling): A Sentry auth token used by automation (e.g., CI) to create releases, upload source maps, etc. This is a secret and must never be exposed to the browser or committed to version control. It is referenced in .env.example for this purpose.

Using Sentry with AI-SDK requires the use of telemetry integration provided by AI-SDK. This integration captures detailed telemetry data from AI interactions, including token usage, response times, and error rates. By leveraging this telemetry data, Sentry can provide deeper insights into the performance and reliability of AI models within your applications. More details can be found in the AI-SDK documentation. This app is already configured to use both Telemetry and Sentry. If you want to use this service, create a Sentry account and set the environment variables as described above. In the Sentry configuration (apps/backend/src/infrastructure/security/instrument.ts), I have set the sendDefaultPii property to false. Setting sendDefaultPii to true sends personally identifiable information to Sentry unconditionally. This should be configurable via environment variable or set to false by default, especially to comply with privacy regulations like GDPR. In the configuration, you can adjust the tracesSampleRate based on the environment. I've set it to 0.1 for production and 1.0 for development to balance performance monitoring with overhead.

tracesSampleRate: EnvConfig.NODE_ENV === 'production' ? 0.1 : 1.0

GitHub Actions

The project includes two automated GitHub Actions workflows to maintain code quality and dependency management:

1. CI/CD Pipeline (.github/workflows/ci-cd.yml)

  • Triggers: Runs on pull requests and pushes to the main branch
  • Purpose: Ensures code quality and compatibility across Node.js versions
  • Matrix Testing: Tests against Node.js 22.x and 24.x
  • Steps:
    • Format checking (Prettier)
    • Linting (ESLint)
    • Unit tests (Vitest)
    • Production build verification
  • Benefits: Catches issues early before merging to main

2. Update Dependencies (.github/workflows/update-dependencies.yml)

  • Triggers: Runs daily at 9:00 AM UTC (configurable via cron) or manually via workflow_dispatch
  • Purpose: Automatically updates minor and patch dependencies to keep the project secure and up-to-date
  • Steps:
    • Updates dependencies in root, frontend, and backend workspaces using pnpm update
    • Runs tests, type checking, and linting to detect breaking changes
    • Creates a pull request with the updates if changes are detected
    • PR title indicates if tests fail (⚠️ TESTS FAILING) for immediate attention
  • Benefits: Reduces manual dependency maintenance, ensures security patches are applied promptly, and provides visibility into potential breaking changes

Both workflows use PNPM for consistent package management and leverage caching to improve execution speed.

How to create an API endpoint

This app follows an API-First development workflow. This means that the API specification is written first and is the the single source of truth for the API. The API specification is written in the OpenAPI format and is located in packages/shared/src/openapi.json.

Firstly, create an endpoint specification in the OpenAPI format and add it to packages/shared/src/openapi.json.

In this example I've added a delete operation to an exising endpoint. After your change, the OpenAPI spec should include a delete operation on the chosen path with an appropriate operationId.

In the shared package run 'pnpm run lint:api'. It uses Spectral to validate the OpenAPI spec.

Then run 'pnpm run build' in the shared package to generate types from the OpenAPI spec. These types are available to use in both the frontend and backend packages.

Then run 'pnpm run build' in the shared package to generate types from the OpenAPI spec. These types are available to use in both the frontend and backend packages.

In 'apps/backend/src/adapters/primary/http' find the correct controller to add the new operation to.

For this example, I've added a delete operation to 'apps/backend/src/adapters/primary/http/controllers/customers.controller.ts'. The controller now includes a handler method for the delete operation that matches the operationId defined in the OpenAPI spec. The operationId in the OpenAPI spec should be the same as the method name in the controller.

You can create a Data Transfer Objects in 'apps/backend/src/application/dtos' for runtime validation if needed, or you can use Drizzle schemas directly.

Then implement the use case in 'apps/backend/src/application/use-cases'.

There may already be a use case that matches the operation you are implementing, in which case you can just call that use case from the controller.

Or you can create a new use case.

In the 'apps/backend/src/adapters/secondary/repositories' repository is where database queries should be implemented.

In this case I created a new deleteUsers method for the PostgresUserRepository. ** example **

Also, add this method to the relevant port interface in 'apps/backend/src/application/ports': ** example **

The dependency injection container is in 'apps/backend/src/infrastructure/di/container.ts'. Here, bind the new repository method to the use case.

Finally, implement the database logic in 'apps/backend/src/infrastructure/db'.

Audit Domain

The audit domain apps/backend/src/domain/audit directory contains the domain logic for audit logging in the application.

Type-Safe Audit Changes

The audit log system uses a union type AuditChanges to provide type safety and autocomplete support for different change structures based on the action type.

Available Change Types

CreateChanges

Used when an entity is created:

const changes: CreateChanges = {
  created: {
    name: 'John Doe',
    email: 'john@example.com',
    role: 'user',
  },
}

UpdateChanges

Used when an entity is updated, capturing before/after states:

const changes: UpdateChanges = {
  before: {
    name: 'Old Name',
    email: 'old@example.com',
  },
  after: {
    name: 'New Name',
    email: 'new@example.com',
  },
}

DeleteChanges

Used when an entity is deleted:

const changes: DeleteChanges = {
  deleted: {
    chatId: '123',
    messageCount: 42,
    wasActive: true,
  },
}

LoginChanges

Used for successful login events:

const changes: LoginChanges = {
  success: true,
  method: 'jwt',
  sessionDuration: '7d',
}

LoginFailedChanges

Used for failed login attempts:

const changes: LoginFailedChanges = {
  email: 'user@example.com',
  reason: 'invalid_password',
}

LogoutChanges

Used for logout events:

const changes: LogoutChanges = {
  reason: 'user_initiated',
  sessionDuration: '2h',
}

PasswordChangeChanges

Used for password change events:

const changes: PasswordChangeChanges = {
  success: true,
  method: 'email_verification',
}

EmailChangeChanges

Used for email change events:

const changes: EmailChangeChanges = {
  before: 'old@example.com',
  after: 'new@example.com',
  verified: true,
}

Usage Example

import { AuditLogPort, CreateAuditLogDTO } from '../application/ports/audit-log.port.js'
import { AuditAction, EntityType } from '../domain/audit/entity-type.enum.js'
import type { LoginFailedChanges } from '../domain/audit/audit-changes.types.js'

// Type-safe audit log creation
const auditEntry: CreateAuditLogDTO = {
  userId: null,
  entityType: EntityType.USER,
  entityId: 'unknown',
  action: AuditAction.LOGIN_FAILED,
  changes: {
    email: 'user@example.com',
    reason: 'invalid_password',
  } satisfies LoginFailedChanges, // Type-checked!
  ipAddress: '192.168.1.1',
  userAgent: 'Mozilla/5.0',
}

await auditLog.log(auditEntry)

Benefits

  1. Type Safety: TypeScript will catch type errors at compile time
  2. Autocomplete: IDEs provide intelligent code completion for change structures
  3. Documentation: Change structures are self-documenting with TypeScript types
  4. Flexibility: The union type includes Record<string, unknown> as a fallback for custom change structure

Sensitive Data Redaction

All audit log entries are automatically redacted for sensitive fields (passwords, tokens, API keys, etc.) before being stored in the database. See redact-sensitive-data.ts for the complete list of sensitive fields.

Architecture

This project is a monorepo with the following structure:

  • frontend: Next.js framework with React 19 and Material UI
  • backend: Fastify TypeScript API server
  • PostgreSQL: Docker-based PostgreSQL 18.1 database

Tech Stack

Frontend

Monorepo Tools

Database

  • PostgreSQL 18.1: Docker-based PostgreSQL instance

Prerequisites

  • Node.js >= 22 < 25
  • PNPM >= 10
  • Docker and Docker Compose (for PostgreSQL)

Getting Started

1. Install Dependencies

pnpm install

2. Set Up Environment Variables

Copy the example environment file:

cd backend
cp .env.example .env

Update the values in .env with your configuration.

3. Start PostgreSQL

Start the PostgreSQL database:

cd backend
docker compose up -d

See DOCKER_POSTGRES.md for detailed database setup instructions.

4. Development

Run all workspaces in development mode:

pnpm dev

Or run individual workspaces:

# Frontend only
cd frontend && pnpm dev

# Backend only
cd backend && pnpm dev

5. Build

Build all workspaces:

pnpm build

Available Scripts

  • pnpm dev - Start development servers for all workspaces
  • pnpm build - Build all workspaces
  • pnpm lint - Run linting across all workspaces
  • pnpm lint:fix - Run linting with auto-fix across all workspaces
  • pnpm typecheck - Run TypeScript type checking across all workspaces
  • pnpm format - Format code across all workspaces with Prettier
  • pnpm format:check - Check code formatting across all workspaces with Prettier
  • pnpm test - Run tests across all workspaces
  • pnpm test:e2e - Run Playwright E2E tests in the frontend workspace

Frontend Development

The frontend is built with Next.js and React 19. Key features:

  • App Router: Next.js 16's powerful routing system
  • Server & Client Components: Optimal performance with RSC
  • Material UI: Pre-built UI components with dark theme
  • AI Integration: Ready for AI-powered features
  • Database: Drizzle ORM for type-safe database queries

Running Tests

cd frontend

# Unit tests
pnpm test

# E2E tests
pnpm test:e2e

Project Structure

norberts-spark/
├── frontend/           # Next.js + React frontend
│   ├── src/
│   │   ├── app/       # Next.js App Router
│   │   ├── view/      # View layer (components, hooks)
│   │   ├── domain/    # Domain layer (entities, schemas)
│   │   ├── application/ # Application layer (use cases)
│   │   ├── infrastructure/ # Infrastructure layer (DB, API)
│   │   └── test/      # Test utilities
│   ├── e2e/           # Playwright E2E tests
│   └── public/        # Static assets
├── backend/           # Fastify TypeScript API
│   ├── src/
│   ├── docker-compose.yml  # PostgreSQL Docker configuration
│   ├── init-scripts/       # PostgreSQL initialization scripts
│   └── .env.example        # Environment variables template
├── turbo.json         # Turborepo configuration
├── pnpm-workspace.yaml# PNPM workspace configuration
└── package.json       # Root package.json

Contributing

  1. Create a new branch
  2. Make your changes
  3. Run linting and tests
  4. Submit a pull request

License

GPLv3

This project is licensed under the GNU General Public License version 3 (GPLv3). By using, modifying, or distributing this software, you agree to comply with the terms of the GPLv3 license.

The GNU General Public License version 3 (GPLv3) is a strong “copyleft” open-source license that lets anyone use, modify, and redistribute software, but requires that any distributed copies or derivatives remain under the same license and include source code. It ensures users can run, study, and change the software, adds explicit patent protection so contributors cannot later sue users over patent claims related to the code, and prevents practices like “tivoization” (where modified versions are blocked from running on hardware). In short, it guarantees software freedom persists downstream while protecting users from legal and technical restrictions.

For more details, see the LICENSE file.