A REST API service for managing IoT meter configurations with hierarchical organization and validation. Features include:
- Catalogue-based validation - Define meter models with validation constraints
- Hierarchical structure - Organize meters by Client → Project → Location → Meter
- Web UI - Modern htmx-powered frontend (no Node.js required)
- REST API - Full CRUD operations with OpenAPI/Swagger documentation
- Stage-based validation - Progressive validation through different stages (INTAKE, CONNECTION, etc.)
- Backend: Spring Boot 3.5.7, Java 25
- Database: MongoDB
- Frontend: Thymeleaf + htmx (server-side rendering)
- API Documentation: SpringDoc OpenAPI 3
- Security: Spring Security with optional OAuth2 JWT
- Java 25
- Docker & Docker Compose
# From the project root directory
docker compose up -dThis starts MongoDB on port 28017 with credentials tester:tester.
cd application
./gradlew bootRun- Web UI: http://localhost:8080/
- API Documentation: http://localhost:8080/swagger-ui/index.html
- Health Check: http://localhost:8080/actuator/health
The application includes a modern web interface built with htmx and Thymeleaf:
- Home Page (
/) - Overview and quick navigation - Catalogue Management (
/catalogue) - Create and view catalogue items - Meters (
/meters) - View and filter meters by location - Clients (
/clients) - Manage clients and organizations
- No Node.js required - Pure server-side rendering
- Interactive - htmx provides AJAX-like interactions without JavaScript frameworks
- Fast development - Spring DevTools enables hot reload
- Reuses REST API - No code duplication
Login to GitHub Container Registry:
export CRPAT=<your_personal_access_token>
echo $CRPAT | docker login ghcr.io -u USERNAME --password-stdinRun the application stack:
docker compose --profile=repository up# Build the Docker image
./gradlew jib
# Run with docker-compose
docker compose upThe application can be configured using environment variables:
MongoDB Configuration:
export MONGODB_URI=mongodb://tester:tester@localhost:28017/test
export MONGODB_HOST=localhost
export MONGODB_PORT=28017
export MONGODB_USERNAME=tester
export MONGODB_PASSWORD=tester
export MONGODB_DATABASE=testSecurity Configuration:
# Enable OAuth2 JWT authentication (disabled by default for development)
export SECURITY_ENABLED=true
export OAUTH2_ISSUER_URI=https://your-oauth-provider.com
export OAUTH2_JWK_SET_URI=https://your-oauth-provider.com/.well-known/jwks.jsonBy default, security is disabled for development (lumina.security.enabled=false). This allows unrestricted access to all endpoints.
For production, set SECURITY_ENABLED=true and configure OAuth2 properties.
Client
└── Projects
└── Locations
└── Meters (validated against Catalogue Items)
The Catalogue defines meter models with validation constraints:
- Model: Unique identifier (e.g., "GATEWAY-LORAWAN-V1")
- Level: GATEWAY or DEVICE
- Type: LORAWAN, MODBUS, or SIDEWALK
- Constraints: Validation rules applied to meter configurations
Validates string values with optional length restrictions:
{
"type": "TEXT",
"name": "publicKey",
"description": "Public key configuration",
"minLength": 0,
"maxLength": 256,
"isRequired": false,
"stage": "INTAKE"
}Validates numeric values (INTEGER or FLOAT) with optional bounds:
{
"type": "NUMERIC",
"name": "age",
"description": "Device age in days",
"numberType": "INTEGER",
"min": 0,
"max": 100,
"isRequired": true,
"stage": "INTAKE"
}Validates values against regex patterns:
{
"type": "PATTERN",
"name": "specialId",
"description": "Format: XXXX-XXXX-XXXX-XXXX",
"pattern": "\\d{4}-\\d{4}-\\d{4}-\\d{4}",
"isRequired": true,
"stage": "CONNECTION"
}stage: Defines when validation starts (e.g., INTAKE, CONNECTION)isRequired:true: Field must be present and valid at the specified stage and all subsequent stagesfalse: Field is only validated if present; optional fields pass validation when missing
curl -X 'POST' \
'http://localhost:8080/api/catalogue/item' \
-H 'Content-Type: application/json' \
-d '{
"model": "GATEWAY-LORAWAN-V1",
"level": "GATEWAY",
"type": "LORAWAN",
"description": "LoRaWAN Gateway Version 1",
"manufacturer": "Acme IoT Devices Inc."
}'Preset constraints are automatically added based on the level and type.
curl -X 'POST' \
'http://localhost:8080/api/client' \
-H 'Content-Type: application/json' \
-d '{
"name": "Acme Corporation"
}'Response includes the client id to use in subsequent requests.
curl -X 'POST' \
'http://localhost:8080/api/project' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "660ae9a0c1e5a312013963ef",
"name": "Solar Farm Project"
}'curl -X 'POST' \
'http://localhost:8080/api/location' \
-H 'Content-Type: application/json' \
-d '{
"name": "Building A",
"projectId": "660aea55c1e5a312013963f0"
}'curl -X 'POST' \
'http://localhost:8080/api/meter' \
-H 'Content-Type: application/json' \
-d '{
"locationId": "660aeb0fc1e5a312013963f1",
"model": "GATEWAY-LORAWAN-V1",
"lines": [
{
"type": "NUMERIC",
"name": "age",
"numberType": "INTEGER",
"value": 50
},
{
"type": "TEXT",
"name": "publicKey",
"value": "SHA-256:mypublickeyvalue"
}
]
}'The meter configuration is validated against the catalogue item constraints. Validation errors are returned with detailed messages if constraints are violated.
curl -X 'GET' 'http://localhost:8080/api/client'Returns clients with nested projects, locations, and meter IDs.
curl -X 'GET' 'http://localhost:8080/api/meter/location/660aeb0fc1e5a312013963f1'lumina/
├── application/ # Main Spring Boot application
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/lumina/
│ │ │ │ ├── catalogue/ # Catalogue management
│ │ │ │ ├── client/ # Client management
│ │ │ │ ├── project/ # Project management
│ │ │ │ ├── location/ # Location management
│ │ │ │ ├── meter/ # Meter configuration
│ │ │ │ ├── web/ # Web controllers
│ │ │ │ └── validation/ # Validation framework
│ │ │ └── resources/
│ │ │ ├── templates/ # Thymeleaf templates
│ │ │ ├── static/css/ # CSS stylesheets
│ │ │ └── application.yml
│ │ └── test/
│ └── build.gradle.kts
├── infrastructure/ # AWS CDK infrastructure code
└── compose.yaml # Docker Compose configuration
# Build the project
./gradlew build
# Run tests
./gradlew test
# Build Docker image
./gradlew jibSpring DevTools is included for development. Changes to Java code and templates will automatically reload.
Full interactive API documentation is available at:
- Swagger UI: http://localhost:8080/swagger-ui/index.html
- OpenAPI JSON: http://localhost:8080/v3/api-docs
Security is disabled by default (lumina.security.enabled=false):
- All endpoints are accessible without authentication
- CSRF protection is disabled
- OAuth2 JWT is not configured
Enable security by setting SECURITY_ENABLED=true and configuring OAuth2:
export SECURITY_ENABLED=true
export OAUTH2_ISSUER_URI=https://your-oauth-provider.com
export OAUTH2_JWK_SET_URI=https://your-oauth-provider.com/.well-known/jwks.jsonIn production mode:
- All API endpoints require JWT authentication (except health checks and Swagger UI)
- CSRF protection is disabled for API endpoints (JWT is used instead)
- Web UI endpoints require authentication
Spring Boot Actuator endpoints:
If you see MongoDB connection errors:
-
Ensure MongoDB is running:
docker compose ps
-
Check MongoDB logs:
docker compose logs mongodb
-
Verify connection string in
application.ymlor environment variables
If port 8080 is already in use, you can change it:
SERVER_PORT=8081 ./gradlew bootRunIf you get errors accessing /swagger-ui/index.html, ensure:
- The application is fully started (check logs)
- SpringDoc OpenAPI dependency is included (version 2.7.0 for Spring Boot 3.5.x)
- Security is disabled or properly configured
[Add your license here]
[Add contributing guidelines here]