Files
ArchiTools/docs/architecture/SYSTEM-ARCHITECTURE.md
Marius Tarau 4c46e8bcdd Initial commit: ArchiTools modular dashboard platform
Complete Next.js 16 application with 13 fully implemented modules:
Email Signature, Word XML Generator, Registratura, Dashboard,
Tag Manager, IT Inventory, Address Book, Password Vault,
Mini Utilities, Prompt Generator, Digital Signatures,
Word Templates, and AI Chat.

Includes core platform systems (module registry, feature flags,
storage abstraction, i18n, theming, auth stub, tagging),
16 technical documentation files, Docker deployment config,
and legacy HTML tool reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:50:25 +02:00

32 KiB
Raw Permalink Blame History

ArchiTools — System Architecture

1. Platform Overview

ArchiTools is a modular internal web dashboard platform built for a group of architecture, urban design, and engineering companies based in Cluj-Napoca, Romania:

  • Beletage SRL — architecture office
  • Urban Switch SRL — architecture and urban projects
  • Studii de Teren SRL — engineering, surveying, GIS, technical studies

The platform centralizes daily operational tools: document registries, generators, templates, inventories, AI-assisted workflows, and technical utilities. It replaces scattered standalone HTML tools and manual processes with a unified, themeable, module-driven dashboard.

Key constraints:

  • Internal-first deployment (external/guest access planned for later phases)
  • Romanian UI labels; English code and comments
  • On-premise Docker deployment behind reverse proxy
  • localStorage as initial persistence layer, abstracted for future database/object store migration
  • Must function as a module platform, not a monolithic application

2. High-Level Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        NGINX PROXY MANAGER                         │
│                   (TLS termination, routing)                        │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                     DOCKER CONTAINER (Next.js)                      │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │                    PRESENTATION LAYER                         │  │
│  │  App Shell  │  Sidebar  │  Theme  │  i18n  │  Module Routes  │  │
│  └──────────────────────────┬────────────────────────────────────┘  │
│                              │                                      │
│  ┌──────────────────────────┴────────────────────────────────────┐  │
│  │                      MODULE LAYER                             │  │
│  │                                                               │  │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐        │  │
│  │  │Registra- │ │  Email   │ │ Word XML │ │ Prompt   │  ...   │  │
│  │  │  tura    │ │Signature │ │Generator │ │Generator │        │  │
│  │  └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘        │  │
│  │       │             │            │             │              │  │
│  └───────┼─────────────┼────────────┼─────────────┼──────────────┘  │
│          │             │            │             │                  │
│  ┌───────┴─────────────┴────────────┴─────────────┴──────────────┐  │
│  │                    CORE SERVICES LAYER                        │  │
│  │                                                               │  │
│  │  Module Registry  │  Feature Flags  │  Storage Abstraction   │  │
│  │  Tagging System   │  i18n Engine    │  Theme Provider        │  │
│  │  Auth Stub        │  Navigation     │  Config                │  │
│  └──────────────────────────┬────────────────────────────────────┘  │
│                              │                                      │
│  ┌──────────────────────────┴────────────────────────────────────┐  │
│  │                      STORAGE LAYER                            │  │
│  │                                                               │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐        │  │
│  │  │ localStorage │  │  MinIO       │  │  Database    │        │  │
│  │  │  (current)   │  │  (planned)   │  │  (planned)   │        │  │
│  │  └──────────────┘  └──────────────┘  └──────────────┘        │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
                               │
            ┌──────────────────┼──────────────────┐
            ▼                  ▼                  ▼
     ┌─────────────┐  ┌──────────────┐  ┌──────────────┐
     │  Authentik   │  │    MinIO     │  │     N8N      │
     │   (SSO)     │  │ (obj store)  │  │ (automation) │
     └─────────────┘  └──────────────┘  └──────────────┘

3. Core Architecture Principles

3.1 Module Platform, Not Monolith

The application is a platform that hosts modules. The shell (layout, sidebar, theme, navigation) exists to load and present modules. No module is assumed to exist at build time — the system must function with zero modules enabled.

3.2 Module Isolation

Each module owns its:

  • Route subtree (/app/(modules)/[module-name]/)
  • Business logic (/modules/[module-name]/)
  • Types, services, hooks, and components
  • Storage namespace (scoped key prefix)
  • Configuration entry in the module registry

Modules must never import from another module's internal directories. Cross-module communication happens exclusively through core services (tagging system, storage abstraction, shared hooks).

3.3 Removability

Disabling a module via feature flag must not produce build errors, runtime errors, or broken navigation. This is enforced by:

  • Config-driven navigation (only enabled modules appear)
  • Dynamic imports for module routes
  • No direct cross-module imports
  • Feature flag guards at route and component boundaries

3.4 Storage Independence

No module or component may call localStorage, sessionStorage, or any browser storage API directly. All persistence flows through the storage abstraction layer, which resolves to the active adapter at runtime.

3.5 Presentation/Logic Separation

UI components receive data via props and hooks. Business logic lives in services/ (pure functions) and hooks/ (stateful logic). Components do not contain data transformation, validation, or persistence logic.


4. Layer Architecture

┌─────────────────────────────────────────────────────┐
│  PRESENTATION LAYER                                 │
│  Next.js App Router pages, layouts, UI components   │
│  shadcn/ui primitives, Tailwind styling             │
│  Theme provider, i18n labels                        │
├─────────────────────────────────────────────────────┤
│  MODULE LAYER                                       │
│  Module-specific components, hooks, services        │
│  Module config, types, route pages                  │
│  Isolated per module, lazy-loadable                 │
├─────────────────────────────────────────────────────┤
│  CORE SERVICES LAYER                                │
│  Module registry, feature flags, navigation         │
│  Storage abstraction, tagging, auth stub            │
│  i18n engine, theme system, config                  │
├─────────────────────────────────────────────────────┤
│  STORAGE / INTEGRATION LAYER                        │
│  Storage adapters (localStorage, MinIO, DB, API)    │
│  External service clients                           │
│  Environment configuration                          │
└─────────────────────────────────────────────────────┘

Layer Rules

Layer May Import From Must Not Import From
Presentation Module Layer, Core Services, Shared
Module Layer Core Services, Shared Other modules
Core Services Shared, Config Module Layer, Presentation
Storage/Integration Config only Everything else

5. Runtime Architecture

5.1 Next.js App Router

The application uses Next.js 15 with the App Router. The routing structure:

app/
├── layout.tsx          → Root layout: providers, shell wrapper
├── page.tsx            → Dashboard home (widget grid)
├── globals.css         → Tailwind base + custom tokens
└── (modules)/          → Route group (no URL segment)
    ├── registratura/
    │   ├── page.tsx    → Module entry page
    │   └── [id]/
    │       └── page.tsx → Detail view
    ├── email-signature/
    │   └── page.tsx
    └── ...

5.2 Client-Side Primary, SSR Where Needed

The platform is primarily a client-side interactive application. Most module pages use "use client" directives because they:

  • Manage complex form state
  • Interact with browser storage
  • Require immediate user feedback
  • Handle drag-and-drop, clipboard, and other browser APIs

Server-side rendering is used for:

  • The shell layout (static structure, fast first paint)
  • Metadata generation
  • Any future API routes serving data to external consumers

5.3 Provider Stack

The root layout wraps the application in a provider stack:

<ThemeProvider>
  <I18nProvider>
    <StorageProvider>
      <FeatureFlagProvider>
        <AppShell>
          {children}
        </AppShell>
      </FeatureFlagProvider>
    </StorageProvider>
  </I18nProvider>
</ThemeProvider>

Each provider is independent and can be replaced or extended without affecting others.


6. Module Isolation Model

6.1 Module Structure

Every module follows a standard directory structure:

src/modules/[module-name]/
├── components/       # Module-specific React components
├── hooks/            # Module-specific React hooks
├── services/         # Pure business logic functions
├── types.ts          # TypeScript interfaces and types
├── config.ts         # Module metadata for the registry
└── index.ts          # Public API barrel export

6.2 Module Registration

Each module exports a ModuleConfig object:

interface ModuleConfig {
  id: string;                    // Unique identifier (kebab-case)
  name: string;                  // Romanian display name
  description: string;           // Romanian description
  icon: string;                  // Lucide icon name
  route: string;                 // Base route path
  category: ModuleCategory;      // Grouping for navigation
  enabled: boolean;              // Default enabled state
  featureFlag: string;           // Flag key in feature flag system
  requiredRole?: UserRole;       // Minimum role (future use)
  visibility?: Visibility;       // internal | admin | public
  version: string;               // Semver
  storageNamespace: string;      // Storage key prefix
}

The central module registry (src/config/modules.ts) imports all module configs and provides them to the navigation system and feature flag guards.

6.3 Module Lifecycle

1. Module config registered in modules.ts
2. Feature flag checked at runtime
3. If enabled: route is accessible, nav item visible
4. Module page loads (dynamic import possible)
5. Module initializes its hooks/services
6. Module reads/writes through storage abstraction
7. If disabled: route returns redirect/404, nav item hidden

7. Core Systems Overview

7.1 Module Registry

Location: src/core/module-registry/

Central catalog of all available modules. Provides:

  • List of all registered modules with metadata
  • Lookup by ID, route, or category
  • Filtering by enabled state, visibility, role
  • Module category grouping for navigation

The registry is the single source of truth for what modules exist. Navigation, routing guards, and the dashboard widget grid all read from it.

7.2 Feature Flags

Location: src/core/feature-flags/

Controls module activation and experimental feature visibility.

interface FeatureFlag {
  key: string;
  enabled: boolean;
  scope: 'module' | 'feature' | 'experiment';
  requiredRole?: UserRole;
  description: string;
}

Flag resolution order:

  1. Environment variable override (NEXT_PUBLIC_FLAG_*)
  2. Runtime config (src/config/flags.ts)
  3. Default from module config

Flags are checked via the useFeatureFlag(key) hook and the <FeatureGate flag="key"> component wrapper.

7.3 Storage Abstraction

Location: src/core/storage/

All data persistence flows through a StorageService interface:

interface StorageService {
  get<T>(namespace: string, key: string): Promise<T | null>;
  set<T>(namespace: string, key: string, value: T): Promise<void>;
  delete(namespace: string, key: string): Promise<void>;
  list(namespace: string): Promise<string[]>;
  clear(namespace: string): Promise<void>;
}

Adapters:

Adapter Status Use Case
LocalStorageAdapter Current Browser-local persistence, demo/dev mode
MinIOAdapter Planned File and object storage (signatures, templates)
DatabaseAdapter Planned Structured data (registry entries, inventory)
APIAdapter Planned External service delegation

The active adapter is resolved at startup from environment configuration. Modules never know which adapter is active.

Namespace isolation: Each module operates within its own namespace (e.g., registratura:entries, password-vault:credentials). Modules cannot read or write to another module's namespace without explicit cross-module service mediation.

7.4 Tagging System

Location: src/core/tagging/

A cross-module tagging service used by multiple modules to categorize and link entities.

Tags are structured objects:

interface Tag {
  id: string;
  label: string;              // Romanian display label
  category: TagCategory;      // project | client | domain | custom
  color?: string;
  metadata?: Record<string, unknown>;
  createdAt: string;
}

The tagging system provides:

  • Tag CRUD operations (stored via storage abstraction)
  • Tag selector component (shared UI)
  • Tag filtering and search
  • Tag usage tracking across modules

Modules that use tags: Registratura, Prompt Generator, Word Templates, Digital Signatures, IT Inventory, Address Book.

7.5 Internationalization (i18n)

Location: src/core/i18n/

Current implementation: Romanian-only with structured label access for future multi-language support.

Labels are organized by module namespace:

const labels = {
  common: {
    save: 'Salvează',
    cancel: 'Anulează',
    delete: 'Șterge',
    search: 'Caută',
    // ...
  },
  registratura: {
    title: 'Registratură',
    newEntry: 'Înregistrare nouă',
    // ...
  },
};

Access pattern: useLabel('registratura.newEntry') or <Label k="common.save" />.

The system is designed so that adding a second language requires only adding translation files, not changing component code.

7.6 Theme System

Location: src/core/theme/

Dark/light theme support using CSS custom properties and Tailwind's dark: variant.

Design tokens:

  • Background and surface colors
  • Text hierarchy (primary, secondary, muted)
  • Border and divider colors
  • Accent colors per company (Beletage, Urban Switch, Studii de Teren)
  • Semantic colors (success, warning, error, info)

Theme preference is persisted in storage and respects system preference as default. The theme provider exposes useTheme() with theme, setTheme, and toggleTheme.

Visual style: professional, technical, card-based dashboard. No playful or consumer-oriented aesthetics.

7.7 Auth Stub

Location: src/core/auth/

Current state: no authentication enforced (internal network only).

The auth module provides a stub interface that modules can code against:

interface AuthContext {
  user: User | null;
  role: UserRole;             // 'admin' | 'user' | 'guest'
  isAuthenticated: boolean;
  company: CompanyId | null;
  permissions: string[];
}

In the current phase, AuthContext returns a default internal user with admin role. When Authentik SSO integration is implemented, the auth module will resolve real user identity from SSO tokens without any module code changes.

Data model fields (visibility, requiredRole, createdBy) are included from day one so that enabling auth does not require data migration.


8. External Integration Points

8.1 Current Infrastructure

ArchiTools runs alongside existing services on the internal network:

Service Integration Type Purpose
Authentik Future SSO provider User authentication and role assignment
MinIO Future storage adapter Object/file storage for documents, signatures, templates
N8N Future webhook/API Workflow automation (document processing, notifications)
Gitea Development Source code hosting
Stirling PDF Dashboard link PDF manipulation (external tool link)
IT-Tools Dashboard link Technical utilities (external tool link)
Filebrowser Dashboard link File management (external tool link)
Uptime Kuma Dashboard widget Service health status
Netdata Dashboard widget Server performance metrics

8.2 Integration Patterns

Dashboard links: External tools appear as navigation entries or dashboard widgets with target="_blank" links. No embedding or API integration needed.

Dashboard widgets: Services like Uptime Kuma and Netdata can expose status endpoints or embed iframes for health/monitoring widgets on the dashboard home.

Storage integration (MinIO): When the MinIO adapter is implemented, modules that manage files (Digital Signatures, Word Templates) will store binary assets in MinIO buckets while keeping metadata in the primary storage.

Automation integration (N8N): Modules can trigger N8N webhooks for automated workflows. Example: Registratura creates a new entry, triggering an N8N workflow that sends a notification or generates a document.

SSO integration (Authentik): The auth stub will be replaced with an Authentik OIDC client. The middleware layer will validate tokens and populate AuthContext. No module code changes required.


9. Data Flow Patterns

9.1 Module Data Read

User action
  → Component calls hook (e.g., useRegistryEntries())
    → Hook calls service function (e.g., getEntries())
      → Service calls StorageService.get(namespace, key)
        → StorageService resolves active adapter
          → Adapter reads from storage backend
            → Data returns up the chain
              → Hook updates state
                → Component re-renders

9.2 Module Data Write

User submits form
  → Component calls hook mutation (e.g., createEntry(data))
    → Hook validates via service (e.g., validateEntry(data))
      → Service calls StorageService.set(namespace, key, data)
        → Adapter writes to storage backend
          → Hook updates local state / invalidates cache
            → Component re-renders with new data

9.3 Cross-Module Data (via Tagging)

User tags an entity in Module A
  → Module A calls TaggingService.addTag(entityId, tagId)
    → Tag association stored in tagging namespace

User filters by tag in Module B
  → Module B calls TaggingService.getEntitiesByTag(tagId)
    → Returns entity IDs across modules
      → Module B fetches its own entities matching those IDs

9.4 Feature Flag Check

Route or component renders
  → <FeatureGate flag="module.registratura">
    → useFeatureFlag('module.registratura')
      → Checks env override → config → default
        → Returns boolean
          → Children render or fallback shown

10. Deployment Architecture

10.1 Container Structure

┌──────────────────────────────────────────────────┐
│              Ubuntu Server (on-premise)           │
│                                                   │
│  ┌─────────────────────────────────────────────┐  │
│  │           Docker (via Portainer)             │  │
│  │                                             │  │
│  │  ┌──────────────┐  ┌──────────────────────┐ │  │
│  │  │  ArchiTools   │  │  Other containers    │ │  │
│  │  │  (Next.js)   │  │  (Authentik, MinIO,  │ │  │
│  │  │  Port: 3000  │  │   N8N, Gitea, etc.)  │ │  │
│  │  └──────┬───────┘  └──────────────────────┘ │  │
│  │         │                                    │  │
│  └─────────┼────────────────────────────────────┘  │
│            │                                       │
│  ┌─────────┴─────────────────────────────────────┐ │
│  │         Nginx Proxy Manager                    │ │
│  │    tools.internal.domain → localhost:3000      │ │
│  └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

10.2 Docker Configuration

Dockerfile: Multi-stage build.

  1. deps stage — installs Node.js dependencies
  2. build stage — runs next build, produces standalone output
  3. runtime stage — minimal Node.js image, copies standalone build, exposes port 3000

docker-compose.yml: Single service definition for ArchiTools. Environment variables passed from .env file. Optional volume mounts for persistent data if needed beyond localStorage.

10.3 Environment Variables

# Application
NEXT_PUBLIC_APP_URL=https://tools.internal.domain
NEXT_PUBLIC_APP_ENV=production

# Feature flags (override defaults)
NEXT_PUBLIC_FLAG_MODULE_REGISTRATURA=true
NEXT_PUBLIC_FLAG_MODULE_AI_CHAT=false

# Future: Storage backend
STORAGE_BACKEND=localStorage
MINIO_ENDPOINT=minio.internal.domain
MINIO_ACCESS_KEY=...
MINIO_SECRET_KEY=...

# Future: Auth
AUTHENTIK_ISSUER=https://auth.internal.domain
AUTHENTIK_CLIENT_ID=...
AUTHENTIK_CLIENT_SECRET=...

10.4 Build and Deploy Flow

Developer pushes to Gitea
  → (future: CI pipeline builds image)
  → Docker image built (manual or Watchtower auto-update)
  → Portainer deploys/restarts container
  → Nginx Proxy Manager routes traffic
  → Users access via internal domain

11. Scalability Considerations

11.1 Current Scale

  • Users: ~520 internal staff across three companies
  • Data volume: Low (hundreds to low thousands of records per module)
  • Concurrency: Minimal (localStorage is per-browser, no shared state conflicts)

11.2 Growth Path

Concern Current Growth Path
Data persistence localStorage (per-browser) Database + MinIO (shared, centralized)
Authentication None (network trust) Authentik SSO with RBAC
Multi-user data Isolated per browser Centralized with user ownership
File storage Not supported MinIO buckets per module
Search Client-side filter Server-side indexed search
API access None Next.js API routes for external consumers
Automation Manual N8N webhooks triggered by module events

11.3 Module Scaling

New modules are added by:

  1. Creating the module directory structure
  2. Registering the module config
  3. Adding the feature flag
  4. Creating the route pages

No changes to the shell, navigation, or other modules are required. The navigation rebuilds itself from the registry.


12. Security Boundaries

12.1 Current Phase: Internal Network Trust

┌─────────────────────────────────────────────┐
│            Internal Network                  │
│                                              │
│   ┌──────────┐     ┌──────────────────────┐ │
│   │  Users    │────▶│    ArchiTools        │ │
│   │ (trusted) │     │  (no auth required)  │ │
│   └──────────┘     └──────────────────────┘ │
│                                              │
│   Security: network-level only               │
│   Data: browser-local, no shared secrets     │
│   Risk: low (internal, trusted users)        │
└─────────────────────────────────────────────┘

Current security model:

  • Network perimeter security via Crowdsec and firewall rules
  • No application-level authentication
  • No sensitive data in localStorage (password vault uses demo-grade encryption)
  • No external API endpoints exposed
  • All data stays in the user's browser

12.2 Future Phase: SSO + Role-Based Access

┌──────────────────────────────────────────────────────────┐
│                                                          │
│   ┌──────────┐    ┌────────────┐    ┌──────────────────┐│
│   │  Users    │───▶│ Authentik  │───▶│    ArchiTools    ││
│   │(internal/ │    │   (SSO)    │    │ (auth enforced)  ││
│   │ external) │    └────────────┘    └──────────────────┘│
│   └──────────┘                                           │
│                                                          │
│   Security: SSO + RBAC + module permissions              │
│   Data: centralized DB + MinIO with access control       │
│   Roles: admin, user, guest                              │
│   Visibility: per-field, per-module, per-company         │
└──────────────────────────────────────────────────────────┘

Planned security layers:

  • Authentik OIDC authentication (SSO)
  • Role-based module access (admin, user, guest)
  • Company-scoped data visibility
  • Per-field visibility metadata (internal, admin, public)
  • API route protection via middleware token validation
  • Audit logging for sensitive operations

Design-for-security decisions made now:

  • All data models include visibility and createdBy fields
  • Module configs include requiredRole field
  • Feature flags support role-based activation
  • Auth context interface defined (stubbed with defaults)
  • Storage namespace isolation prevents cross-module data leaks

Appendix A: Technology Decisions

Decision Choice Rationale
Framework Next.js 15 (App Router) Modern React with file-based routing, SSR capability, API routes
Language TypeScript Type safety across modules, better refactorability
Styling Tailwind CSS Utility-first, consistent with shadcn/ui, theme support
Component library shadcn/ui Copy-paste components, full control, professional aesthetic
Deployment Docker Consistent with existing infrastructure (Portainer)
Initial storage localStorage Zero infrastructure, immediate development start
Storage pattern Adapter abstraction Allows migration without module changes
Auth pattern Stub with interface Enables SSO integration without refactoring

Appendix B: Module Catalog

Module ID Category Status
Dashboard dashboard Core Planned
Registratura registratura Registry Planned
Email Signature Generator email-signature Generators Planned (legacy exists)
Word XML Generators word-xml Generators Planned (legacy exists)
Digital Signatures & Stamps digital-signatures Assets Planned
Password Vault password-vault Security Planned
IT Inventory it-inventory Infrastructure Planned
Address Book address-book Contacts Planned
Prompt Generator prompt-generator AI Tools Planned
Word Template Library word-templates Templates Planned
Tag Manager tag-manager Administration Planned
Mini Utilities mini-utilities Tools Planned
AI Chat ai-chat AI Tools Planned