Skip to content

Commit

Permalink
feat: support searching with date ranges (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
naftis authored Sep 26, 2023
1 parent 8de12d1 commit 05ae404
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 93 deletions.
28 changes: 23 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"scripts": {
"start": "npm start --workspace=dci-api",
"dev": "npm run dev --workspace=dci-api",
"test": "npm run test --workspaces",
"test:watch": "npm run test:watch --workspaces"
"test": "npm run test --workspace=dci-api --workspace=dci-opencrvs-bridge",
"test:watch": "npm run test:watch --workspace=dci-api --workspace=dci-opencrvs-bridge"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.4.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/dci-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"hapi-pino": "^12.1.0",
"lodash": "^4.17.21",
"ts-node": "^10.9.1",
"typescript": "^5.1.6",
"typescript": "^5.2.2",
"zod": "^3.22.2",
"zod-validation-error": "^1.5.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/dci-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ createServer().then(async (server) => {
})

export type * from './registry-core-api'
export type { SyncSearchRequest, EventType } from './validations'
export type * from './validations'
87 changes: 49 additions & 38 deletions packages/dci-api/src/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ const authorize = z.object({

const languageCode = z.string().regex(/^[a-z]{3,3}$/)

const version = z.string().optional().default('1.0.0')
const version = z.string().default('1.0.0')

const syncHeader = z.object({
version,
version: version.optional(),
message_id: z.string(),
message_ts: dateTime,
action: z.literal('search'),
Expand All @@ -54,7 +54,7 @@ const syncHeader = z.object({
})

const asyncHeader = z.object({
version,
version: version.optional(),
message_id: z.string(),
message_ts: dateTime,
action: z.literal('search'),
Expand Down Expand Up @@ -87,58 +87,66 @@ const reference = (value: ZodType = z.string()) =>
value
})

const attributeValue = z.string().or(z.number()).or(z.boolean())
const commonSearchCriteria = z.object({
version: version.optional(),
reg_type: reference().optional(),
reg_event_type: reference(eventTypes),
result_record_type: reference(),
sort: z.array(searchSort).optional(),
pagination: paginationRequest.optional(),
consent: consent.optional(),
authorize: authorize.optional()
})

const identifier = z.enum(['BRN', 'DRN', 'MRN', 'OPENCRVS_RECORD_ID', 'NID'])

const identifierTypeValue = z.object({
identifier_type: reference(),
identifier_value: attributeValue
identifier_type: reference(identifier),
identifier_value: z.string()
})

const identifierTypeQuery = z.object({
query_type: z.literal('idtype-value'),
query: identifierTypeValue
})
const identifierTypeQuery = commonSearchCriteria.and(
z.object({
query_type: z.literal('idtype-value'),
query: identifierTypeValue
})
)

const expressionCondition = z.enum(['and'])

const expressionCondition = z.enum(['and', 'or', 'not'])
const expression = z.enum(['gt', 'lt', 'eq', 'ge', 'le'])

const expressionOperator = z.enum(['gt', 'lt', 'eq', 'ge', 'le', 'in'])
const expressionSupportedFields = z.enum(['birthdate'])

const expressionPredicate = z.object({
attribute_name: z.string(),
operator: expressionOperator,
attribute_value: attributeValue
attribute_name: expressionSupportedFields,
operator: expression,
attribute_value: z.coerce.date()
})

const predicateQuery = z.object({
query_type: z.literal('predicate'),
query: z.array(
z.object({
seq_num: z.number().optional(),
expression1: expressionPredicate,
condition: expressionCondition.optional(),
expression2: expressionPredicate.optional()
})
)
})
const predicateQuery = commonSearchCriteria.and(
z.object({
query_type: z.literal('predicate'),
query: z.array(
z.object({
seq_num: z.number().optional(),
expression1: expressionPredicate,
condition: expressionCondition,
expression2: expressionPredicate
})
)
})
)

const searchCriteria = predicateQuery.or(identifierTypeQuery)

const searchRequest = z.object({
transaction_id: z.string().max(99),
search_request: z.array(
z.object({
reference_id: z.string(),
timestamp: dateTime,
search_criteria: z
.object({
version,
reg_type: reference().optional(),
reg_event_type: reference(eventTypes),
result_record_type: reference(),
sort: z.array(searchSort).optional(),
pagination: paginationRequest.optional(),
consent: consent.optional(),
authorize: authorize.optional()
})
.and(predicateQuery.or(identifierTypeQuery)),
search_criteria: searchCriteria,
locale: languageCode.optional().default('eng')
})
)
Expand All @@ -159,3 +167,6 @@ export const asyncSearchRequestSchema = z.object({
export type SyncSearchRequest = TypeOf<typeof syncSearchRequestSchema>
export type EventType = TypeOf<typeof eventTypes>
export type AsyncSearchRequest = TypeOf<typeof asyncSearchRequestSchema>
export type SearchCriteria = TypeOf<typeof searchCriteria>
export type PredicateQuery = TypeOf<typeof predicateQuery>
export type IdentifierTypeQuery = TypeOf<typeof identifierTypeQuery>
9 changes: 9 additions & 0 deletions packages/dci-opencrvs-bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@
"description": "Supplies tools and types for converting DCI search queries into OpenCRVS search queries, and converting the search results into DCI search results.",
"main": "src/index.ts",
"license": "ISC",
"scripts": {
"test": "TZ=utc node --require ts-node/register --test src/**/*.test.ts",
"test:watch": "TZ=utc node --require ts-node/register --test --watch src/**/*.test.ts"
},
"dependencies": {
"@types/lodash": "^4.14.197",
"date-fns": "^2.30.0",
"lodash": "^4.17.21",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/lodash": "^4.14.197",
"ts-node": "^10.9.1"
}
}
41 changes: 0 additions & 41 deletions packages/dci-opencrvs-bridge/src/dci-to-opencrvs.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { describe, it } from 'node:test'
import { searchRequestToAdvancedSearchParameters } from './dci-to-opencrvs'
import assert from 'node:assert'

describe('DCI standard to OpenCRVS', () => {
// `gt` => date + 1 day
// `ge` => date
// `lt` => date - 1 day
// `le` => date
it('converts gt and lt properly', () => {
const parameters = searchRequestToAdvancedSearchParameters({
reference_id: '123456789020211216223812',
timestamp: '2022-12-04T17:20:07-04:00',
search_criteria: {
reg_event_type: { namespace: '?', value: 'birth' },
result_record_type: { value: 'person' },
sort: [{ attribute_name: 'dateOfDeclaration', sort_order: 'asc' }],
pagination: { page_size: 5, page_number: 1 },
query_type: 'predicate',
query: [
{
expression1: {
attribute_name: 'birthdate',
operator: 'gt',
attribute_value: new Date('2010-05-04T00:00:00.000Z')
},
condition: 'and',
expression2: {
attribute_name: 'birthdate',
operator: 'lt',
attribute_value: new Date('2022-05-04T00:00:00.000Z')
}
}
]
},
locale: 'eng'
})

assert.strictEqual(
parameters.advancedSearchParameters.childDoBStart,
'2010-05-05'
)
assert.strictEqual(
parameters.advancedSearchParameters.childDoBEnd,
'2022-05-03'
)
})

it('converts ge and le properly', () => {
const parameters = searchRequestToAdvancedSearchParameters({
reference_id: '123456789020211216223812',
timestamp: '2022-12-04T17:20:07-04:00',
search_criteria: {
reg_event_type: { namespace: '?', value: 'birth' },
result_record_type: { value: 'person' },
sort: [{ attribute_name: 'dateOfDeclaration', sort_order: 'asc' }],
pagination: { page_size: 5, page_number: 1 },
query_type: 'predicate',
query: [
{
expression1: {
attribute_name: 'birthdate',
operator: 'ge',
attribute_value: new Date('2010-05-04')
},
condition: 'and',
expression2: {
attribute_name: 'birthdate',
operator: 'le',
attribute_value: new Date('2022-05-04')
}
}
]
},
locale: 'eng'
})

assert.strictEqual(
parameters.advancedSearchParameters.childDoBStart,
'2010-05-04'
)
assert.strictEqual(
parameters.advancedSearchParameters.childDoBEnd,
'2022-05-04'
)
})
})
Loading

0 comments on commit 05ae404

Please sign in to comment.