LAIM: Lab Asset Inventory Manager

[+] Status: Active [+] Origin: Work Project [+] Date: 2025.01
>> TECH_STACK:
[FastAPI][PostgreSQL][SQLAlchemy][Docker][Tailwind CSS][JWT][Python 3.12][Netdisco][LibreNMS]

A complete rewrite of a legacy PHP inventory system I built as a college capstone project. LAIM transforms manual spreadsheet-based asset tracking into an automated, API-driven platform with multi-source device discovery, role-based access control, and modern deployment patterns.

The original system was functional but showed its age -table-based layouts, popup windows, hardcoded dropdown values, and no API layer. LAIM addresses these limitations while adding capabilities that weren't feasible in the original architecture: automated device sync from Netdisco and LibreNMS, backup/restore functionality, and a proper separation of concerns.

Open Source

This project is open source! Check out the code, fork it, or give it a star if you find it useful.

🚫 BEFORE: PHP Capstone (2024)
  • PHP + MySQL + Nginx stack
  • Table-based layouts, inline CSS
  • window.open() popup forms
  • Full page refresh for search
  • Hardcoded categories (37 locations!)
  • Manual registration via email
  • Basic session auth, no RBAC
  • No API, server-rendered only
AFTER: LAIM (2025)
  • FastAPI + PostgreSQL + async
  • Tailwind CSS, responsive design
  • Modal dialogs, SPA-like UX
  • Real-time client-side filtering
  • Configurable via settings API
  • Self-service registration with roles
  • JWT auth with RBAC enforcement
  • RESTful API with OpenAPI docs

Modern async Python stack with external integrations for automated device discovery:

FastAPI
Async Web Framework

Modern Python framework with automatic OpenAPI docs, async/await support, and dependency injection

PostgreSQL
Database

Relational database with asyncpg driver for high-performance async queries

SQLAlchemy 2.0
ORM & Migrations

Async ORM with Alembic migrations for schema versioning and type-safe queries

Netdisco + LibreNMS
Device Discovery

Multi-source network discovery with intelligent merge logic for automated asset population

EXTERNAL SOURCES
📡 Netdisco L2/L3 Discovery
📊 LibreNMS SNMP Monitoring
📄 CSV Import Bulk Upload
Sync / Import
APPLICATION LAYER
FastAPI Async Routes + Auth
SQLAlchemy Async ORM
APScheduler Background Jobs
Async Queries
DATA LAYER
PostgreSQL Inventory Items Users & Roles Backups Sync Logs

01 Async Database Operations

Session Management

from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import create_async_engine engine = create_async_engine( DATABASE_URL, pool_size=10, max_overflow=20 ) async def get_db() -> AsyncGenerator: async with AsyncSession(engine) as session: yield session

Route with Dependency Injection

@app.get("/api/items") async def list_items( db: AsyncSession = Depends(get_db), user: User = Depends(get_current_user), search: str = None, item_type: ItemType = None ): query = select(InventoryItem) .where(InventoryItem.is_active == True) if search: query = query.where( InventoryItem.hostname.ilike(f"%{search}%") ) return await db.execute(query)

02 Multi-Source Device Sync

Deduplication Strategy

# Priority: serial_number > mac_address > hostname+ip def merge_devices(netdisco: list, librenms: list): merged = {} for device in netdisco + librenms: key = ( device.serial_number or device.mac_address or f"{device.hostname}:{device.ip}" ) if key in merged: # LibreNMS has higher data quality merged[key].update(device) else: merged[key] = device return list(merged.values())

Scheduled Sync

from apscheduler.schedulers.asyncio import AsyncIOScheduler scheduler = AsyncIOScheduler() @scheduler.scheduled_job( 'interval', hours=SYNC_INTERVAL_HOURS ) async def scheduled_sync(): async with AsyncSession(engine) as db: await sync_service.sync_all(db)

Device sync fetches from both Netdisco (L2/L3 topology) and LibreNMS (SNMP data), then merges by serial number, MAC address, or hostname+IP to prevent duplicates while preserving the highest-quality data from each source.

03 JWT Authentication & RBAC

Token Generation

from jose import jwt from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"]) def create_access_token(user_id: int) -> str: expire = datetime.utcnow() + timedelta(hours=8) return jwt.encode( {"sub": str(user_id), "exp": expire}, SECRET_KEY, algorithm="HS256" )

Role Enforcement

class UserRole(str, Enum): SUPERUSER = "superuser" ADMIN = "admin" def require_admin(user = Depends(get_current_user)): if user.role not in [Role.ADMIN, Role.SUPERUSER]: raise HTTPException(403, "Admin required") return user @app.post("/api/items") async def create_item( data: ItemCreate, user: User = Depends(require_admin) ): ...
Architecture Async-First Design
Problem: Legacy PHP used blocking I/O for database and external API calls
Solution: Built entirely on async/await from HTTP handling through database operations to external API integrations with connection pooling and retry logic
Result: Non-blocking request handling with efficient resource utilization
Integration Multi-Source Device Sync
Problem: Manual data entry was tedious and error-prone for large device inventories
Solution: Integrated Netdisco and LibreNMS APIs with intelligent deduplication by serial → MAC → hostname+IP, scheduled via APScheduler every 6 hours
Result: Automated device discovery eliminates manual entry for network-connected assets
Security Role-Based Access Control
Problem: Original system had basic session auth with no permission levels
Solution: JWT-based authentication with bcrypt password hashing, two-tier RBAC (superuser/admin), dependency injection for route protection
Result: Granular access control with secure session management
Operations Backup & Restore System
Problem: No way to recover from accidental deletions or rollback changes
Solution: Point-in-time JSON snapshots with atomic restore operations that soft-delete current items and reactivate backup state
Result: Full data recovery capability with audit trail
Python & FastAPI
  • Async/await patterns
  • Dependency injection
  • Pydantic validation
  • Background tasks
Database Design
  • SQLAlchemy ORM
  • Alembic migrations
  • Indexing strategy
  • Soft deletes
API Integration
  • httpx async client
  • Retry logic
  • Rate limiting
  • Data merging
Frontend
  • Jinja2 templating
  • Tailwind CSS
  • Dark/light mode
  • Real-time search
DevOps
  • Docker Compose
  • Health checks
  • Environment config
  • Non-root containers
🐳 Docker Compose

Single-command deployment with PostgreSQL and FastAPI services, health checks, and named volumes for data persistence.

🔒 Security

Non-root container user, HttpOnly JWT cookies, bcrypt password hashing, and environment-based secrets management.

🔧 Configuration

Environment variables for all settings: database URL, API tokens, sync intervals, room locations, and excluded IP ranges.

📁 Migrations

Alembic-managed schema with versioned migrations. Current schema at version 006 with full upgrade/downgrade paths.

Active development
  • Core CRUD operations functional
  • JWT authentication with RBAC
  • Netdisco + LibreNMS sync integration
  • Backup/restore system operational
  • Docker deployment configured
  • Dark/light mode with real-time search