From c0552dcf4b16e3b0a640b64f18394aa76ccec1da Mon Sep 17 00:00:00 2001 From: Duncan-Brain Date: Thu, 9 Jan 2025 10:57:45 -0500 Subject: [PATCH 1/4] LF-4621 Add migration file - destroys unused sensor feature data --- ...106233711_sensors_refactor_destroy_data.js | 250 ++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js diff --git a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js new file mode 100644 index 0000000000..9c6180d3cf --- /dev/null +++ b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2024 LiteFarm.org + * This file is part of LiteFarm. + * + * LiteFarm is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiteFarm is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details, see . + */ + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const up = async function (knex) { + // WARNING: Will delete all existing sensors data except for 'integrating_partner' + await knex.schema.dropTable('sensor_reading'); + await knex.schema.dropTable('sensor_reading_type'); + await knex.schema.dropTable('sensor'); + await knex.schema.dropTable('partner_reading_type'); + await knex.schema.dropTable('farm_external_integration'); + + // Add inbound integration type + await knex.schema.createTable('inbound_integration_type', (table) => { + table.increments('id').primary(); + table.string('type').notNullable(); + }); + + await knex('inbound_integration_type').insert([ + { type: 'SENSOR_READING' }, + { type: 'IRRIGATION_PRESCRIPTION' }, + ]); + + // Create farm inbound integration + await knex.schema.createTable('farm_inbound_integration', (table) => { + table.primary(['farm_id', 'partner_id']); + // New notNullable constraints on farm_id and partner_id since null != null violating our original intention for primary key + table.uuid('farm_id').references('farm_id').inTable('farm').notNullable(); + table + .integer('partner_id') + .references('partner_id') + .inTable('integrating_partner') + .notNullable(); + table.uuid('organization_uuid').nullable(); + table.integer('webhook_id').nullable(); + table.integer('type_id').references('id').inTable('inbound_integration_type').notNullable(); + }); + + // Create sensor manufacturer table + await knex.schema.createTable('sensor_manufacturer', (table) => { + table.increments('id').primary(); + // references name on integrating_partner if integrating_partner_id exists + table.string('name').notNullable(); + table.uuid('farm_id').references('farm_id').inTable('farm').nullable(); + table + .integer('integrating_partner_id') + .references('partner_id') + .inTable('integrating_partner') + .notNullable(); + }); + + // Create sensor_array table + await knex.schema.createTable('sensor_array', (table) => { + // Other locations seem to use .onDelete('CASCADE') but I don't think it is appropriate + table.uuid('location_id').primary().references('location_id').inTable('location').notNullable(); + table.uuid('field_location_id').references('location_id').inTable('location').notNullable(); + }); + + // Create sensor table + await knex.schema.createTable('sensor', (table) => { + table.increments('id').primary(); + table.string('name').nullable(); + // Contrains sensor to arrays instead of all locations + table.uuid('location_id').references('location_id').inTable('sensor_array').notNullable(); + // changed from notNullable + table.string('external_id').nullable(); + table + .integer('sensor_manufacturer_id') + .references('id') + .inTable('sensor_manufacturer') + .nullable(); + table.check( + '(?? IS NULL AND ?? IS NULL) OR (?? IS NOT NULL AND ?? IS NOT NULL)', + ['external_id', 'sensor_manufacturer_id', 'external_id', 'sensor_manufacturer_id'], + 'external_data_check', + ); + // combining depth and elevation + table.float('depth_elevation').nullable(); + // removing defaultTo('cm') + table.enu('depth_elevation_unit', ['cm', 'm', 'in', 'ft']).nullable(); + table.check( + '(?? IS NULL AND ?? IS NULL) OR (?? IS NOT NULL AND ?? IS NOT NULL)', + ['depth_elevation', 'depth_elevation_unit', 'depth_elevation', 'depth_elevation_unit'], + 'depth_elevation_unit_check', + ); + table.boolean('retired').notNullable().defaultTo('false'); + table.boolean('deleted').notNullable().defaultTo('false'); + }); + + // Refactor sensor_reading_type - references partner_reading_type, sensor_reading_type + await knex.schema.createTable('sensor_reading_type', (table) => { + table.increments('id').primary(); + table.string('type').notNullable(); + table.string('unit').notNullable(); + }); + + await knex('sensor_reading_type').insert([ + { type: 'temperature', unit: 'C' }, + { type: 'soil_water_potential', unit: 'kPa' }, + { type: 'soil_water_content', unit: 'mm' }, + ]); + + // Create relationship table + await knex.schema.createTable('sensor_reading_mode', (table) => { + table.integer('sensor_id').references('id').inTable('sensor').notNullable(); + table + .integer('sensor_reading_type_id') + .references('id') + .inTable('sensor_reading_type') + .notNullable(); + }); + + // Create sensor reading table + await knex.schema.createTable('sensor_reading', (table) => { + table.increments('id').primary(); + table.integer('sensor_id').references('id').inTable('sensor').notNullable(); + table + .integer('sensor_reading_type_id') + .references('id') + .inTable('sensor_reading_type') + .notNullable(); + table.timestamp('created_at').notNullable(); + table.float('value').notNullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export const down = async function (knex) { + // WARNING: Will delete all existing sensors data except for 'integrating_partner' + await knex.schema.dropTable('sensor_reading'); + await knex.schema.dropTable('sensor_reading_mode'); + await knex.schema.dropTable('sensor_reading_type'); + await knex.schema.dropTable('sensor'); + await knex.schema.dropTable('sensor_array'); + await knex.schema.dropTable('sensor_manufacturer'); + await knex.schema.dropTable('farm_inbound_integration'); + await knex.schema.dropTable('inbound_integration_type'); + + // Recreate farm_external_integration - from /20220610171713_create-farm-external-integrations-table.js + // Including alteration from webhook address to webhook id + await knex.schema.createTable('farm_external_integration', (table) => { + table.primary(['farm_id', 'partner_id']); + table.uuid('farm_id').references('farm_id').inTable('farm'); + table.integer('partner_id').references('partner_id').inTable('integrating_partner'); + table.uuid('organization_uuid'); + table.integer('webhook_id'); + }); + + // Recreate partner reading type - from /20220614195741_sensor_reading_types.js + await knex.schema.createTable('partner_reading_type', (table) => { + table + .uuid('partner_reading_type_id') + .primary() + .notNullable() + .defaultTo(knex.raw('uuid_generate_v1()')); + table + .integer('partner_id') + .references('partner_id') + .inTable('integrating_partner') + .notNullable(); + table.integer('raw_value'); + table.string('readable_value'); + }); + + await knex('partner_reading_type').insert([ + { + partner_id: 1, + readable_value: 'soil_water_content', + }, + { + partner_id: 1, + readable_value: 'soil_water_potential', + }, + { + partner_id: 1, + readable_value: 'temperature', + }, + ]); + + // Recreate sensor table - from /20220715214935_sensor_as_standard_location.js + // Including alteration to depth_unit + await knex.schema.createTable('sensor', function (table) { + table + .uuid('location_id') + .primary() + .notNullable() + .references('location_id') + .inTable('location') + .unique() + .onDelete('CASCADE'); + table + .integer('partner_id') + .references('partner_id') + .inTable('integrating_partner') + .notNullable(); + table.string('external_id').notNullable(); + table.float('depth'); + table.enu('depth_unit', ['cm', 'm', 'in', 'ft']).defaultTo('cm'); + table.float('elevation'); + table.string('model'); + }), + // Rebuild sensor reading type - from /20220715214935_sensor_as_standard_location.js + await knex.schema.createTable('sensor_reading_type', function (table) { + table + .uuid('sensor_reading_type_id') + .primary() + .notNullable() + .defaultTo(knex.raw('uuid_generate_v1()')); + table + .uuid('partner_reading_type_id') + .references('partner_reading_type_id') + .inTable('partner_reading_type'); + table.uuid('location_id').references('location_id').inTable('sensor').onDelete('CASCADE'); + }); + + // Rebuild sensor reading type - from /20220715214935_sensor_as_standard_location.js + await knex.schema.createTable('sensor_reading', function (table) { + table.uuid('reading_id').primary().notNullable().defaultTo(knex.raw('uuid_generate_v1()')); + table + .uuid('location_id') + .notNullable() + .references('location_id') + .inTable('sensor') + .onDelete('CASCADE'); + table.timestamp('read_time').notNullable(); + table.timestamp('created_at').notNullable().defaultTo(knex.fn.now()); + table.string('reading_type').notNullable(); + table.float('value').notNullable(); + table.string('unit').notNullable(); + table.boolean('valid').notNullable().defaultTo(true); + }); +}; From 8a77e7cff4c6a7543820c7542bbfc22ad35eb5ac Mon Sep 17 00:00:00 2001 From: Duncan-Brain Date: Thu, 9 Jan 2025 18:39:58 -0500 Subject: [PATCH 2/4] LF-4621 Re-add some suggestions that snuck in --- .../migration/20250106233711_sensors_refactor_destroy_data.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js index 9c6180d3cf..a393bd11e5 100644 --- a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js +++ b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js @@ -68,7 +68,7 @@ export const up = async function (knex) { await knex.schema.createTable('sensor_array', (table) => { // Other locations seem to use .onDelete('CASCADE') but I don't think it is appropriate table.uuid('location_id').primary().references('location_id').inTable('location').notNullable(); - table.uuid('field_location_id').references('location_id').inTable('location').notNullable(); + table.uuid('field_location_id').references('location_id').inTable('field').notNullable(); }); // Create sensor table @@ -89,6 +89,7 @@ export const up = async function (knex) { ['external_id', 'sensor_manufacturer_id', 'external_id', 'sensor_manufacturer_id'], 'external_data_check', ); + table.string('model'); // combining depth and elevation table.float('depth_elevation').nullable(); // removing defaultTo('cm') From 6dfa219b2e1d80e16fbc0474a7f7dc065c40cfa3 Mon Sep 17 00:00:00 2001 From: Duncan-Brain Date: Fri, 10 Jan 2025 11:08:16 -0500 Subject: [PATCH 3/4] LF-4621 update the year on licence --- ...20250106233711_sensors_refactor_destroy_data.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js index a393bd11e5..090236fd0a 100644 --- a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js +++ b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 LiteFarm.org + * Copyright (c) 2025 LiteFarm.org * This file is part of LiteFarm. * * LiteFarm is free software: you can redistribute it and/or modify @@ -71,6 +71,18 @@ export const up = async function (knex) { table.uuid('field_location_id').references('location_id').inTable('field').notNullable(); }); + // Add inbound integration type + await knex.schema.createTable('sensor_status', (table) => { + table.increments('id').primary(); + table.string('key').notNullable(); + }); + + await knex('sensor_status').insert([ + { key: 'ONLINE_OK' }, + { key: 'ONLINE_WARNING' }, + { key: 'NOT_CONNECTED' }, + ]); + // Create sensor table await knex.schema.createTable('sensor', (table) => { table.increments('id').primary(); From b41498a04869ab469d52529209522dc7838f138d Mon Sep 17 00:00:00 2001 From: Duncan-Brain Date: Fri, 10 Jan 2025 11:15:26 -0500 Subject: [PATCH 4/4] LF-4621 Update sensor status variables and add down drop function --- .../20250106233711_sensors_refactor_destroy_data.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js index 090236fd0a..0df1ac3bfb 100644 --- a/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js +++ b/packages/api/db/migration/20250106233711_sensors_refactor_destroy_data.js @@ -77,11 +77,7 @@ export const up = async function (knex) { table.string('key').notNullable(); }); - await knex('sensor_status').insert([ - { key: 'ONLINE_OK' }, - { key: 'ONLINE_WARNING' }, - { key: 'NOT_CONNECTED' }, - ]); + await knex('sensor_status').insert([{ key: 'ONLINE' }, { key: 'OFFLINE' }]); // Create sensor table await knex.schema.createTable('sensor', (table) => { @@ -162,6 +158,7 @@ export const down = async function (knex) { await knex.schema.dropTable('sensor_reading_mode'); await knex.schema.dropTable('sensor_reading_type'); await knex.schema.dropTable('sensor'); + await knex.schema.dropTable('sensor_status'); await knex.schema.dropTable('sensor_array'); await knex.schema.dropTable('sensor_manufacturer'); await knex.schema.dropTable('farm_inbound_integration');