diff --git a/package.json b/package.json index 328e1df..ad181db 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ }, "dependencies": { "@godaddy/terminus": "^4.12.1", - "@openforis/arena-core": "^0.0.174", + "@openforis/arena-core": "^0.0.188", "bcryptjs": "^2.4.3", "body-parser": "^1.20.2", "compression": "^1.7.4", diff --git a/src/api/api.ts b/src/api/api.ts index 0143a11..2019de5 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,12 +1,15 @@ import { Express } from 'express' import { ExpressInitializer } from '../server' -import { ChainApi } from './chain' + import { AuthApi } from './auth' +import { ChainApi } from './chain' +import { DataQueryApi } from './dataQuery' export const Api: ExpressInitializer = { init: (express: Express): void => { - ChainApi.init(express) AuthApi.init(express) + ChainApi.init(express) + DataQueryApi.init(express) }, } diff --git a/src/api/dataQuery/create.ts b/src/api/dataQuery/create.ts new file mode 100644 index 0000000..26c39fe --- /dev/null +++ b/src/api/dataQuery/create.ts @@ -0,0 +1,32 @@ +import { Express } from 'express' + +import { ServiceRegistry } from '@openforis/arena-core' + +import { ExpressInitializer } from '../../server' +import { ServerServiceType } from '../../server/arenaServer/serverServiceType' +import { DataQueryService } from '../../service' +import { Requests } from '../../utils' + +import { ApiEndpoint } from '../endpoint' + +const getDataQueryService = (): DataQueryService => + ServiceRegistry.getInstance().getService(ServerServiceType.dataQuery) + +export const DataQueryCreate: ExpressInitializer = { + init: (express: Express): void => { + express.post(ApiEndpoint.dataQuery.dataQuery(':surveyId', ':queryUuid'), async (req, res, next) => { + try { + const { surveyId } = Requests.getParams(req) + const querySummary = req.body + + const service = getDataQueryService() + + const querySummaryInserted = await service.insert({ surveyId, item: querySummary }) + + res.json(querySummaryInserted) + } catch (error) { + next(error) + } + }) + }, +} diff --git a/src/api/dataQuery/delete.ts b/src/api/dataQuery/delete.ts new file mode 100644 index 0000000..0f74aec --- /dev/null +++ b/src/api/dataQuery/delete.ts @@ -0,0 +1,31 @@ +import { Express } from 'express' + +import { ServiceRegistry } from '@openforis/arena-core' + +import { ExpressInitializer } from '../../server' +import { ServerServiceType } from '../../server/arenaServer/serverServiceType' +import { DataQueryService } from '../../service' +import { Requests } from '../../utils' + +import { ApiEndpoint } from '../endpoint' + +const getDataQueryService = (): DataQueryService => + ServiceRegistry.getInstance().getService(ServerServiceType.dataQuery) + +export const DataQueryDelete: ExpressInitializer = { + init: (express: Express): void => { + express.delete(ApiEndpoint.dataQuery.dataQuery(':surveyId', ':queryUuid'), async (req, res, next) => { + try { + const { surveyId, queryUuid } = Requests.getParams(req) + + const service = getDataQueryService() + + const querySummaryUpdated = await service.deleteItem({ surveyId, uuid: queryUuid }) + + res.json(querySummaryUpdated) + } catch (error) { + next(error) + } + }) + }, +} diff --git a/src/api/dataQuery/index.ts b/src/api/dataQuery/index.ts new file mode 100644 index 0000000..1212cb4 --- /dev/null +++ b/src/api/dataQuery/index.ts @@ -0,0 +1,16 @@ +import { Express } from 'express' + +import { ExpressInitializer } from '../../server' +import { DataQueryRead } from './read' +import { DataQueryCreate } from './create' +import { DataQueryUpdate } from './update' +import { DataQueryDelete } from './delete' + +export const DataQueryApi: ExpressInitializer = { + init: (express: Express): void => { + DataQueryCreate.init(express) + DataQueryRead.init(express) + DataQueryUpdate.init(express) + DataQueryDelete.init(express) + }, +} diff --git a/src/api/dataQuery/read.ts b/src/api/dataQuery/read.ts new file mode 100644 index 0000000..391ad9a --- /dev/null +++ b/src/api/dataQuery/read.ts @@ -0,0 +1,59 @@ +import { Express } from 'express' + +import { ServiceRegistry } from '@openforis/arena-core' + +import { ExpressInitializer } from '../../server' +import { ServerServiceType } from '../../server/arenaServer/serverServiceType' +import { DataQueryService } from '../../service' +import { Requests } from '../../utils' + +import { ApiEndpoint } from '../endpoint' + +const getDataQueryService = (): DataQueryService => + ServiceRegistry.getInstance().getService(ServerServiceType.dataQuery) as DataQueryService + +export const DataQueryRead: ExpressInitializer = { + init: (express: Express): void => { + express.get(ApiEndpoint.dataQuery.dataQueriesCount(':surveyId'), async (req, res, next) => { + try { + const { surveyId } = Requests.getParams(req) + + const service = getDataQueryService() + + const count = await service.count({ surveyId }) + + res.json({ count }) + } catch (error) { + next(error) + } + }) + + express.get(ApiEndpoint.dataQuery.dataQueries(':surveyId'), async (req, res, next) => { + try { + const { surveyId } = Requests.getParams(req) + + const service = getDataQueryService() + + const list = await service.getAll({ surveyId }) + + res.json({ list }) + } catch (error) { + next(error) + } + }) + + express.get(ApiEndpoint.dataQuery.dataQuery(':surveyId', ':querySummaryUuid'), async (req, res, next) => { + try { + const { surveyId, querySummaryUuid } = Requests.getParams(req) + + const service = getDataQueryService() + + const dataQuerySummary = await service.getByUuid({ surveyId, uuid: querySummaryUuid }) + + res.json(dataQuerySummary) + } catch (error) { + next(error) + } + }) + }, +} diff --git a/src/api/dataQuery/update.ts b/src/api/dataQuery/update.ts new file mode 100644 index 0000000..0f958f4 --- /dev/null +++ b/src/api/dataQuery/update.ts @@ -0,0 +1,32 @@ +import { Express } from 'express' + +import { ServiceRegistry } from '@openforis/arena-core' + +import { ExpressInitializer } from '../../server' +import { ServerServiceType } from '../../server/arenaServer/serverServiceType' +import { DataQueryService } from '../../service' +import { Requests } from '../../utils' + +import { ApiEndpoint } from '../endpoint' + +const getDataQueryService = (): DataQueryService => + ServiceRegistry.getInstance().getService(ServerServiceType.dataQuery) + +export const DataQueryUpdate: ExpressInitializer = { + init: (express: Express): void => { + express.put(ApiEndpoint.dataQuery.dataQuery(':surveyId', ':queryUuid'), async (req, res, next) => { + try { + const { surveyId } = Requests.getParams(req) + const querySummary = req.body + + const service = getDataQueryService() + + const querySummaryUpdated = await service.update({ surveyId, item: querySummary }) + + res.json(querySummaryUpdated) + } catch (error) { + next(error) + } + }) + }, +} diff --git a/src/api/endpoint/dataQuery.ts b/src/api/endpoint/dataQuery.ts new file mode 100644 index 0000000..30c6c26 --- /dev/null +++ b/src/api/endpoint/dataQuery.ts @@ -0,0 +1,10 @@ +import { getApiPathSurvey } from './common' + +const moduleName = 'data_queries' + +export const dataQuery = { + dataQueriesCount: (surveyId: string): string => getApiPathSurvey(surveyId, moduleName, 'count'), + dataQueries: (surveyId: string): string => getApiPathSurvey(surveyId, moduleName), + dataQuery: (surveyId: string, querySummaryUuid: string): string => + getApiPathSurvey(surveyId, moduleName, querySummaryUuid), +} diff --git a/src/api/endpoint/index.ts b/src/api/endpoint/index.ts index afa00e9..f528f9f 100644 --- a/src/api/endpoint/index.ts +++ b/src/api/endpoint/index.ts @@ -1,7 +1,9 @@ import { auth } from './auth' import { chain } from './chain' +import { dataQuery } from './dataQuery' export const ApiEndpoint = { auth, chain, + dataQuery, } diff --git a/src/api/middleware/auth.ts b/src/api/middleware/auth.ts index f9bf70d..5e8873d 100644 --- a/src/api/middleware/auth.ts +++ b/src/api/middleware/auth.ts @@ -9,7 +9,7 @@ import { Users, UserService, } from '@openforis/arena-core' -import { UnauthorizedError } from '../../server' +import { UnauthorizedError } from '../../server/error' import { Requests } from '../../utils' type PermissionFn = (user: User, ...args: Array) => boolean @@ -34,56 +34,47 @@ const checkPermission = (req: Request, next: NextFunction, permissionFn: Permiss } } -const requireSurveyPermission = (permissionFn: PermissionFn) => async ( - req: Request, - _res: Response, - next: NextFunction -) => { - try { - const { surveyId } = Requests.getParams(req) - const service = ServiceRegistry.getInstance().getService(ServiceType.survey) as SurveyService - const survey = await service.get({ surveyId }) +const requireSurveyPermission = + (permissionFn: PermissionFn) => async (req: Request, _res: Response, next: NextFunction) => { + try { + const { surveyId } = Requests.getParams(req) + const service = ServiceRegistry.getInstance().getService(ServiceType.survey) as SurveyService + const survey = await service.get({ surveyId }) - checkPermission(req, next, permissionFn, survey) - } catch (error) { - next(error) + checkPermission(req, next, permissionFn, survey) + } catch (error) { + next(error) + } } -} -const requireRecordPermission = (permissionFn: PermissionFn) => async ( - req: Request, - _res: Response, - next: NextFunction -) => { - try { - const { surveyId, recordUuid } = Requests.getParams(req) - const service = ServiceRegistry.getInstance().getService(ServiceType.record) as RecordService - const record = await service.get({ surveyId, recordUuid }) +const requireRecordPermission = + (permissionFn: PermissionFn) => async (req: Request, _res: Response, next: NextFunction) => { + try { + const { surveyId, recordUuid } = Requests.getParams(req) + const service = ServiceRegistry.getInstance().getService(ServiceType.record) as RecordService + const record = await service.get({ surveyId, recordUuid }) - checkPermission(req, next, permissionFn, record) - } catch (error) { - next(error) + checkPermission(req, next, permissionFn, record) + } catch (error) { + next(error) + } } -} -const requireUserPermission = (permissionFn: PermissionFn) => async ( - req: Request, - _res: Response, - next: NextFunction -) => { - try { - const { surveyId, userUuid } = Requests.getParams(req) - const serviceRegistry = ServiceRegistry.getInstance() - const surveyService = serviceRegistry.getService(ServiceType.survey) as SurveyService - const userService = serviceRegistry.getService(ServiceType.user) as UserService - const survey = await surveyService.get({ surveyId }) - const userToEdit = await userService.get({ userUuid }) +const requireUserPermission = + (permissionFn: PermissionFn) => async (req: Request, _res: Response, next: NextFunction) => { + try { + const { surveyId, userUuid } = Requests.getParams(req) + const serviceRegistry = ServiceRegistry.getInstance() + const surveyService = serviceRegistry.getService(ServiceType.survey) as SurveyService + const userService = serviceRegistry.getService(ServiceType.user) as UserService + const survey = await surveyService.get({ surveyId }) + const userToEdit = await userService.get({ userUuid }) - checkPermission(req, next, permissionFn, survey, userToEdit) - } catch (error) { - next(error) + checkPermission(req, next, permissionFn, survey, userToEdit) + } catch (error) { + next(error) + } } -} export const ApiAuthMiddleware = { // Admin diff --git a/src/db/dbMigrator/dbMigrator.ts b/src/db/dbMigrator/dbMigrator.ts index 2d44d9a..0b8b415 100644 --- a/src/db/dbMigrator/dbMigrator.ts +++ b/src/db/dbMigrator/dbMigrator.ts @@ -23,8 +23,9 @@ const migrateSchema = async (params: { schema?: string; migrationsFolder?: strin } const migrateSurveySchema = async (surveyId: number): Promise => { - logger.info(`starting db migrations for survey ${surveyId}`) + logger.info(`migrations for survey ${surveyId} - start`) await migrateSchema({ schema: Schemata.getSchemaSurvey(surveyId) }) + logger.info(`migrations for survey ${surveyId} - end`) } const migrateSurveySchemas = async (): Promise => { @@ -33,7 +34,7 @@ const migrateSurveySchemas = async (): Promise => { logger.info(`starting survey migrations for ${surveyIds.length} surveys`) - for (const surveyId of surveyIds) { + for await (const surveyId of surveyIds) { await migrateSurveySchema(surveyId) } diff --git a/src/db/dbMigrator/migration/survey/migrations/20240403130324-add-table-data-query.js b/src/db/dbMigrator/migration/survey/migrations/20240403130324-add-table-data-query.js new file mode 100644 index 0000000..d2db4f6 --- /dev/null +++ b/src/db/dbMigrator/migration/survey/migrations/20240403130324-add-table-data-query.js @@ -0,0 +1,51 @@ +'use strict' + +var dbm +var type +var seed +var fs = require('fs') +var path = require('path') +var Promise + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate + type = dbm.dataType + seed = seedLink + Promise = options.Promise +} + +exports.up = function (db) { + var filePath = path.join(__dirname, 'sqls', '20240403130324-add-table-data-query-up.sql') + return new Promise(function (resolve, reject) { + fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) { + if (err) return reject(err) + console.log('received data: ' + data) + + resolve(data) + }) + }).then(function (data) { + return db.runSql(data) + }) +} + +exports.down = function (db) { + var filePath = path.join(__dirname, 'sqls', '20240403130324-add-table-data-query-down.sql') + return new Promise(function (resolve, reject) { + fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) { + if (err) return reject(err) + console.log('received data: ' + data) + + resolve(data) + }) + }).then(function (data) { + return db.runSql(data) + }) +} + +exports._meta = { + version: 1, +} diff --git a/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-down.sql b/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-down.sql new file mode 100644 index 0000000..44f074e --- /dev/null +++ b/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-down.sql @@ -0,0 +1 @@ +/* Replace with your SQL commands */ \ No newline at end of file diff --git a/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-up.sql b/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-up.sql new file mode 100644 index 0000000..073bd3b --- /dev/null +++ b/src/db/dbMigrator/migration/survey/migrations/sqls/20240403130324-add-table-data-query-up.sql @@ -0,0 +1,14 @@ +CREATE TABLE + data_query +( + id bigint NOT NULL GENERATED ALWAYS AS IDENTITY, + uuid uuid NOT NULL DEFAULT uuid_generate_v4(), + + props jsonb NOT NULL DEFAULT '{}'::jsonb, + content jsonb NOT NULL DEFAULT '{}'::jsonb, + + date_created TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), + date_modified TIMESTAMP NOT NULL DEFAULT (now() AT TIME ZONE 'UTC'), + + PRIMARY KEY (id) +); diff --git a/src/db/dbs/index.ts b/src/db/dbs/index.ts index ee5e6d3..54acaa4 100644 --- a/src/db/dbs/index.ts +++ b/src/db/dbs/index.ts @@ -1,7 +1,8 @@ -import { transformCallback } from './transformCallback' +import { transformCallback, transformCallbackCount } from './transformCallback' import { toChar } from './toChar' export const DBs = { transformCallback, + transformCallbackCount, toChar, } diff --git a/src/db/dbs/transformCallback.ts b/src/db/dbs/transformCallback.ts index aa3a372..9f93676 100644 --- a/src/db/dbs/transformCallback.ts +++ b/src/db/dbs/transformCallback.ts @@ -21,15 +21,23 @@ export const transformCallback = (options: { draft?: boolean assocPublishedDraft?: boolean backup?: boolean + skip?: string[] }): any => { - const { row, draft = false, assocPublishedDraft = false, backup = false } = options + const { row, draft = false, assocPublishedDraft = false, backup = false, skip = [] } = options if (row === null) { return null } // Assoc published and draft properties based on props const currentRow = backup || assocPublishedDraft ? _assocPublishedDraft(row) : row - const rowUpdated = Objects.camelize(currentRow, { skip: ['validation', 'props', 'props_draft'] }) + const rowUpdated = Objects.camelize(currentRow, { + skip: ['validation', 'props', 'props_draft', ...skip], + sideEffect: true, + }) + + if (!Object.hasOwn(rowUpdated, 'props_draft')) { + return rowUpdated + } if (!backup) { return mergeProps({ row: rowUpdated, draft }) @@ -40,3 +48,5 @@ export const transformCallback = (options: { delete rowUpdated.props_draft return rowUpdated } + +export const transformCallbackCount = (row: any): number => Number(row?.count) diff --git a/src/db/index.ts b/src/db/index.ts index ab3af5e..9fa5155 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -7,7 +7,7 @@ export { DBMigrator } from './dbMigrator' export { Schemata } from './schemata' -export { SqlSelectBuilder, SQLs, SqlJoinBuilder } from './sql' +export { SqlDeleteBuilder, SqlInsertBuilder, SqlSelectBuilder, SQLs, SqlJoinBuilder, SqlUpdateBuilder } from './sql' export { TableSchemaPublic, @@ -17,6 +17,7 @@ export { TableChain, TableChainNodeDef, TableChainNodeDefAggregate, + TableDataQuery, TableNodeDef, TableRecord, TableSurvey, diff --git a/src/db/sql/index.ts b/src/db/sql/index.ts index 111937f..bbab53c 100644 --- a/src/db/sql/index.ts +++ b/src/db/sql/index.ts @@ -1,4 +1,7 @@ export { SQLs } from './sqls' -export { SqlSelectBuilder } from './sqlSelectBuilder' +export { SqlDeleteBuilder } from './sqlDeleteBuilder' +export { SqlInsertBuilder } from './sqlInsertBuilder' export { SqlJoinBuilder } from './sqlJoinBuilder' +export { SqlSelectBuilder } from './sqlSelectBuilder' +export { SqlSelectCountBuilder } from './sqlSelectCountBuilder' export { SqlUpdateBuilder } from './sqlUpdateBuilder' diff --git a/src/db/sql/sqlDeleteBuilder.ts b/src/db/sql/sqlDeleteBuilder.ts new file mode 100644 index 0000000..b1b8af6 --- /dev/null +++ b/src/db/sql/sqlDeleteBuilder.ts @@ -0,0 +1,40 @@ +import { Objects } from '@openforis/arena-core' +import { SqlBuilder } from './sqlBuilder' +import { Table } from '../table' +import { Column } from '../column' + +interface ValuesByColumn { + [key: string]: any +} + +export class SqlDeleteBuilder extends SqlBuilder { + private _table: Table | null = null + private _whereValues: ValuesByColumn = {} + private _returning: Column[] = [] + + deleteFrom(table: Table): this { + this._table = table + return this + } + + where(values: ValuesByColumn): this { + this._whereValues = values + return this + } + + returning(...fields: Array): this { + this._returning.push(...fields) + return this + } + + build(): string { + if (Objects.isEmpty(this._table) || Objects.isEmpty(this._whereValues)) + throw new Error(`missingParams, ${this._table}, ${this._whereValues}`) + + const whereConditions = Object.keys(this._whereValues) + .map((columnName) => `${columnName} = $/${columnName}/`) + .join(' AND ') + + return `DELETE FROM ${this._table} WHERE ${whereConditions}` + } +} diff --git a/src/db/sql/sqlInsertBuilder.ts b/src/db/sql/sqlInsertBuilder.ts new file mode 100644 index 0000000..c86588d --- /dev/null +++ b/src/db/sql/sqlInsertBuilder.ts @@ -0,0 +1,49 @@ +import { Objects } from '@openforis/arena-core' +import { SqlBuilder } from './sqlBuilder' +import { Table } from '../table' +import { Column } from '../column' + +interface ValuesByColumn { + [key: string]: any +} + +export class SqlInsertBuilder extends SqlBuilder { + private _table: Table | null = null + private _valuesByColumn: ValuesByColumn = {} + private _returning: Column[] = [] + + insertInto(table: Table): this { + this._table = table + return this + } + + valuesByColumn(valuesByColumn: ValuesByColumn): this { + this._valuesByColumn = valuesByColumn + return this + } + + returning(...fields: Array): this { + this._returning.push(...fields) + return this + } + + build(): string { + if (Objects.isEmpty(this._table) || Objects.isEmpty(this._valuesByColumn)) + throw new Error(`missingParams, ${this._table}, ${this._valuesByColumn}`) + + const columnNames: string[] = [] + const valuesParams: string[] = [] + + Object.keys(this._valuesByColumn).forEach((columnName) => { + columnNames.push(columnName) + valuesParams.push(`$/${columnName}/`) + }) + + const parts = [`INSERT INTO ${this._table}`, `(${columnNames.join(', ')})`, `VALUES (${valuesParams.join(', ')})`] + + if (!Objects.isEmpty(this._returning)) { + parts.push(`RETURNING ${this._returning.join(', ')}`) + } + return parts.join(' ') + } +} diff --git a/src/db/sql/sqlSelectCountBuilder.ts b/src/db/sql/sqlSelectCountBuilder.ts new file mode 100644 index 0000000..b148df3 --- /dev/null +++ b/src/db/sql/sqlSelectCountBuilder.ts @@ -0,0 +1,34 @@ +import { Objects } from '@openforis/arena-core' +import { SqlBuilder } from './sqlBuilder' +import { Table } from '../table' + +interface ValuesByColumn { + [key: string]: any +} + +export class SqlSelectCountBuilder extends SqlBuilder { + private _table: Table | null = null + private _whereValues: ValuesByColumn = {} + + selectCountFrom(table: Table): this { + this._table = table + return this + } + + where(values: ValuesByColumn): this { + this._whereValues = values + return this + } + + build(): string { + if (Objects.isEmpty(this._table)) throw new Error(`missingParams, ${this._table}`) + + const whereConditions = Object.keys(this._whereValues) + .map((columnName) => `${columnName} = $/${columnName}/`) + .join(' AND ') + + const whereClause = whereConditions ? ` WHERE ${whereConditions}` : '' + + return `SELECT COUNT(*) FROM ${this._table}${whereClause}` + } +} diff --git a/src/db/table/index.ts b/src/db/table/index.ts index 4a34669..d1f3c93 100644 --- a/src/db/table/index.ts +++ b/src/db/table/index.ts @@ -14,6 +14,7 @@ export { TableChain, TableChainNodeDef, TableChainNodeDefAggregate, + TableDataQuery, TableNodeDef, TableRecord, } from './schemaSurvey' diff --git a/src/db/table/schemaSurvey/dataQuery.ts b/src/db/table/schemaSurvey/dataQuery.ts new file mode 100644 index 0000000..bd3b162 --- /dev/null +++ b/src/db/table/schemaSurvey/dataQuery.ts @@ -0,0 +1,19 @@ +import { TableSchemaSurvey } from './tableSchemaSurvey' +import { Column, ColumnType } from '../../column' + +export class TableDataQuery extends TableSchemaSurvey { + readonly id: Column = new Column(this, 'id', ColumnType.bigint) + readonly uuid: Column = new Column(this, 'uuid', ColumnType.uuid) + readonly props: Column = new Column(this, 'props', ColumnType.jsonb) + readonly content: Column = new Column(this, 'content', ColumnType.jsonb) + readonly dateCreated: Column = new Column(this, 'date_created', ColumnType.timeStamp) + readonly dateModified: Column = new Column(this, 'date_modified', ColumnType.timeStamp) + + constructor(surveyId: number) { + super(surveyId, 'data_query') + } + + get summaryColumns(): Column[] { + return [this.id, this.uuid, this.props, this.dateCreated, this.dateModified] + } +} diff --git a/src/db/table/schemaSurvey/index.ts b/src/db/table/schemaSurvey/index.ts index e047112..9ef5347 100644 --- a/src/db/table/schemaSurvey/index.ts +++ b/src/db/table/schemaSurvey/index.ts @@ -4,6 +4,8 @@ export { TableChain } from './chain' export { TableChainNodeDef } from './chainNodeDef' export { TableChainNodeDefAggregate } from './chainNodeDefAggregate' +export { TableDataQuery } from './dataQuery' + export { TableNodeDef } from './nodeDef' export { TableRecord } from './record' diff --git a/src/repository/dataQuery/delete.ts b/src/repository/dataQuery/delete.ts new file mode 100644 index 0000000..2d0990e --- /dev/null +++ b/src/repository/dataQuery/delete.ts @@ -0,0 +1,25 @@ +import { DataQuerySummary } from '@openforis/arena-core' + +import { BaseProtocol, DB, DBs, SqlDeleteBuilder, TableDataQuery } from '../../db' + +export const deleteItem = ( + params: { surveyId: number; uuid: string }, + client: BaseProtocol = DB +): Promise => { + const { surveyId, uuid } = params + if (!surveyId || !uuid) throw new Error(`missingParams, ${params}`) + + const table = new TableDataQuery(surveyId) + + const values = { + [table.uuid.columnName]: uuid, + } + + const sql = new SqlDeleteBuilder() + .deleteFrom(table) + .where(values) + .returning(...table.summaryColumns) + .build() + + return client.oneOrNone(sql, values, (row) => DBs.transformCallback({ row })) +} diff --git a/src/repository/dataQuery/index.ts b/src/repository/dataQuery/index.ts new file mode 100644 index 0000000..e03e161 --- /dev/null +++ b/src/repository/dataQuery/index.ts @@ -0,0 +1,13 @@ +import { insert } from './insert' +import { count, getAll, getByUuid } from './read' +import { update } from './update' +import { deleteItem } from './delete' + +export const DataQueryRepository = { + count, + deleteItem, + getByUuid, + getAll, + insert, + update, +} diff --git a/src/repository/dataQuery/insert.ts b/src/repository/dataQuery/insert.ts new file mode 100644 index 0000000..c9a7315 --- /dev/null +++ b/src/repository/dataQuery/insert.ts @@ -0,0 +1,28 @@ +import { DataQuerySummary } from '@openforis/arena-core' + +import { BaseProtocol, DB, DBs, SqlInsertBuilder, TableDataQuery } from '../../db' + +export const insert = ( + params: { surveyId: number; item: DataQuerySummary }, + client: BaseProtocol = DB +): Promise => { + const { surveyId, item } = params + if (!surveyId || !item) throw new Error(`missingParams, ${params}`) + + const table = new TableDataQuery(surveyId) + const { content, props, uuid } = item + + const values = { + [table.content.columnName]: content, + [table.props.columnName]: props, + [table.uuid.columnName]: uuid, + } + + const sql = new SqlInsertBuilder() + .insertInto(table) + .valuesByColumn(values) + .returning(...table.summaryColumns) + .build() + + return client.one(sql, values, (row) => DBs.transformCallback({ row })) +} diff --git a/src/repository/dataQuery/read.ts b/src/repository/dataQuery/read.ts new file mode 100644 index 0000000..f75163d --- /dev/null +++ b/src/repository/dataQuery/read.ts @@ -0,0 +1,52 @@ +import { DataQuerySummary } from '@openforis/arena-core' + +import { BaseProtocol, DB, DBs, SqlSelectBuilder, TableDataQuery } from '../../db' +import { SqlSelectCountBuilder } from '../../db/sql' + +export const count = (params: { surveyId: number }, client: BaseProtocol = DB): Promise => { + const { surveyId } = params + const table = new TableDataQuery(surveyId) + const sql = new SqlSelectCountBuilder().selectCountFrom(table).build() + return client.one(sql, {}, DBs.transformCallbackCount) +} + +export const getAll = (params: { surveyId: number }, client: BaseProtocol = DB): Promise => { + const { surveyId } = params + if (!surveyId) throw new Error(`missingParams, ${params}`) + + const table = new TableDataQuery(surveyId) + + const sql = new SqlSelectBuilder() + .select(table.id, table.uuid, table.props, table.dateCreated, table.dateModified) + .from(table) + .build() + + return client.map(sql, [surveyId], (row) => DBs.transformCallback({ row })) +} + +/** + * Returns a query by uuid. + * + * @param params + * @param client - Database client. + */ +export const getByUuid = async ( + params: { + surveyId: number + uuid: string + }, + client: BaseProtocol = DB +): Promise => { + const { surveyId, uuid } = params + + const table = new TableDataQuery(surveyId) + const sql = new SqlSelectBuilder() + .select(table.id, table.uuid, table.props, table.content, table.dateCreated, table.dateModified) + .from(table) + .where(`${table.uuid} = $1`) + .build() + + return client.one(sql, [uuid], (row) => + DBs.transformCallback({ row, skip: [table.content.columnName] }) + ) +} diff --git a/src/repository/dataQuery/update.ts b/src/repository/dataQuery/update.ts new file mode 100644 index 0000000..1b515bb --- /dev/null +++ b/src/repository/dataQuery/update.ts @@ -0,0 +1,23 @@ +import { DataQuerySummary } from '@openforis/arena-core' + +import { BaseProtocol, DB, DBs, TableDataQuery } from '../../db' +import { SqlUpdateBuilder } from '../../db/sql' + +export const update = async ( + params: { surveyId: number; item: DataQuerySummary }, + client: BaseProtocol = DB +): Promise => { + const { surveyId, item } = params + + const table = new TableDataQuery(surveyId) + + const sql = new SqlUpdateBuilder() + .update(table) + .set(table.props, `${table.props} || $1::jsonb`) + .set(table.content, '$2::jsonb') + .where(`${table.uuid} = $3`) + .returning(...table.summaryColumns) + .build() + + return client.one(sql, [item.props, item.content, item.uuid], (row) => DBs.transformCallback({ row })) +} diff --git a/src/server/arenaServer/registerServices.ts b/src/server/arenaServer/registerServices.ts index 861c184..083b4c9 100644 --- a/src/server/arenaServer/registerServices.ts +++ b/src/server/arenaServer/registerServices.ts @@ -1,8 +1,10 @@ import { ServiceRegistry, ServiceType } from '@openforis/arena-core' -import { SurveyServiceServer, UserServiceServer } from '../../service' +import { DataQueryServiceServer, SurveyServiceServer, UserServiceServer } from '../../service' +import { ServerServiceType } from './serverServiceType' export const registerServices = (): void => { ServiceRegistry.getInstance() + .registerService(ServerServiceType.dataQuery, DataQueryServiceServer) .registerService(ServiceType.survey, SurveyServiceServer) .registerService(ServiceType.user, UserServiceServer) } diff --git a/src/server/arenaServer/serverServiceType.ts b/src/server/arenaServer/serverServiceType.ts new file mode 100644 index 0000000..8756832 --- /dev/null +++ b/src/server/arenaServer/serverServiceType.ts @@ -0,0 +1,3 @@ +export enum ServerServiceType { + dataQuery = 'dataQuery', +} diff --git a/src/service/SurveyItemService.ts b/src/service/SurveyItemService.ts new file mode 100644 index 0000000..3e79fd7 --- /dev/null +++ b/src/service/SurveyItemService.ts @@ -0,0 +1,16 @@ +import { ArenaService } from '@openforis/arena-core' +import { BaseProtocol } from '../db' + +export interface SurveyItemService extends ArenaService { + count(params: { surveyId: number }, client?: BaseProtocol): Promise + + getAll(params: { surveyId: number }, client?: BaseProtocol): Promise + + getByUuid(params: { surveyId: number; uuid: string }, client?: BaseProtocol): Promise + + insert(params: { surveyId: number; item: T }, client?: BaseProtocol): Promise + + update(params: { surveyId: number; item: T }, client?: BaseProtocol): Promise + + deleteItem(params: { surveyId: number; uuid: string }, client?: BaseProtocol): Promise +} diff --git a/src/service/dataQuery/index.ts b/src/service/dataQuery/index.ts new file mode 100644 index 0000000..746eaac --- /dev/null +++ b/src/service/dataQuery/index.ts @@ -0,0 +1,17 @@ +import { DataQuerySummary } from '@openforis/arena-core' + +import { DataQueryRepository } from '../../repository/dataQuery' +import { SurveyItemService } from '../SurveyItemService' + +const { count, deleteItem, getAll, getByUuid, insert, update } = DataQueryRepository + +export interface DataQueryService extends SurveyItemService {} + +export const DataQueryServiceServer: DataQueryService = { + count, + getAll, + getByUuid, + insert, + update, + deleteItem, +} diff --git a/src/service/index.ts b/src/service/index.ts index 0aa8ae6..0ce1fcd 100644 --- a/src/service/index.ts +++ b/src/service/index.ts @@ -1,2 +1,4 @@ +export type { DataQueryService } from './dataQuery' +export { DataQueryServiceServer } from './dataQuery' export { SurveyServiceServer } from './survey' export { UserServiceServer } from './user' diff --git a/src/utils/responses.ts b/src/utils/responses.ts index b775dd9..039c1f3 100644 --- a/src/utils/responses.ts +++ b/src/utils/responses.ts @@ -1,5 +1,5 @@ import { Response } from 'express' -import { ServerError, ServerErrorCode, UnauthorizedError } from '../server' +import { ServerError, ServerErrorCode, UnauthorizedError } from '../server/error' enum Status { ok = 'ok', diff --git a/yarn.lock b/yarn.lock index abd4229..5a93ec9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -858,10 +858,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openforis/arena-core@^0.0.174": - version "0.0.174" - resolved "https://npm.pkg.github.com/download/@openforis/arena-core/0.0.174/0dab03cf5567202a785fb42bca367a46789919ae#0dab03cf5567202a785fb42bca367a46789919ae" - integrity sha512-aQDqspdFuw2aTP0guFQJXcepCZ4Q15vD+3sHDMZin50E1MTtjqkbHbKtHvhPvK2k98A1h6el+w742I7mUBUc4Q== +"@openforis/arena-core@^0.0.188": + version "0.0.188" + resolved "https://npm.pkg.github.com/download/@openforis/arena-core/0.0.188/ff79e8e185b69aa124595de65c56b16161878f49#ff79e8e185b69aa124595de65c56b16161878f49" + integrity sha512-BiYLoBpaEIbw5R6XJpxiYMjBl5biGLuj15lHyFj0egxWPpXaGuj0rJl1EHpk9DWwPDBKo21vGpuoxZQAjOHKJQ== dependencies: "@jsep-plugin/regex" "^1.0.3" bignumber.js "^9.1.2" @@ -871,7 +871,7 @@ lodash.frompairs "^4.0.1" lodash.isequal "^4.5.0" lodash.topairs "^4.3.0" - proj4 "^2.9.0" + proj4 "^2.11.0" uuid "^9.0.1" "@pkgjs/parseargs@^0.11.0": @@ -4590,13 +4590,13 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -proj4@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/proj4/-/proj4-2.9.0.tgz#1b61db8c998313f80e2514609b339fb2d28cbb82" - integrity sha512-BoDXEzCVnRJVZoOKA0QHTFtYoE8lUxtX1jST38DJ8U+v1ixY70Kpwi0Llu6YqSWEH2xqu4XMEBNGcgeRIEywoA== +proj4@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/proj4/-/proj4-2.11.0.tgz#795a5790aed30a7535d6a4c5775c0ce2a763cc41" + integrity sha512-SasuTkAx8HnWQHfIyhkdUNJorSJqINHAN3EyMWYiQRVorftz9DHz650YraFgczwgtHOxqnfuDxSNv3C8MUnHeg== dependencies: mgrs "1.0.0" - wkt-parser "^1.3.1" + wkt-parser "^1.3.3" prompt@^1.0.0: version "1.1.0" @@ -5603,10 +5603,10 @@ winston@2.x: isstream "0.1.x" stack-trace "0.0.x" -wkt-parser@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/wkt-parser/-/wkt-parser-1.3.2.tgz#deeff04a21edc5b170a60da418e9ed1d1ab0e219" - integrity sha512-A26BOOo7sHAagyxG7iuRhnKMO7Q3mEOiOT4oGUmohtN/Li5wameeU4S6f8vWw6NADTVKljBs8bzA8JPQgSEMVQ== +wkt-parser@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/wkt-parser/-/wkt-parser-1.3.3.tgz#46b4e3032dd9c86907f7e630b57e3c6ea2bb772b" + integrity sha512-ZnV3yH8/k58ZPACOXeiHaMuXIiaTk1t0hSUVisbO0t4RjA5wPpUytcxeyiN2h+LZRrmuHIh/1UlrR9e7DHDvTw== wrap-ansi@^6.2.0: version "6.2.0"