Skip to content

lbodlev888/inventory_app

Repository files navigation

Inventory App

A web-based asset inventory and lifecycle-tracking system for institutions (schools, universities, public offices, companies) that manage fleets of active equipment — computers, projectors, printers, network gear, and similar hardware.

Each asset is tracked through its functional state over time, every change is recorded in a tamper-evident audit journal, and the data can be exported as reports (Excel / PDF) or raw backups (JSON / CSV). User access is governed by role-based access control (RBAC), and an optional REST API lets external systems integrate with the inventory.

The end-user interface is in Romanian; this document and the codebase are in English.


Table of contents


Features

Asset / equipment management

  • Create, edit, view, and delete equipment records (name, serial number, category, location, free-form notes).
  • Functional-state tracking — every asset carries one of six lifecycle statuses:
    Value Meaning (RO label)
    perfect Stare perfectă
    ok Functional
    testing În testare
    repair În reparatie
    broken Defect
    retired Scos din uz
  • Quick status-change action straight from the list/detail views (no full edit needed).
  • Filtering & search by status, category, location, and free-text (name or serial).
  • Categories management (create, rename, delete) to organize the asset fleet.

Reporting & export

  • Excel (.xlsx) reports via ExcelJS — includes a data sheet (auto-filter enabled) plus a summary sheet with per-status counts and totals.
  • PDF reports via PDFKit — landscape, institution-branded header, filter summary, status breakdown, and a paginated table.
  • Reports honor the same filters as the equipment list, so you can export exactly the subset you're looking at.
  • Database backups: full JSON export of all tables, or per-table CSV export (UTF-8 BOM for Excel compatibility).

Users, authentication & RBAC

  • First-run setup wizard — when the database has no users, the app forces creation of the initial admin account.
  • Session-based login with Argon2id password hashing (with automatic rehash-on-login when parameters change).
  • Three rolesviewer, editor, admin — enforced on every route (see Roles & permissions).
  • Admin user management: create users, change roles, reset passwords, delete users (with guards against self-lockout).
  • Self-service profile: each user can update their display name / email and change their own password.

Password reset by email

  • "Forgot password" flow generates a single-use, 30-minute, SHA-256-hashed token and emails a reset link via SMTP (Nodemailer).
  • Privacy-preserving: the response is identical whether or not the email exists.
  • If SMTP is not configured, the reset link is logged to the console instead — convenient for development.
  • Admins can also reset any user's password directly from the admin panel.

Audit journal

  • A full audit log records who did what and when: actor (user or API key), action, entity type, entity id, and a JSON details payload.
  • Covers equipment, categories, users, API keys, and exports (create / update / delete / status_change / rename / reset_password / revoke / export …).
  • Browsable in the admin UI with a configurable row limit.

REST API

  • Versioned (/api/v1/...) JSON API authenticated by API keys (sent via X-API-Key or Authorization: Bearer).
  • API keys are created/revoked in the admin UI, stored only as SHA-256 hashes, and track last_used_at.
  • Endpoints for listing statuses & categories, listing/reading equipment (with filters), creating equipment, and patching equipment status. All write actions are audited under the key's identity.

Tech stack

Layer Technology
Runtime Node.js (v26-alpine in Docker)
Web framework Express 5
Views EJS + express-ejs-layouts (server-rendered)
Database PostgreSQL (via pg)
Cache / sessions Redis (via ioredis + connect-redis)
Auth express-session, Argon2id (argon2)
Email Nodemailer (SMTP)
Reports ExcelJS (xlsx), PDFKit (pdf)
Misc morgan (logging), method-override, cookie-parser, dotenv
Deployment Docker (multi-stage build) + Docker Compose

Architecture

                        ┌──────────────────────────┐
   Browser / API  ───►  │   Express app (Node.js)  │
                        │  - EJS server-rendered UI│
                        │  - REST API (/api/v1)    │
                        └───────┬───────────┬──────┘
                                │           │
                   sessions &   │           │  data
                     cache      ▼           ▼
                          ┌─────────┐  ┌──────────────┐
                          │  Redis  │  │  PostgreSQL  │
                          └─────────┘  └──────────────┘
  • src/app.js — bootstrap: loads config, initializes DB / cache / mailer, wires up middleware (sessions, logging, method-override) and all route modules, then starts the HTTP server. It also exposes res.locals.user and a needsSetup flag (true when no users exist) to every view.
  • src/db.js — PostgreSQL pool, idempotent schema creation (CREATE TABLE IF NOT EXISTS …) on startup, default-category seeding, and the canonical EQUIPMENT_STATUSES list.
  • src/cache.js — lazy Redis client; gracefully degrades to in-memory sessions and no caching if REDIS_URL is unset or Redis is unreachable.
  • src/auth.jsrequireAuth / requireRole(...) middleware and the hasWrite helper.
  • src/password.js — Argon2id hashing / verification / rehash detection.
  • src/mailer.js — Nodemailer transport + password-reset email (console fallback when SMTP is absent).
  • src/audit.jslogAudit(...) plus actor helpers for sessions and API keys.
  • src/routes/*.routes.js — feature-scoped routers: auth, equipment, categories, admin, backup, reports, profile, api.
  • src/views/ — EJS templates (layout, partials/nav, and one folder per feature area).

Routes are organized so that each module receives the shared db pool via a factory function (equipmentRoutes(db), etc.), keeping dependencies explicit and testable.


Data model

Tables are auto-created on first start (see src/db.js):

  • usersusername (unique), password_hash, display_name, email (case-insensitive unique), role (viewer/editor/admin), created_at.
  • categoriesname (unique); seeded with a default General category.
  • equipmentname, serial, category_id (FK → categories, ON DELETE SET NULL), status (checked enum), location, notes, created_at, updated_at.
  • api_keysname, key_hash, created_by (FK → users), created_at, last_used_at, revoked.
  • password_resetsuser_id (FK → users, ON DELETE CASCADE), token_hash (unique), expires_at, used_at.
  • audit_logcreated_at, actor_id, actor_label, action, entity_type, entity_id, details (indexed on created_at).

Roles & permissions

Capability viewer editor admin
View equipment / categories / reports
Export reports (xlsx / pdf)
Create / edit equipment
Change equipment status
Create / rename categories
Delete equipment / categories
Manage users & roles
Manage API keys
View audit log
Database export / backup

Every authenticated user can edit their own profile and password.


Getting started (local)

Prerequisites

  • Node.js 18+ (the Docker image uses Node 26)
  • A running PostgreSQL instance
  • (Optional) a running Redis instance — without it, sessions fall back to in-memory and caching is disabled

Steps

# 1. Install dependencies
npm install

# 2. Create your environment file
cp .env.example .env
#    then edit .env — at minimum set DATABASE_URL and a strong SESSION_SECRET

# 3. Run
npm run dev      # development (auto-reload via nodemon)
# or
npm start        # production

Open http://localhost:3000. On first launch (no users yet) you'll be redirected to /setup to create the initial admin account.


Deployment (Docker)

The project ships with a multi-stage Dockerfile and a Docker Compose stack that provisions the full infrastructure (app + PostgreSQL + Redis).

Dockerfile (multi-stage)

  1. builder stage (node:26-alpine) — installs production dependencies only (npm ci --omit=dev).
  2. runtime stage (node:26-alpine) — copies installed node_modules and the app source, runs as the unprivileged node user, exposes port 3000, and starts node src/app.js.

This keeps the final image small and free of build-only tooling.

Compose stack

docker-compose.yaml defines three services:

  • app — the inventory application (build/tag the image as inventory_base_image), published on 3000:3000, reading .env.
  • db — PostgreSQL, with data persisted to a host volume ./postgres_data.
  • cache — Redis.

All services use restart: unless-stopped.

Steps

# 1. Build the application image (tag must match docker-compose.yaml)
docker build -t inventory_base_image .

# 2. Prepare environment
cp .env.example .env
#    For Compose, the service hostnames are 'db' and 'cache', e.g.:
#    DATABASE_URL=postgresql://user:password@db:5432/inventory_app
#    REDIS_URL=redis://cache:6379
#    Also set Postgres' own vars (POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB)
#    in .env so the db service and DATABASE_URL stay in sync.

# 3. Launch the stack
docker compose up -d

# 4. Tail logs / check status
docker compose logs -f app

The app will be available on http://localhost:3000. The database schema is created automatically on first boot, and PostgreSQL data survives restarts thanks to the ./postgres_data volume.

Note: because db and app both read .env, make sure the credentials in DATABASE_URL match the POSTGRES_* variables the Postgres image expects.


Configuration

All configuration is via environment variables (see .env.example):

Variable Description Default
PORT HTTP port the app listens on 3000
SESSION_SECRET Secret used to sign session cookies — set a strong value dev_secret (dev only)
DATABASE_URL PostgreSQL connection string local default in code
REDIS_URL Redis connection string; empty disables Redis (in-memory sessions) (empty)
INSTITUTION_NAME Header shown on generated PDF reports Inventar IT
SMTP_HOST SMTP server host; if empty, reset links are logged to console (empty)
SMTP_PORT SMTP port 587
SMTP_SECURE true for port 465 (SMTPS), false for 587/25 (STARTTLS) false
SMTP_USER SMTP username (empty)
SMTP_PASS SMTP password (empty)
MAIL_FROM "From" address used for outgoing email no-reply@localhost

REST API

Base path: /api/v1. Authenticate with an API key (created in Admin → API Keys) sent as either header:

X-API-Key: <key>
# or
Authorization: Bearer <key>
Method & path Description
GET /api/v1/statuses List the equipment status enum
GET /api/v1/categories List categories
GET /api/v1/equipment List equipment (filters: status, categoryId, location, q)
GET /api/v1/equipment/:id Get a single equipment record
POST /api/v1/equipment Create equipment (name required)
PATCH /api/v1/equipment/:id/status Update an equipment's status

Example:

curl -H "X-API-Key: $KEY" \
  "http://localhost:3000/api/v1/equipment?status=repair"

curl -X POST -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"name":"Projector A1","serial":"PRJ-001","status":"ok"}' \
  http://localhost:3000/api/v1/equipment

All write operations through the API are recorded in the audit log under the API key's identity.


Security notes

  • Passwords are hashed with Argon2id (19 MiB memory cost, time cost 2) and rehashed automatically on login if parameters change.
  • Session cookies are httpOnly, sameSite=lax, and signed; sessions are stored in Redis when available.
  • API keys and password-reset tokens are persisted only as SHA-256 hashes — the plaintext key is shown once at creation time.
  • Password-reset tokens are single-use and expire after 30 minutes.
  • The "forgot password" endpoint returns an identical response regardless of whether the email exists (no account enumeration).
  • All SQL uses parameterized queries; export endpoints restrict columns/tables to an explicit allow-list.

For production, always set a strong SESSION_SECRET, serve behind HTTPS (and consider enabling cookie.secure), and use real SMTP credentials.

About

Aplicatia de inventariere a echipamentelor din cadrul unei institutii de invatamant elaborate in cadrul lucrarii de diploma

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors