Skip to content

About

Full-stack microservice management panel with role-based authorization, RabbitMQ RPC, PostgreSQL, and Gemini AI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

24 Commits

Folders and files

Repository files navigation

πŸš€ AI-Integrated Microservice Management Panel

β–Ά Try the interactive demo β€” sign in as each role and watch the authorization bite. Runs entirely in the browser on mock data; the real system needs Docker.

CI

A full-stack demo system built around asynchronous service communication: RabbitMQ (async publish + RPC request/reply), PostgreSQL logging with JSONB and a trigger/procedure, document analysis powered by Google Gemini, and a Next.js panel whose every screen is gated by permissions the server actually enforces.

πŸ›  Tech Stack

Layer Technology
Frontend Next.js (React + TailwindCSS)
Backend Node.js + Express
Database PostgreSQL (JSONB, triggers, procedures, pgvector)
Message Broker RabbitMQ (async + RPC)
Cache Redis (optional)
AI Google Gemini (file analysis)
Containers Docker & Docker Compose

🌟 Features

  • Authentication & dynamic RBAC: JWT + bcrypt login, with permissions stored in role_permissions rather than hard-coded. Every guarded route resolves the caller's permissions from the database on each request, so a Supervisor granting or revoking a permission through PUT /admin/permissions/:roleId takes effect on the very next call β€” the user does not need to log in again.
  • RabbitMQ + RPC: GET /rpc-test publishes a message to task_queue with a replyTo queue and a correlationId, then waits for the consumer's reply β€” a full request/reply round trip.
  • PostgreSQL: Records are stored in a JSONB column; an AFTER INSERT trigger calls a plpgsql procedure that writes an audit row automatically.
  • Gemini AI: An uploaded .txt file is summarized by Gemini, with fallback across model versions.
  • Panel bound to the real API: The Next.js frontend logs in, stores the JWT and reads its permissions from GET /auth/me β€” never from the token payload. Each feature card declares the permission code it needs; a card the caller cannot use renders locked, with a button that fires the request anyway and prints the server's 403, so the enforcement is visible rather than claimed.
  • Retrieval-augmented answers: Documents are split into overlapping chunks, embedded, and stored in pgvector. A question is embedded too, the nearest chunks are retrieved by cosine distance, and only those passages are given to the model β€” which then has to cite them. The retrieved passages come back with the answer, because otherwise a reader cannot tell a grounded answer from a fluent invention. POST /documents/search exposes the retrieval half on its own, so the ranking can be inspected directly rather than guessed at through the generated text.
  • Redis caching: The per-request permission lookup is served from Redis when it is available. The interesting part is what the cache must not break β€” a Supervisor's edit still has to apply to the very next request β€” so setRolePermissions drops the role's key after the transaction commits, and only that role's key. The cache is optional: with no REDIS_URL every lookup goes to PostgreSQL, and if Redis fails mid-request the read falls through rather than erroring.
  • Docker Compose: Postgres, RabbitMQ, Redis, backend and frontend start with one command, with healthchecks so the backend waits until its dependencies are actually ready.

⚠️ Current status β€” what is and isn't implemented

Being explicit so nobody is misled:

Area Status
RabbitMQ async + RPC βœ… Implemented (src/config/rabbitmq.js, src/routes/rpc.routes.js)
PostgreSQL JSONB + trigger + procedure βœ… Implemented (db/schema.sql)
Gemini document analysis βœ… Implemented β€” .txt only
Docker Compose orchestration βœ… Implemented
Authentication (JWT + bcrypt) βœ… Implemented
Server-enforced dynamic RBAC βœ… Implemented β€” permissions live in the database and are checked per request
Automated tests + CI βœ… 99 integration tests against a real PostgreSQL, run on Node 20 and 22, twice β€” with and without the cache
Frontend wired to real auth βœ… Implemented β€” login, token storage, and permission-gated panels
Redis caching βœ… Implemented β€” optional, in front of the permission lookup, invalidated on write
RAG / embeddings βœ… Implemented β€” pgvector store, chunked documents, cited answers
RabbitMQ paths in CI ⬜ Not yet β€” covering them needs a broker service container; only their failure paths are tested today

πŸ“‘ API

All routes except /health and /auth/login require an Authorization: Bearer <token> header. Permission codes are enforced server-side.

Note that /auth/register is not open self-service. It takes the new user's role from the request body, so leaving it unauthenticated would let anyone hand themselves a Supervisor account and rewrite the permission matrix. Creating accounts is an administrative act and sits behind users:write; the first Supervisor is seeded by npm run setup-db, and there is deliberately no bootstrap path through the API.

Method Route Required permission
GET /health β€”
POST /auth/login β€”
POST /auth/register users:write
GET /auth/me authenticated
GET /records records:read
POST /records records:write
POST /ai-analyze ai:analyze
GET /rpc-test rpc:execute
GET /admin/permissions permissions:manage
PUT /admin/permissions/:roleId permissions:manage
GET /admin/users users:read
GET /documents documents:read
POST /documents documents:write
DELETE /documents/:id documents:write
POST /documents/search documents:read
POST /documents/ask documents:read

Default roles: Supervisor (all permissions), School (read/write/analyze), Company (read/analyze/rpc), Student (read/analyze).

Permission codes: records:read, records:write, ai:analyze, rpc:execute, permissions:manage, users:read, users:write, documents:read, documents:write. They are rows in the permissions table, so a Supervisor edits who holds what at runtime rather than through a redeploy.

Embeddings without an API key

GEMINI_API_KEY gets gemini-embedding-001, asked for 768 dimensions to fit the vector(768) column and renormalised afterwards, since truncating an embedding costs it its unit length.

Without a key the app falls back to a deterministic hashing embedder. Be clear about what that is: it matches documents that share words, not documents that share meaning β€” "car" and "automobile" are as unrelated to it as "car" and "banana". It exists so the pipeline (chunking, storage, cosine search, ranking, citations) can be exercised in CI and by anyone who clones the repo, and the panel labels it as a fallback rather than passing it off as semantic search.

Every chunk records the model that embedded it, and searches filter on that. Vectors from different models occupy unrelated coordinate spaces, so mixing them does not return worse results β€” it returns confidently ranked nonsense.

πŸ“ Project structure

index.js                    entry point β€” connects services, starts the server
src/
  app.js                    builds the Express app (exported so tests can drive it)
  config/db.js              PostgreSQL pool
  config/rabbitmq.js        broker connection + task_queue consumer
  config/redis.js           optional cache; every helper degrades to a no-op
  services/embedding.service.js  Gemini or offline embeddings, same dimensions
  services/document.service.js   chunking, ingestion, cosine search
  middleware/auth.js        authenticate() and requirePermission()
  middleware/errorHandler.js
  routes/                   auth Β· records Β· ai Β· rpc Β· admin
  services/permission.service.js
db/schema.sql               full schema, idempotent
scripts/setup-db.js         applies the schema, seeds the first Supervisor
tests/                      integration suite + globalSetup/globalTeardown
frontend/
  src/lib/api.js            fetch wrapper β€” attaches the token, normalises errors
  src/lib/auth-context.js   session state; permissions come from GET /auth/me
  src/app/login/page.js     login screen
  src/app/page.js           dashboard
  src/components/           Panel (permission gate) Β· matrix Β· records Β· rpc Β· ai

πŸ§ͺ Tests

npm test              # 91 tests; the 8 cache tests skip without Redis

# Same suite with the cache in front of every permission lookup β€” 99 tests.
# Use a dedicated database index so a run cannot evict a dev server's entries.
TEST_REDIS_URL=redis://localhost:6379/15 npm test

npm run test:coverage # with a coverage report

CI runs both passes on Node 20 and 22. The second one is the real cache test: if invalidation were wrong, the existing authorization assertions would start failing rather than some cache-specific assertion.

After adding a dependency on Windows

Run npm run lock:refresh, not just npm install.

An incremental npm install on Windows drops the wasm32-wasi optional packages (@emnapi/*) from the lock while leaving @napi-rs/wasm-runtime's dependency on them in place. Windows never installs those, so nothing looks wrong locally β€” but on Linux npm ci resolves the range, finds no matching entry, and refuses to install. It fails only in CI, at the very first step. lock:refresh regenerates the lock from scratch and then verifies it with npm ci --dry-run.

The suite runs against a real PostgreSQL instance, not mocks β€” the schema, the audit trigger and the JSONB queries are all genuinely exercised. tests/globalSetup.js creates a throwaway microservice_panel_test database, applies db/schema.sql, and drops it afterwards. It refuses to run at all unless the database name ends in _test, so a mistyped variable cannot touch real data.

What is covered: registration and login (including bcrypt hashing, timing-safe rejection of unknown emails, expired and forged tokens), permission enforcement per route, permissions granted and revoked mid-session on an already-issued token, pagination limits, the audit trigger, upload validation, and the error handler not leaking internals. Three tests specifically pin the registration route shut: an anonymous caller gets 401, a Student registering anyone gets 403, and a Student cannot mint a Supervisor β€” each asserting the row was never created, not merely that the status code was right.

Current line coverage is ~79%. The gap is rpc.routes.js and config/rabbitmq.js, which need a live broker; only their failure paths are covered today.

βš™οΈ Setup

Option A β€” Docker (recommended)

  1. Docker and Docker Compose must be installed.
  2. Provide your Gemini key to the environment (optional, for the AI feature):
    export GEMINI_API_KEY=your_key   # Windows PowerShell: $env:GEMINI_API_KEY="your_key"
  3. Start all services:
    docker-compose up --build
  4. Services:

Option B β€” Local development

Backend:

# Prepare the .env file
cp .env.example .env        # Windows: copy .env.example .env
# Fill in .env: DB details, GEMINI_API_KEY, and a JWT_SECRET.
# The server refuses to start without JWT_SECRET. Generate one with:
#   node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
# Set SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD to create the first Supervisor.

npm install
npm run setup-db            # applies db/schema.sql, seeds the first Supervisor
npm start                   # backend at http://localhost:3006

Frontend:

cd frontend
cp .env.example .env.local  # set NEXT_PUBLIC_API_URL if needed
npm install
npm run dev                 # http://localhost:3000

You need to run PostgreSQL, RabbitMQ and (optionally) Redis separately for local development, or just start them from Docker: docker-compose up -d postgres_db rabbitmq redis.

Leaving REDIS_URL blank is fine β€” the server says so at boot and reads permissions straight from PostgreSQL.

NEXT_PUBLIC_API_URL and the backend's PORT are read independently and will not agree on their own β€” if you move the backend off 3006, change both.

Signing in the first time

npm run setup-db creates one Supervisor from SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD; sign in with those. Everyone else is created from that account, since /auth/register requires users:write:

curl -X POST http://localhost:3006/auth/register \
  -H "Authorization: Bearer <supervisor-token>" \
  -H "Content-Type: application/json" \
  -d '{"email":"student@example.com","password":"at-least-8-chars","role":"Student"}'

Grab <supervisor-token> from the token field of POST /auth/login.

πŸ” Security / Environment Variables

Secrets (GEMINI_API_KEY, DB password) are no longer hard-coded; they are read from .env, which is not committed to git. See .env.example and frontend/.env.example for sample values.

πŸ—„οΈ Database

  • db/schema.sql is the source of truth for the schema, trigger and procedure. npm run setup-db (scripts/setup-db.js) applies it and then seeds the first Supervisor from SEED_ADMIN_*. Every statement is idempotent, so re-running it never clobbers a Supervisor's runtime permission edits.
  • db/seed.sql is the original schema from the first version of the project, kept for reference only β€” do not run it. Its table names are still the Turkish ones used before the rename (genel_veriler β†’ records, sistem_loglari β†’ audit_logs); the file header maps them all.

πŸ‘₯ Contributors

See CONTRIBUTORS.md.

About

Full-stack microservice management panel with role-based authorization, RabbitMQ RPC, PostgreSQL, and Gemini AI.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages