feat(registry): add IoT device connection profile (iot_profiles table)
New optional 1:1 extension to services: providers can declare how IoT
devices connect to their hub (hub_url, protocols, provisioning endpoint,
device classes, firmware compatibility) via PATCH /services/{id}.
- New entity IotProfileEntity + Liquibase changeset 009 (GIN indexes for
jsonb device_classes and protocols arrays)
- IotProtocol / CredentialFormat enums; IotProfileRequest / IotProfileResponse DTOs
- JsonStringListConverter for jsonb List<String> persistence
- GET /services/{id}/replacements extended with iotProfile per candidate
and new filter params: iotReady=true, deviceClass=..., protocol=...
- 6 new BDD scenarios (IotProfileCucumberTest) covering profile creation,
candidate enrichment, iotReady / deviceClass filtering, partial update,
and missing-field 422 validation
- All 57 tests green (6 new + 27 existing transition + 24 unit)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.27.xsd">
|
||||
|
||||
<changeSet id="009" author="apix">
|
||||
<!-- Optional IoT device-connection profile attached to a registered service.
|
||||
A service without an iot_profiles row is a generic API service with no
|
||||
declared device-onboarding support. A service with a row is discoverable
|
||||
by IoT devices polling for a cloud-sunset replacement target. -->
|
||||
<createTable tableName="iot_profiles">
|
||||
<column name="id" type="uuid" defaultValueComputed="gen_random_uuid()">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<!-- Owning FK — one profile per service, deleted with the service -->
|
||||
<column name="service_id" type="uuid">
|
||||
<constraints nullable="false" unique="true"
|
||||
foreignKeyName="fk_iot_profile_service"
|
||||
references="services(id)"
|
||||
deleteCascade="true"/>
|
||||
</column>
|
||||
|
||||
<!-- Device connection -->
|
||||
<!-- WebSocket / MQTT broker / CoAP endpoint the device connects to -->
|
||||
<column name="hub_url" type="varchar(512)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- JSONB array of IotProtocol enum values, e.g. ["MQTT_5_0","WEBSOCKET"] -->
|
||||
<column name="protocols" type="jsonb">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="tls_min_version" type="varchar(10)" defaultValue="1.2">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<!-- Provisioning — device calls this URL directly; APIX never sees device identity -->
|
||||
<column name="provisioning_endpoint" type="varchar(512)"/>
|
||||
<!-- CredentialFormat enum: JWT | PSK | X509 | NONE -->
|
||||
<column name="credential_format" type="varchar(30)"/>
|
||||
<!-- Which device field is presented at provisioning (serialNumber, mac, deviceEui) -->
|
||||
<column name="device_id_field" type="varchar(100)"/>
|
||||
|
||||
<!-- Device class targeting — same capability taxonomy as GET /services?capability -->
|
||||
<!-- JSONB array of class identifiers, e.g. ["device.class.smart-home-hub"] -->
|
||||
<column name="device_classes" type="jsonb">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<!-- Migration aid -->
|
||||
<column name="min_firmware_version" type="varchar(50)"/>
|
||||
<column name="firmware_update_url" type="varchar(512)"/>
|
||||
<!-- Estimated wall-clock minutes to complete the migration (for UX display) -->
|
||||
<column name="estimated_migration_minutes" type="integer"/>
|
||||
|
||||
<column name="created_at" type="timestamptz" defaultValueComputed="now()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="updated_at" type="timestamptz" defaultValueComputed="now()">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<!-- Liquibase createIndex does not support PostgreSQL-specific index types;
|
||||
GIN must be declared via raw SQL for jsonb containment queries. -->
|
||||
<sql>CREATE INDEX idx_iot_device_classes ON iot_profiles USING gin(device_classes)</sql>
|
||||
<sql>CREATE INDEX idx_iot_protocols ON iot_profiles USING gin(protocols)</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
@@ -13,5 +13,6 @@
|
||||
<include file="changes/006-sunset-lock.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/007-service-replacements.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/008-sunset-at.xml" relativeToChangelogFile="true"/>
|
||||
<include file="changes/009-iot-profiles.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
||||
Reference in New Issue
Block a user