From 007f302943ecf7c633094063b8350090b36d23eb Mon Sep 17 00:00:00 2001 From: Gwynn Dandridge-Perry Date: Thu, 9 Mar 2023 18:40:27 -0800 Subject: [PATCH 1/2] feat: add query to look up captures near a particular lat lon --- server/app.ts | 15 +++-- server/infra/database/CaptureRepository.ts | 67 ++++++++++++++++++++++ server/interfaces/CaptureLocationFilter.ts | 11 ++++ server/models/Capture.ts | 17 ++++++ server/models/RawCapture.ts | 4 +- server/routers/capturesRouter.ts | 49 ++++++++++++++++ 6 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 server/interfaces/CaptureLocationFilter.ts diff --git a/server/app.ts b/server/app.ts index b8ef64c3..da953aaf 100644 --- a/server/app.ts +++ b/server/app.ts @@ -64,24 +64,31 @@ app.use(express.json()); // parse application/json // routers app.use('/countries', countriesRouter); app.use('/v2/countries', countriesRouter); -app.use('/trees', treesRouter); + app.use('/planters', plantersRouter); +app.use('/v2/growers', growerAccountsRouter); + app.use('/organizations', organizationsRouter); app.use('/v2/organizations', organizationsRouterV2); + app.use('/species', speciesRouter); app.use('/v2/species', speciesRouterV2); + app.use('/wallets', walletsRouter); app.use('/v2/wallets', walletsRouter); -app.use('/transactions', transactionsRouter); + app.use('/tokens', tokensRouter); app.use('/v2/tokens', tokensRouter); + +app.use('/trees', treesRouter); +app.use('/v2/trees', treesRouterV2); + app.use('/v2/captures', capturesRouter); app.use('/raw-captures', rawCapturesRouter); -app.use('/v2/growers', growerAccountsRouter); -app.use('/v2/trees', treesRouterV2); app.use('/bounds', boundsRouter); app.use('/gis', gisRouter); app.use('/contract', contractsRouter); +app.use('/transactions', transactionsRouter); // Global error handler app.use(errorHandler); diff --git a/server/infra/database/CaptureRepository.ts b/server/infra/database/CaptureRepository.ts index df2cb73b..f9ee9e57 100644 --- a/server/infra/database/CaptureRepository.ts +++ b/server/infra/database/CaptureRepository.ts @@ -1,5 +1,6 @@ import Capture from 'interfaces/Capture'; import CaptureFilter from 'interfaces/CaptureFilter'; +import CaptureLocationFilter from 'interfaces/CaptureLocationFilter'; import FilterOptions from 'interfaces/FilterOptions'; import HttpError from 'utils/HttpError'; import BaseRepository from './BaseRepository'; @@ -107,6 +108,16 @@ export default class CaptureRepository extends BaseRepository { delete filterObject.organization_id; } + // remove these filters because they'll be included manually + if (filterObject.lat && filterObject.lon && filterObject.deviation) { + // result.where( + // `ST_DWithin(treetracker.capture.estimated_geometric_location, ST_GeomFromText('POINT (${filterObject.lon} ${filterObject.lat})', 4326), ${filterObject.deviation})`, + // ); + delete filterObject.lat; + delete filterObject.lon; + delete filterObject.deviation; + } + result.where(filterObject); } @@ -184,6 +195,62 @@ export default class CaptureRepository extends BaseRepository { return captures; } + async getByLocation( + filterCriteria: CaptureLocationFilter, + options: FilterOptions, + ) { + const knex = this.session.getDB(); + const { sort, ...filter } = filterCriteria; + + let promise = knex.select( + knex.raw( + ` + treetracker.capture.*, + field_data.device_configuration.device_identifier, + field_data.device_configuration.manufacturer AS device_manufacturer, + treetracker.grower_account.reference_id as grower_reference_id, + treetracker.grower_account.wallet as grower_wallet, + treetracker.grower_account.location as grower_location, + field_data.session.id as session_id + FROM treetracker.capture + LEFT JOIN treetracker.grower_account + ON grower_account.id = treetracker.capture.grower_account_id + LEFT JOIN field_data.device_configuration + ON field_data.device_configuration.id = treetracker.capture.device_configuration_id + LEFT JOIN field_data.raw_capture + ON field_data.raw_capture.id = treetracker.capture.id + LEFT JOIN field_data.session + ON field_data.raw_capture.session_id = field_data.session.id + ${ + filter.lat && filter.lon && filter.deviation + ? `WHERE + ST_DWithin(treetracker.capture.estimated_geometric_location, ST_GeomFromText('POINT (${filter.lon} ${filter.lat})', 4326), ${filter.deviation}) + ` + : '' + }`, + ), + ); + // .where('field_data.session.id', filter.session_id); + // .where((builder) => this.filterWhereBuilder(filter, builder)); + + promise = promise.orderBy( + sort?.order_by || 'treetracker.capture.created_at', + sort?.order || 'desc', + ); + + const { limit, offset } = options; + if (limit) { + promise = promise.limit(limit); + } + if (offset) { + promise = promise.offset(offset); + } + + const captures = await promise; + + return captures; + } + async getCount(filterCriteria: CaptureFilter) { const knex = this.session.getDB(); const { ...filter } = filterCriteria; diff --git a/server/interfaces/CaptureLocationFilter.ts b/server/interfaces/CaptureLocationFilter.ts new file mode 100644 index 00000000..7ddfad17 --- /dev/null +++ b/server/interfaces/CaptureLocationFilter.ts @@ -0,0 +1,11 @@ +import DbModel from './DbModel'; + +interface CaptureLocationFilter extends DbModel { + session_id?: Array | undefined; + lat?: string | undefined; + lon?: string | undefined; + deviation?: string | undefined; + sort?: { order?: string; order_by?: string }; +} + +export default CaptureLocationFilter; diff --git a/server/models/Capture.ts b/server/models/Capture.ts index e67b9813..ed04ac24 100644 --- a/server/models/Capture.ts +++ b/server/models/Capture.ts @@ -2,6 +2,7 @@ import CaptureRepository from 'infra/database/CaptureRepository'; import { delegateRepository } from 'infra/database/delegateRepository'; import Capture from 'interfaces/Capture'; import CaptureFilter from 'interfaces/CaptureFilter'; +import CaptureLocationFilter from 'interfaces/CaptureLocationFilter'; import FilterOptions from 'interfaces/FilterOptions'; function getByFilter( @@ -13,6 +14,21 @@ function getByFilter( }; } +function getByLocation( + captureRepository: CaptureRepository, +): ( + filter: CaptureLocationFilter, + options: FilterOptions, +) => Promise { + return async function ( + filter: CaptureLocationFilter, + options: FilterOptions, + ) { + const captures = await captureRepository.getByLocation(filter, options); + return captures; + }; +} + function getCount( captureRepository: CaptureRepository, ): (filter: CaptureFilter) => Promise { @@ -24,6 +40,7 @@ function getCount( export default { getByFilter, + getByLocation, getCount, getById: delegateRepository('getById'), }; diff --git a/server/models/RawCapture.ts b/server/models/RawCapture.ts index ee48c6be..cc581367 100644 --- a/server/models/RawCapture.ts +++ b/server/models/RawCapture.ts @@ -1,5 +1,5 @@ -import RawCaptureRepository from 'infra/database/CaptureRepository'; import { delegateRepository } from 'infra/database/delegateRepository'; +import RawCaptureRepository from 'infra/database/RawCaptureRepository'; import RawCapture from 'interfaces/Capture'; import RawCaptureFilter from 'interfaces/CaptureFilter'; import FilterOptions from 'interfaces/FilterOptions'; @@ -15,7 +15,7 @@ function getByFilter( function getCount( rawCaptureRepository: RawCaptureRepository, -): (filter: RawCaptureFilter) => Promise<{ count: number }> { +): (filter: RawCaptureFilter) => Promise { return async function (filter: RawCaptureFilter) { const count = await rawCaptureRepository.getCount(filter); return count; diff --git a/server/routers/capturesRouter.ts b/server/routers/capturesRouter.ts index e7530b84..38405733 100644 --- a/server/routers/capturesRouter.ts +++ b/server/routers/capturesRouter.ts @@ -50,6 +50,55 @@ router.get( }), ); +router.get( + '/location', + handlerWrapper(async (req, res) => { + const query = queryFormatter(req); + + // verify filter values + Joi.assert( + query, + Joi.object().keys({ + session_id: Joi.string().uuid(), + lat: Joi.number(), + lon: Joi.number(), + deviation: Joi.number(), + limit: Joi.number().integer().min(1).max(20000), + offset: Joi.number().integer().min(0), + order_by: Joi.string(), + order: Joi.string(), + whereNulls: Joi.array(), + whereNotNulls: Joi.array(), + whereIns: Joi.array(), + }), + ); + const { + deviation = '0.01', + limit = 0, + offset = 0, + order = 'desc', + order_by = 'captured_at', + ...rest + } = query; + + console.log('REST', rest); + + const repo = new CaptureRepository(new Session()); + const exe = CaptureModel.getByLocation(repo); + const sort = { order, order_by }; + const result = await exe({ ...rest, deviation, sort }, { limit, offset }); + // const count = await CaptureModel.getCount(repo)({ ...rest }); + res.send({ + captures: result, + total: Number(result.length), + // total: Number(count), + offset, + limit, + }); + res.end(); + }), +); + router.get( '/:id', handlerWrapper(async (req, res) => { From 57387c89b6feec3fb592b55deef27524f88bd48b Mon Sep 17 00:00:00 2001 From: Gwynn Dandridge-Perry Date: Thu, 9 Mar 2023 22:05:11 -0800 Subject: [PATCH 2/2] fix: remove comments from session_id code that doesn't work --- server/infra/database/CaptureRepository.ts | 5 ----- server/routers/capturesRouter.ts | 5 +---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/server/infra/database/CaptureRepository.ts b/server/infra/database/CaptureRepository.ts index f9ee9e57..d8ed638e 100644 --- a/server/infra/database/CaptureRepository.ts +++ b/server/infra/database/CaptureRepository.ts @@ -110,9 +110,6 @@ export default class CaptureRepository extends BaseRepository { // remove these filters because they'll be included manually if (filterObject.lat && filterObject.lon && filterObject.deviation) { - // result.where( - // `ST_DWithin(treetracker.capture.estimated_geometric_location, ST_GeomFromText('POINT (${filterObject.lon} ${filterObject.lat})', 4326), ${filterObject.deviation})`, - // ); delete filterObject.lat; delete filterObject.lon; delete filterObject.deviation; @@ -230,8 +227,6 @@ export default class CaptureRepository extends BaseRepository { }`, ), ); - // .where('field_data.session.id', filter.session_id); - // .where((builder) => this.filterWhereBuilder(filter, builder)); promise = promise.orderBy( sort?.order_by || 'treetracker.capture.created_at', diff --git a/server/routers/capturesRouter.ts b/server/routers/capturesRouter.ts index 38405733..6383030a 100644 --- a/server/routers/capturesRouter.ts +++ b/server/routers/capturesRouter.ts @@ -81,17 +81,14 @@ router.get( ...rest } = query; - console.log('REST', rest); - const repo = new CaptureRepository(new Session()); const exe = CaptureModel.getByLocation(repo); const sort = { order, order_by }; const result = await exe({ ...rest, deviation, sort }, { limit, offset }); - // const count = await CaptureModel.getCount(repo)({ ...rest }); + res.send({ captures: result, total: Number(result.length), - // total: Number(count), offset, limit, });