diff --git a/src/controllers/organization.controller.ts b/src/controllers/organization.controller.ts index bbd62fa0..714363de 100644 --- a/src/controllers/organization.controller.ts +++ b/src/controllers/organization.controller.ts @@ -83,10 +83,12 @@ export class OrganizationController { // create query to get all orgs and their planters if (filter?.where) { - filter.where = await this.organizationRepository.applyOrganizationWhereClause( - filter.where, - organizationId.valueOf(), - ); + filter.where = + await this.organizationRepository.applyOrganizationWhereClause( + filter.where, + organizationId.valueOf(), + 'orgs', + ); } const childOrgs = await this.organizationRepository.find(filter); diff --git a/src/controllers/planter.controller.ts b/src/controllers/planter.controller.ts index 942c8f4f..324f5b81 100644 --- a/src/controllers/planter.controller.ts +++ b/src/controllers/planter.controller.ts @@ -54,6 +54,7 @@ export class PlanterController { where = await this.planterRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, organizationId, + 'planter', ); } // console.log('get /planter/count where -->', where); @@ -85,6 +86,7 @@ export class PlanterController { filter.where = await this.planterRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, organizationId, + 'planter', ); } diff --git a/src/controllers/planterOrganization.controller.ts b/src/controllers/planterOrganization.controller.ts index 40f4eb58..7088f13e 100644 --- a/src/controllers/planterOrganization.controller.ts +++ b/src/controllers/planterOrganization.controller.ts @@ -62,9 +62,8 @@ export class PlanterOrganizationController { const filterOrgId = organizationId; if (filterOrgId && filterOrgId !== orgId) { - const entityIds = await this.planterRepository.getEntityIdsByOrganizationId( - orgId, - ); + const entityIds = + await this.planterRepository.getEntityIdsByOrganizationId(orgId); orgId = entityIds.includes(filterOrgId) ? filterOrgId : orgId; } @@ -72,6 +71,7 @@ export class PlanterOrganizationController { where = await this.planterRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, orgId, + 'planter', ); } @@ -103,9 +103,8 @@ export class PlanterOrganizationController { const filterOrgId = organizationId; if (filterOrgId && filterOrgId !== orgId) { - const entityIds = await this.planterRepository.getEntityIdsByOrganizationId( - orgId, - ); + const entityIds = + await this.planterRepository.getEntityIdsByOrganizationId(orgId); orgId = entityIds.includes(filterOrgId) ? filterOrgId : orgId; } @@ -113,6 +112,7 @@ export class PlanterOrganizationController { filter.where = await this.planterRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, orgId, + 'planter', ); } @@ -140,11 +140,16 @@ export class PlanterOrganizationController { ): Promise { // console.log('/organization/{organizationId}/planter-registration'); - const sql = `SELECT * FROM planter_registrations + const sql = `SELECT *, devices.manufacturer FROM planter_registrations + JOIN devices ON devices.android_id=planter_registrations.device_identifier LEFT JOIN ( - SELECT region.name AS country, region.geom FROM region, region_type - WHERE region_type.type='country' AND region.type_id=region_type.id - ) AS region ON ST_DWithin(region.geom, planter_registrations.geom, 0.01)`; + SELECT + region.name AS country, + region.geom FROM region, region_type + WHERE region_type.type='country' + AND region.type_id=region_type.id + ) AS region + ON ST_DWithin(region.geom, planter_registrations.geom, 0.01)`; const params = { filter, diff --git a/src/controllers/trees.controller.ts b/src/controllers/trees.controller.ts index 888bef75..b2019266 100644 --- a/src/controllers/trees.controller.ts +++ b/src/controllers/trees.controller.ts @@ -57,6 +57,7 @@ export class TreesController { where = await this.treesRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, organizationId, + 'trees', ); } @@ -90,6 +91,7 @@ export class TreesController { filter.where = await this.treesRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, organizationId, + 'trees', ); } diff --git a/src/controllers/treesOrganization.controller.ts b/src/controllers/treesOrganization.controller.ts index 5a0575fd..3226aa3b 100644 --- a/src/controllers/treesOrganization.controller.ts +++ b/src/controllers/treesOrganization.controller.ts @@ -61,6 +61,7 @@ export class TreesOrganizationController { where = await this.treesRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, orgId, + 'trees', ); } @@ -100,6 +101,7 @@ export class TreesOrganizationController { filter.where = await this.treesRepository.applyOrganizationWhereClause( whereWithoutOrganizationId, orgId, + 'trees', ); } diff --git a/src/js/buildFilterQuery.js b/src/js/buildFilterQuery.js index a70e2579..067c0f49 100644 --- a/src/js/buildFilterQuery.js +++ b/src/js/buildFilterQuery.js @@ -28,6 +28,20 @@ export function buildFilterQuery(selectStmt, params) { const whereObjClause = connector._buildWhere(modelName, safeWhere); + //add the modelName to each requested field to avoid ambiguity in more complex queries + console.log('whereObjClause.sql -------> ', whereObjClause.sql); + + const tableName = + modelName.toLowerCase() === 'planterregistration' + ? 'planter_registrations' + : modelName.toLowerCase(); + const newQueryFields = whereObjClause.sql + .replace(/"/, `"${tableName}.`) + .replace(/AND "/gi, `AND "${tableName}.`) + .replace(/"/g, ''); + + whereObjClause.sql = newQueryFields; + if (whereObjClause.sql) { const hasWhere = /WHERE(?![^(]*\))/i.test(selectStmt); query.sql += ` ${hasWhere ? 'AND' : 'WHERE'} ${whereObjClause.sql}`; diff --git a/src/mixins/utils.repository-mixin.ts b/src/mixins/utils.repository-mixin.ts new file mode 100644 index 00000000..71f71044 --- /dev/null +++ b/src/mixins/utils.repository-mixin.ts @@ -0,0 +1,120 @@ +import { MixinTarget } from '@loopback/core'; +import { CrudRepository, Model } from '@loopback/repository'; +import expect from 'expect-runtime'; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function UtilsRepositoryMixin< + M extends Model, + R extends MixinTarget>, +>(superClass: R) { + return class extends superClass { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [x: string]: any; + // put the shared code here + async getEntityIdsByOrganizationId( + organizationId: number, + ): Promise> { + expect(organizationId).number(); + expect(this).property('execute').defined(); + const result = await this.execute( + `select * from getEntityRelationshipChildren(${organizationId})`, + [], + ); + return result.map((e) => e.entity_id); + } + + async applyOrganizationWhereClause( + where: Object | undefined, + organizationId: number | undefined, + model: string, + ): Promise { + if (!where || organizationId === undefined) { + return Promise.resolve(where); + } + + // if planter or tree repository request + if (model === 'trees' || model === 'planter') { + const organizationWhereClause = await this.getOrganizationWhereClause( + organizationId, + model, + ); + return { + and: [where, organizationWhereClause], + }; + } else { + const entityIds = await this.getEntityIdsByOrganizationId( + organizationId, + ); + return { + and: [where, { id: { inq: entityIds } }], + }; + } + } + + async getPlanterIdsByOrganizationId( + organizationId: number, + ): Promise> { + expect(organizationId).number(); + const result = await this.execute( + `select * from planter where organization_id in (select entity_id from getEntityRelationshipChildren(${organizationId}))`, + [], + ); + expect(result).match([{ id: expect.any(Number) }]); + return result.map((e) => e.id); + } + + async getNonOrganizationPlanterIds(): Promise> { + const result = await this.execute( + `select * from planter where organization_id isnull`, + [], + ); + expect(result).match([{ id: expect.any(Number) }]); + return result.map((e) => e.id); + } + + async getOrganizationWhereClause( + organizationId: number, + model: string, + ): Promise { + if (organizationId === null) { + const planterIds = await this.getNonOrganizationPlanterIds(); + // if planter repository request + if (model === 'planter') { + return { id: { inq: planterIds } }; + } else { + // if trees or other repository request + return { + and: [ + { plantingOrganizationId: null }, + { planterId: { inq: planterIds } }, + ], + }; + } + } else { + const planterIds = await this.getPlanterIdsByOrganizationId( + organizationId, + ); + const entityIds = await this.getEntityIdsByOrganizationId( + organizationId, + ); + // if planter repository request + if (model === 'planter') { + return { + or: [ + { organizationId: { inq: entityIds } }, + { 'planter.id': { inq: planterIds } }, + ], + }; + } else { + // if trees or other repository request + return { + or: [ + { plantingOrganizationId: { inq: entityIds } }, + { planterId: { inq: planterIds } }, + ], + }; + } + } + } + }; +} diff --git a/src/repositories/organization.repository.ts b/src/repositories/organization.repository.ts index 423cc87e..e9742ef4 100644 --- a/src/repositories/organization.repository.ts +++ b/src/repositories/organization.repository.ts @@ -1,42 +1,22 @@ +import { Constructor, inject } from '@loopback/core'; import { DefaultCrudRepository } from '@loopback/repository'; -import { Organization, OrganizationRelations } from '../models'; import { TreetrackerDataSource } from '../datasources'; -import { inject } from '@loopback/core'; -import expect from 'expect-runtime'; +import { UtilsRepositoryMixin } from '../mixins/utils.repository-mixin'; +import { Organization, OrganizationRelations } from '../models'; -export class OrganizationRepository extends DefaultCrudRepository< +export class OrganizationRepository extends UtilsRepositoryMixin< Organization, - typeof Organization.prototype.id, - OrganizationRelations -> { + Constructor< + DefaultCrudRepository< + Organization, + typeof Organization.prototype.id, + OrganizationRelations + > + > +>(DefaultCrudRepository) { constructor( @inject('datasources.treetracker') dataSource: TreetrackerDataSource, ) { super(Organization, dataSource); } - - async getEntityIdsByOrganizationId( - organizationId: number, - ): Promise> { - expect(organizationId).number(); - expect(this).property('execute').defined(); - const result = await this.execute( - `select * from getEntityRelationshipChildren(${organizationId})`, - [], - ); - return result.map((e) => e.entity_id); - } - - async applyOrganizationWhereClause( - where: Object | undefined, - organizationId: number | undefined, - ): Promise { - if (!where || organizationId === undefined) { - return Promise.resolve(where); - } - const entityIds = await this.getEntityIdsByOrganizationId(organizationId); - return { - and: [where, { id: { inq: entityIds } }], - }; - } } diff --git a/src/repositories/planter.repository.ts b/src/repositories/planter.repository.ts index 3f130ac2..af0bc93a 100644 --- a/src/repositories/planter.repository.ts +++ b/src/repositories/planter.repository.ts @@ -1,3 +1,4 @@ +import { Constructor, inject, Getter } from '@loopback/core'; import { DefaultCrudRepository, repository, @@ -9,17 +10,21 @@ import { } from '@loopback/repository'; import { Planter, PlanterRelations, PlanterRegistration } from '../models'; import { TreetrackerDataSource } from '../datasources'; -import { inject, Getter } from '@loopback/core'; import { PlanterRegistrationRepository } from './planterRegistration.repository'; -import expect from 'expect-runtime'; +import { UtilsRepositoryMixin } from '../mixins/utils.repository-mixin'; import { buildFilterQuery } from '../js/buildFilterQuery'; import { utils } from '../js/utils'; -export class PlanterRepository extends DefaultCrudRepository< +export class PlanterRepository extends UtilsRepositoryMixin< Planter, - typeof Planter.prototype.id, - PlanterRelations -> { + Constructor< + DefaultCrudRepository< + Planter, + typeof Planter.prototype.id, + PlanterRelations + > + > +>(DefaultCrudRepository) { public readonly planterRegs: HasManyRepositoryFactory< PlanterRegistration, typeof Planter.prototype.id @@ -40,75 +45,6 @@ export class PlanterRepository extends DefaultCrudRepository< ); } - async getEntityIdsByOrganizationId( - organizationId: number, - ): Promise> { - expect(organizationId).number(); - expect(this).property('execute').defined(); - const result = await this.execute( - `select * from getEntityRelationshipChildren(${organizationId})`, - [], - ); - return result.map((e) => e.entity_id); - } - - async getPlanterIdsByOrganizationId( - organizationId: number, - ): Promise> { - expect(organizationId).number(); - const result = await this.execute( - `select * from planter where organization_id in (select entity_id from getEntityRelationshipChildren(${organizationId}))`, - [], - ); - expect(result).match([{ id: expect.any(Number) }]); - return result.map((e) => e.id); - } - - async getNonOrganizationPlanterIds(): Promise> { - const result = await this.execute( - `select * from planter where organization_id isnull`, - [], - ); - expect(result).match([{ id: expect.any(Number) }]); - return result.map((e) => e.id); - } - - async getOrganizationWhereClause(organizationId: number): Promise { - if (organizationId === null) { - const planterIds = await this.getNonOrganizationPlanterIds(); - return { - and: [{ organizationId: null }, { 'planter.id': { inq: planterIds } }], - }; - } else { - const planterIds = await this.getPlanterIdsByOrganizationId( - organizationId, - ); - const entityIds = await this.getEntityIdsByOrganizationId(organizationId); - - return { - or: [ - { organizationId: { inq: entityIds } }, - { 'planter.id': { inq: planterIds } }, - ], - }; - } - } - - async applyOrganizationWhereClause( - where: Object | undefined, - organizationId: number | undefined, - ): Promise { - if (!where || organizationId === undefined) { - return Promise.resolve(where); - } - const organizationWhereClause = await this.getOrganizationWhereClause( - organizationId, - ); - return { - and: [where, organizationWhereClause], - }; - } - getPlanterRegistrationJoinClause(deviceIdentifier: string): string { if (deviceIdentifier === null) { return `LEFT JOIN planter_registrations @@ -120,7 +56,8 @@ export class PlanterRepository extends DefaultCrudRepository< WHERE (planter_registrations.device_identifier='${deviceIdentifier}')`; } - // loopback .find() wasn't applying the org filters + // default .find() wasn't applying the org filters + async findWithOrg( filter?: Filter, deviceIdentifier?: string, @@ -132,17 +69,13 @@ export class PlanterRepository extends DefaultCrudRepository< try { if (this.dataSource.connector) { - const columnNames = this.dataSource.connector - .buildColumnNames('Planter', filter) - .replace('"id"', 'planter.id as "id"'); - let selectStmt; if (deviceIdentifier) { selectStmt = `SELECT planter.* FROM planter ${this.getPlanterRegistrationJoinClause( deviceIdentifier, )}`; } else { - selectStmt = `SELECT ${columnNames} FROM planter`; + selectStmt = `SELECT planter.* FROM planter`; } const params = { @@ -152,7 +85,6 @@ export class PlanterRepository extends DefaultCrudRepository< }; const query = buildFilterQuery(selectStmt, params); - // console.log('query ---------', query); const result = await this.execute(query.sql, query.params, options); return result.map((planter) => utils.convertCamel(planter)); @@ -194,7 +126,6 @@ export class PlanterRepository extends DefaultCrudRepository< return >await this.execute(query.sql, query.params).then( (res) => { - // responds with count value as a string return (res && res[0]) || { count: 0 }; }, ); diff --git a/src/repositories/trees.repository.ts b/src/repositories/trees.repository.ts index 0a27c403..a778d536 100644 --- a/src/repositories/trees.repository.ts +++ b/src/repositories/trees.repository.ts @@ -1,3 +1,4 @@ +import { Constructor, inject, Getter } from '@loopback/core'; import { DefaultCrudRepository, repository, @@ -7,18 +8,19 @@ import { Where, Count, } from '@loopback/repository'; -import { Trees, TreesRelations, TreeTag } from '../models'; import { TreetrackerDataSource } from '../datasources'; -import { inject, Getter } from '@loopback/core'; +import { UtilsRepositoryMixin } from '../mixins/utils.repository-mixin'; import { TreeTagRepository } from './treeTag.repository'; +import { Trees, TreesRelations, TreeTag } from '../models'; import expect from 'expect-runtime'; import { buildFilterQuery } from '../js/buildFilterQuery'; -export class TreesRepository extends DefaultCrudRepository< +export class TreesRepository extends UtilsRepositoryMixin< Trees, - typeof Trees.prototype.id, - TreesRelations -> { + Constructor< + DefaultCrudRepository + > +>(DefaultCrudRepository) { public readonly treeTags: HasManyRepositoryFactory< TreeTag, typeof Trees.prototype.id @@ -49,67 +51,6 @@ export class TreesRepository extends DefaultCrudRepository< return result.map((e) => e.entity_id); } - async getPlanterIdsByOrganizationId( - organizationId: number, - ): Promise> { - expect(organizationId).number(); - const result = await this.execute( - `select * from planter where organization_id in (select entity_id from getEntityRelationshipChildren(${organizationId}))`, - [], - ); - expect(result).match([{ id: expect.any(Number) }]); - return result.map((e) => e.id); - } - - async getNonOrganizationPlanterIds(): Promise> { - const result = await this.execute( - `select * from planter where organization_id isnull`, - [], - ); - expect(result).match([{ id: expect.any(Number) }]); - return result.map((e) => e.id); - } - - async getOrganizationWhereClause(organizationId: number): Promise { - // console.log('getOrganizationWhereClause orgId ---', organizationId); - if (organizationId === null) { - const planterIds = await this.getNonOrganizationPlanterIds(); - return { - and: [ - { plantingOrganizationId: null }, - { planterId: { inq: planterIds } }, - ], - }; - } else { - const planterIds = await this.getPlanterIdsByOrganizationId( - organizationId, - ); - const entityIds = await this.getEntityIdsByOrganizationId(organizationId); - - return { - or: [ - { plantingOrganizationId: { inq: entityIds } }, - { planterId: { inq: planterIds } }, - ], - }; - } - } - - async applyOrganizationWhereClause( - where: Object | undefined, - organizationId: number | undefined, - ): Promise { - if (!where || organizationId === undefined) { - return Promise.resolve(where); - } - const organizationWhereClause = await this.getOrganizationWhereClause( - organizationId, - ); - return { - and: [where, organizationWhereClause], - }; - } - getTreeTagJoinClause(tagId: string): string { if (tagId === null) { return `LEFT JOIN tree_tag ON trees.id=tree_tag.tree_id WHERE (tree_tag.tag_id ISNULL)`;