-
-
Notifications
You must be signed in to change notification settings - Fork 219
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
feat(9586): implement freetext search in cht datasource #9625
base: master
Are you sure you want to change the base?
Changes from 31 commits
ac0ab67
cd11adc
b7c5d1f
f91e393
52ac11b
c1b68cf
45c0ccf
acf903a
89927b1
45abf58
6f5c326
192af16
bb98dcf
43efbef
ede85fd
2b33c07
3bfcace
074b0c1
1907907
a629116
c45cee8
5400736
bf46e4a
b1cf669
98fd4e2
ee32262
b5bc6e6
4a18fc8
eba1b61
9157059
d5fed9c
30d058e
662598c
89eb816
3ea31ef
a7a9272
ef8b93f
43c3896
f97f928
2065125
f8087b9
e6aea4f
c21e52a
c487a7e
ce1013a
19143b4
8dc7de3
91a0125
49553b7
22da4b9
4a0d2bb
bfa0a57
eab5877
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,52 @@ | ||||||
const auth = require('../auth'); | ||||||
const { Contact, Qualifier } = require('@medic/cht-datasource'); | ||||||
const ctx = require('../services/data-context'); | ||||||
const serverUtils = require('../server-utils'); | ||||||
|
||||||
const getContact = ({ with_lineage }) => ctx.bind(with_lineage === 'true' ? Contact.v1.getWithLineage : Contact.v1.get); | ||||||
const getContactIds = () => ctx.bind(Contact.v1.getIdsPage); | ||||||
|
||||||
const checkUserPermissions = async (req) => { | ||||||
const userCtx = await auth.getUserCtx(req); | ||||||
if (!auth.isOnlineOnly(userCtx) || !auth.hasAllPermissions(userCtx, 'can_view_contacts')) { | ||||||
return Promise.reject({ code: 403, message: 'Insufficient privileges' }); | ||||||
} | ||||||
}; | ||||||
|
||||||
module.exports = { | ||||||
v1: { | ||||||
get: serverUtils.doOrError(async (req, res) => { | ||||||
await checkUserPermissions(req); | ||||||
const { uuid } = req.params; | ||||||
const contact = await getContact(req.query)(Qualifier.byUuid(uuid)); | ||||||
|
||||||
if (!contact) { | ||||||
return serverUtils.error({ status: 404, message: 'Contact not found' }, req, res); | ||||||
} | ||||||
|
||||||
return res.json(contact); | ||||||
}), | ||||||
getIds: serverUtils.doOrError(async (req, res) => { | ||||||
await checkUserPermissions(req); | ||||||
|
||||||
if (!req.query.freetext && !req.query.type) { | ||||||
return serverUtils.error({ status: 400, message: 'Either query param freetext or type is required' }, req, res); | ||||||
} | ||||||
const qualifier = {}; | ||||||
|
||||||
if (req.query.freetext) { | ||||||
Object.assign(qualifier, Qualifier.byFreetext(req.query.freetext)); | ||||||
} | ||||||
|
||||||
if (req.query.type) { | ||||||
Object.assign(qualifier, Qualifier.byContactType(req.query.type)); | ||||||
} | ||||||
|
||||||
const limit = req.query.limit ? Number(req.query.limit) : req.query.limit; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems strange that we assign a random non-truthy value (as in: whatever is in
Suggested change
The false is a random pick. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then why not have cht-datasource also do the Number conversion then? Why have this validation here? Is limit ever expected to not be a number? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yeah, in cases like the one above, where it is passed as a query param in REST API, it is expected to be a stringified number. However, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So even if it's a stringified number or a number, we still only ever evaluate it as a number. so it makes sense to only have validation in one spot, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jkuester thoughts on this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at this again, I cannot see any reason why it would be a problem to just directly pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In JS, the query parameters are almost all strings and when they are passed into other functions or classes, will still be strings. I've tried this before. Here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😅 😅 😅 I knew we had discussed this before. I was not able to find that thread you linked to and so I proceeded to repeat the exact same line of reasoning that lead me to start that thread in the first place... 🤦 sigh Now I think I am 100% following what you are saying above and agree with this statement:
The cht-datasource apis are responsible for sanitizing the input to ensure it confirms to the specified expectations. Generally speaking, I do not think cht-datasource should need to include extra logic to "support" different ways that consumers decide to provide data (accidentally or intentionally). The type That being said, the cht-datasource interfaces should be designed to be convenient to consume as long as it does not compromise the clarity of the API. It turns out that TypeScript has a type that represents "a string containing a number value": There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sounds good. As long as we don't have logic around limit in multiple places. Please make the change. |
||||||
|
||||||
const docs = await getContactIds()(qualifier, req.query.cursor, limit); | ||||||
|
||||||
return res.json(docs); | ||||||
}), | ||||||
}, | ||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
const auth = require('../auth'); | ||
const ctx = require('../services/data-context'); | ||
const serverUtils = require('../server-utils'); | ||
const { Report, Qualifier } = require('@medic/cht-datasource'); | ||
|
||
const getReport = () => ctx.bind(Report.v1.get); | ||
const getReportIds = () => ctx.bind(Report.v1.getIdsPage); | ||
|
||
const checkUserPermissions = async (req) => { | ||
const userCtx = await auth.getUserCtx(req); | ||
if (!auth.isOnlineOnly(userCtx) || !auth.hasAllPermissions(userCtx, 'can_view_contacts')) { | ||
sugat009 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Promise.reject({ code: 403, message: 'Insufficient privileges' }); | ||
} | ||
}; | ||
|
||
module.exports = { | ||
v1: { | ||
get: serverUtils.doOrError(async (req, res) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not a big fan of this callback style. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We've been doing this pattern for all the REST endpoints that call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a try-catch block is not so much duplication, and it's more transparent than a nested callback. |
||
await checkUserPermissions(req); | ||
const { uuid } = req.params; | ||
const report = await getReport()(Qualifier.byUuid(uuid)); | ||
|
||
if (!report) { | ||
return serverUtils.error({ status: 404, message: 'Report not found' }, req, res); | ||
} | ||
|
||
return res.json(report); | ||
}), | ||
getIds: serverUtils.doOrError(async (req, res) => { | ||
await checkUserPermissions(req); | ||
|
||
const qualifier = Qualifier.byFreetext(req.query.freetext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So ... this endpoint .. if it doesn't get neither a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
const limit = req.query.limit ? Number(req.query.limit) : req.query.limit; | ||
|
||
const docs = await getReportIds()(qualifier, req.query.cursor, limit); | ||
|
||
return res.json(docs); | ||
}) | ||
} | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,8 +37,10 @@ const exportData = require('./controllers/export-data'); | |
const records = require('./controllers/records'); | ||
const forms = require('./controllers/forms'); | ||
const users = require('./controllers/users'); | ||
const contact = require('./controllers/contact'); | ||
const person = require('./controllers/person'); | ||
const place = require('./controllers/place'); | ||
const report = require('./controllers/report'); | ||
const { people, places } = require('@medic/contacts')(config, db, dataContext); | ||
const upgrade = require('./controllers/upgrade'); | ||
const settings = require('./controllers/settings'); | ||
|
@@ -492,6 +494,12 @@ app.postJson('/api/v1/people', function(req, res) { | |
app.get('/api/v1/person', person.v1.getAll); | ||
app.get('/api/v1/person/:uuid', person.v1.get); | ||
|
||
app.get('/api/v1/contact/id', contact.v1.getIds); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, REST API conventions are to name the API endpoint in a plural way like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That discussion happened in the parent ticket before we spun off the child isssue: #9544 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @jkuester . Your argument here is that "we've already decided and your input is not welcome?" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for being aggressive and confrontational in the above comment. I maintain my comment about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was just trying to provide the context for the discussion that Sugat referenced. 😬 I am happy to continue the design discussion here to come to an agreed upon approach. It will just be most efficient if we all understand what was already said to get us here. When starting work on new REST endpoints for the cht-datasource code, we chose to go with the pattern of singular entity names (so
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with being consistent. I personally can't recall seeing APIs in the world that used this singular form, so for me this seems quite unintuitive. |
||
app.get('/api/v1/contact/:uuid', contact.v1.get); | ||
|
||
app.get('/api/v1/report/id', report.v1.getIds); | ||
app.get('/api/v1/report/:uuid', report.v1.get); | ||
|
||
app.postJson('/api/v1/bulk-delete', bulkDocs.bulkDelete); | ||
|
||
// offline users are not allowed to hydrate documents via the hydrate API | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned in an earlier comment, ideally we should be able to use a
Qualifier.and
function to combine qualifier objects. Something like: