Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lf 4703 nice to have add base properties to farm addon #3687

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
*/

export const up = async function (knex) {
await knex.schema.alterTable('farm_addon', (t) => {
// Knex apparently does not rename indexes along with table names
t.dropPrimary('farm_external_integration_pkey');
});
await knex.schema.alterTable('farm_addon', (t) => {
t.increments('id').primary();
t.boolean('deleted').notNullable().defaultTo(false);
t.string('created_by_user_id')
.references('user_id')
.inTable('users')
.notNullable()
.defaultTo(1);
t.string('updated_by_user_id')
.references('user_id')
.inTable('users')
.notNullable()
.defaultTo(1);
t.dateTime('created_at').notNullable().defaultTo(new Date('2000/1/1').toISOString());
t.dateTime('updated_at').notNullable().defaultTo(new Date('2000/1/1').toISOString());
});

// No need to check for duplicates and mark deleted due to prior composite index

// Add the partial unique index using raw SQL
// Knex partial indexes not working correctly
// TODO: Delete this index when multiple organizations are allowed per partner id
await knex.raw(`
CREATE UNIQUE INDEX farm_addon_uniqueness_composite
ON farm_addon(farm_id, addon_partner_id)
WHERE deleted = false;
`);
};

export const down = async function (knex) {
await knex('farm_addon').where({ deleted: true }).del();
await knex.schema.alterTable('farm_addon', (t) => {
t.dropIndex(['farm_id', 'addon_partner_id'], 'farm_addon_uniqueness_composite');
t.dropPrimary();
});
await knex.schema.alterTable('farm_addon', (t) => {
t.primary(['farm_id', 'addon_partner_id'], {
constraintName: 'farm_external_integration_pkey',
});
t.dropColumns([
'id',
'deleted',
'created_by_user_id',
'updated_by_user_id',
'created_at',
'updated_at',
]);
});
};
10 changes: 4 additions & 6 deletions packages/api/src/controllers/farmAddonController.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import { getValidEnsembleOrg } from '../util/ensemble.js';
const farmAddonController = {
addFarmAddon() {
return async (req, res) => {
const { farm_id } = req.headers;
const { addon_partner_id, org_uuid } = req.body;
const { org_uuid } = req.body;

try {
const organisation = await getValidEnsembleOrg(org_uuid);
Expand All @@ -30,9 +29,7 @@ const farmAddonController = {
}

await FarmAddonModel.upsertFarmAddon({
farm_id,
addon_partner_id,
org_uuid,
req,
org_pk: organisation.pk,
});

Expand All @@ -52,7 +49,8 @@ const farmAddonController = {
const { addon_partner_id } = req.query;
const rows = await FarmAddonModel.query()
.where({ farm_id, addon_partner_id })
.skipUndefined();
.skipUndefined()
.whereNotDeleted();
if (!rows.length) {
return res.sendStatus(404);
}
Expand Down
40 changes: 27 additions & 13 deletions packages/api/src/models/farmAddonModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
*/

import Model from './baseFormatModel.js';

import AddonPartner from './addonPartnerModel.js';
import Farm from './farmModel.js';
import baseModel from './baseModel.js';
import baseController from '../controllers/baseController.js';

class FarmAddon extends Model {
class FarmAddon extends baseModel {
/**
* Identifies the database table for this Model.
* @static
Expand All @@ -31,10 +32,10 @@ class FarmAddon extends Model {
/**
* Identifies the primary key fields for this Model.
* @static
* @returns {string[]} Names of the primary key fields.
* @returns {string} Names of the primary key fields.
*/
static get idColumn() {
return ['farm_id', 'addon_partner_id'];
return 'id';
}

/**
Expand All @@ -46,10 +47,12 @@ class FarmAddon extends Model {
return {
type: 'object',
properties: {
id: { type: 'integer' },
farm_id: { type: 'string' },
addon_partner_id: { type: 'integer' },
org_uuid: { type: 'string' },
org_pk: { type: 'integer' },
...this.baseProperties,
},
additionalProperties: false,
};
Expand Down Expand Up @@ -96,21 +99,32 @@ class FarmAddon extends Model {
.select('org_uuid', 'org_pk')
.where('farm_id', farmId)
.where('addon_partner_id', addonPartnerId)
.whereNotDeleted()
.first();
}

static async upsertFarmAddon({ farm_id, addon_partner_id, org_uuid, org_pk }) {
const existingAddon = await this.query().findOne({ farm_id, addon_partner_id });
static async upsertFarmAddon({ req, org_pk }) {
const { farm_id } = req.headers;
const { addon_partner_id, org_uuid } = req.body;

// With unique composite index only one can exist
const existingAddon = await this.query()
.findOne({ farm_id, addon_partner_id })
.whereNotDeleted();

if (existingAddon) {
return this.query().patch({ org_uuid, org_pk }).where({ farm_id, addon_partner_id });
return baseController.patch(FarmAddon, existingAddon.id, { org_uuid, org_pk }, req);
} else {
return this.query().insert({
farm_id,
addon_partner_id,
org_uuid,
org_pk,
});
return baseController.post(
FarmAddon,
{
farm_id,
addon_partner_id,
org_uuid,
org_pk,
},
req,
);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/api/src/util/ensemble.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ async function registerOrganisationWebhook(farmId, organisationId) {
const authHeader = `${farmId}${process.env.SENSOR_SECRET}`;
const existingIntegration = await FarmAddonModel.query()
.where({ farm_id: farmId, addon_partner_id: 1 })
.whereNotDeleted()
.first();
if (existingIntegration?.webhook_id) {
return;
Expand Down Expand Up @@ -399,6 +400,7 @@ async function createOrganisation(farmId) {
const data = await FarmModel.getFarmById(farmId);
const existingIntegration = await FarmAddonModel.query()
.where({ farm_id: farmId, addon_partner_id: 1 })
.whereNotDeleted()
.first();
if (!existingIntegration) {
const axiosObject = {
Expand Down Expand Up @@ -544,6 +546,7 @@ async function authenticateToGetTokens() {
);
return response.data;
} catch (error) {
console.error(error);
const err = new Error('Failed to authenticate with Ensemble.');
err.status = 500;
throw err;
Expand Down
Loading