Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
64f23ab
Add generate-openapi script to create api docs
jkuester Mar 13, 2026
15ad52f
Move generated docs to cht-datasource/docs so they get pushed to GitH…
jkuester Mar 13, 2026
dc30560
More updates to current docs
jkuester Mar 16, 2026
368500b
Add remaining doc for person endpoints
jkuester Mar 16, 2026
8a3f00a
Add doc for contact endpoints
jkuester Mar 16, 2026
5383b2d
Add doc for report endpoints
jkuester Mar 16, 2026
aaeff97
Add doc for place endpoints
jkuester Mar 16, 2026
5d2bcd5
Add doc for target endpoints
jkuester Mar 17, 2026
95d04f2
Add doc for info endpoints
jkuester Mar 17, 2026
b57bfef
Update generate script to fail on warn
jkuester Mar 17, 2026
aacf368
Add docs for monitoring endpoints
jkuester Mar 17, 2026
def39ff
Add docs for impact endpoints
jkuester Mar 17, 2026
bef704d
Add docs for upgrade endpoints
jkuester Mar 17, 2026
d4cc9fb
Add docs for africas-talking endpoints
jkuester Mar 18, 2026
59e2e62
Add docs for rapidpro endpoints
jkuester Mar 18, 2026
782dfc4
Add docs for sms-gateway endpoints
jkuester Mar 18, 2026
3c1f0b5
Add docs for export endpoints
jkuester Mar 18, 2026
519f848
Add docs for record endpoints
jkuester Mar 18, 2026
9cde9b2
Add docs for forms endpoints
jkuester Mar 18, 2026
167fcf6
Add docs for users endpoints
jkuester Mar 19, 2026
f759f7e
Add docs for places endpoints
jkuester Mar 19, 2026
ae2795c
Add docs for people endpoints
jkuester Mar 19, 2026
b14506a
Add docs for bulk-delete endpoints
jkuester Mar 19, 2026
d56c0c7
Add docs for hydration endpoints
jkuester Mar 19, 2026
0d8836d
Add docs for contacts-by-phone endpoints
jkuester Mar 19, 2026
5f81cc6
Add docs for settings endpoints
jkuester Mar 19, 2026
41360b0
Add docs for credentials endpoints
jkuester Mar 19, 2026
c139117
Add shared schema for OkResponse
jkuester Mar 19, 2026
0d21e91
Add doc for replication-limit-log endpoint
jkuester Mar 19, 2026
73be575
Clean up openapi arrays
jkuester Mar 19, 2026
b04dcfa
Switch Users tab to just User
jkuester Mar 19, 2026
1f65f6e
Switch Users tab to just User
jkuester Mar 19, 2026
0c4697c
Fix build-documentation script
jkuester Mar 25, 2026
b4fe7d7
Clean up and simplify doc building into single script. Update CI to …
jkuester Mar 25, 2026
8ec6ec2
Fix sonar issues in generation script
jkuester Mar 25, 2026
5bd00f5
Merge branch 'master' into 7005_openapi
jkuester Mar 25, 2026
4b4aa3e
Fix minor issues
jkuester Mar 26, 2026
61b98ac
More cleanup
jkuester Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 39 additions & 26 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,27 @@ jobs:
- run: npm ci
- run: npm run lint-translations

build-generated-docs:
needs: build
name: Build generated docs
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- name: Build Documentation
run: npm run build-documentation
- name: Cache generated docs
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@v4
with:
key: cht-datasource-docs
path: ./shared-libs/cht-datasource/docs

tests:
needs: build
name: ${{ matrix.cmd }}-${{ matrix.suite || '' }}${{ matrix.chrome-version == '107' && '-minimum-browser' || '' }}
Expand Down Expand Up @@ -353,7 +374,7 @@ jobs:
if: ${{ failure() }}

publish:
needs: [tests, config-tests, test-cht-form]
needs: [tests, config-tests, test-cht-form, build-generated-docs]
name: Publish branch build
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down Expand Up @@ -403,31 +424,23 @@ jobs:
node ./publish.js
node ./tag-docker-images.js

publish-generated-docs:
needs: [publish]
name: Publish generated docs
runs-on: ubuntu-latest
timeout-minutes: 5
if: ${{ github.event_name != 'pull_request' }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- name: Generate TypeDoc
run: npm run --prefix shared-libs/cht-datasource gen-docs
- name: Main Branch Only - Deploy to GH pages
uses: peaceiris/actions-gh-pages@v4
if: github.ref == 'refs/heads/master'
with:
personal_token: ${{ secrets.DEPLOY_TO_GITHUB_PAGES }}
external_repository: medic/cht-datasource
publish_dir: ./shared-libs/cht-datasource/docs
user_name: medic-ci
user_email: medic-ci@github
publish_branch: main
- name: Restore generated docs
if: github.ref == 'refs/heads/master'
uses: actions/cache/restore@v4
with:
key: cht-datasource-docs
path: ./shared-libs/cht-datasource/docs
fail-on-cache-miss: true
- name: Publish docs to GH pages
if: github.ref == 'refs/heads/master'
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.DEPLOY_TO_GITHUB_PAGES }}
external_repository: medic/cht-datasource
publish_dir: ./shared-libs/cht-datasource/docs
user_name: medic-ci
user_email: medic-ci@github
publish_branch: main

upgrade:
needs: [publish]
Expand Down
102 changes: 102 additions & 0 deletions api/src/controllers/africas-talking.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,61 @@ const validateKey = req => {
});
};

/**
* @openapi
* tags:
* - name: SMS
* description: Operations for SMS messaging integrations
*/
module.exports = {
/**
* @openapi
* /api/v1/sms/africastalking/incoming-messages:
* post:
* summary: Receive incoming SMS from Africa's Talking
* operationId: v1SmsAfricasTalkingIncomingMessagesPost
* description: >
* Webhook endpoint for receiving incoming SMS messages from the Africa's Talking gateway.
* Requires a valid incoming key passed as a query parameter. See the
* [documentation](/building/messaging/gateways/africas-talking/) for more details.
* tags: [SMS]
* parameters:
* - in: query
* name: key
* required: true
* schema:
* type: string
* description: The configured incoming key for authentication.
* requestBody:
* required: true
* content:
* application/x-www-form-urlencoded:
* schema:
* type: object
* properties:
* id:
* type: string
* description: The Africa's Talking message id.
* from:
* type: string
* description: The sender's phone number.
* text:
* type: string
* description: The message content.
* required: [id, from, text]
* responses:
* '200':
* description: Message processing results
* content:
* application/json:
* schema:
* type: object
* additionalProperties: true
* '400':
* $ref: '#/components/responses/BadRequest'
* '403':
* $ref: '#/components/responses/Forbidden'
*/
incomingMessages: (req, res) => {
return validateKey(req)
.then(() => {
Expand All @@ -63,6 +117,54 @@ module.exports = {
.then(results => res.json(results))
.catch(err => serverUtils.error(err, req, res));
},
/**
* @openapi
* /api/v1/sms/africastalking/delivery-reports:
* post:
* summary: Receive delivery reports from Africa's Talking
* operationId: v1SmsAfricasTalkingDeliveryReportsPost
* description: >
* Webhook endpoint for receiving SMS delivery status reports from the Africa's Talking gateway.
* Requires a valid incoming key passed as a query parameter. See the
* [documentation](/building/messaging/gateways/africas-talking/) for more details.
* tags: [SMS]
* parameters:
* - in: query
* name: key
* required: true
* schema:
* type: string
* description: The configured incoming key for authentication.
* requestBody:
* required: true
* content:
* application/x-www-form-urlencoded:
* schema:
* type: object
* properties:
* id:
* type: string
* description: The gateway message reference.
* status:
* enum: [Sent, Submitted, Buffered, Rejected, Success, Failed]
* description: The delivery status from Africa's Talking.
* failureReason:
* type: string
* description: The reason for failure, if applicable.
* required: [id, status]
* responses:
* '200':
* description: Delivery report processing results
* content:
* application/json:
* schema:
* type: object
* additionalProperties: true
* '400':
* $ref: '#/components/responses/BadRequest'
* '403':
* $ref: '#/components/responses/Forbidden'
*/
deliveryReports: (req, res) => {
return validateKey(req)
.then(() => {
Expand Down
68 changes: 68 additions & 0 deletions api/src/controllers/bulk-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,75 @@ const interceptResponse = (requestDocs, req, res, response) => {
return bulkDocs.formatResults(requestDocs, req.body.docs, response);
};

/**
* @openapi
* tags:
* - name: Bulk
* description: Bulk document operations
*/
module.exports = {
/**
* @openapi
* /api/v1/bulk-delete:
* post:
* summary: Bulk delete documents
* operationId: v1BulkDeletePost
* description: |
* Bulk delete endpoint for deleting large numbers of documents. Docs are batched into groups
* of 100 and sent sequentially to CouchDB. The response is chunked JSON (one batch at a
* time), so to get an indication of progress incomplete JSON must be parsed with a library such as
* [`partial-json-parser`](https://github.com/indgov/partial-json-parser).
*
* ### Errors
*
* If an error is encountered part-way through the response (eg on the third batch), it’s impossible to send
* new headers to indicate a 5xx error, so the connection will simply be terminated (as recommended here
* https://github.com/expressjs/express/issues/2700).
* tags: [Bulk]
* x-permissions:
* hasAll: [can_edit]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [docs]
* properties:
* docs:
* type: array
* description: Array of objects each with an `_id` property.
* items:
* type: object
* additionalProperties: true
* required: [_id]
* properties:
* _id:
* type: string
* responses:
* '200':
* description: >
* Chunked JSON response. Each chunk is an array of results for a batch of deletions.
* content:
* application/json:
* schema:
* type: array
* items:
* type: array
* items:
* type: object
* properties:
* ok:
* type: boolean
* id:
* type: string
* rev:
* type: string
* '401':
* $ref: '#/components/responses/Unauthorized'
* '403':
* $ref: '#/components/responses/Forbidden'
*/
bulkDelete: (req, res, next) => {
return auth
.check(req, ['can_edit'])
Expand Down
97 changes: 97 additions & 0 deletions api/src/controllers/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,50 @@ const getContact = ctx.bind(Contact.v1.get);
const getContactWithLineage = ctx.bind(Contact.v1.getWithLineage);
const getContactIds = ctx.bind(Contact.v1.getUuidsPage);

/**
* @openapi
* tags:
* - name: Contact
* description: Operations for contacts (persons and places)
*/
module.exports = {
v1: {
/**
* @openapi
* /api/v1/contact/{id}:
* get:
* summary: Get a contact by id
* operationId: v1ContactIdGet
* description: >
* Returns a contact record (person or place). Optionally includes the full parent place lineage.
* tags: [Contact]
* x-since: 4.18.0
* x-permissions:
* hasAll: [can_view_contacts]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: The id of the contact to retrieve
* - $ref: '#/components/parameters/withLineage'
* responses:
* '200':
* description: The contact record
* content:
* application/json:
* schema:
* oneOf:
* - $ref: '#/components/schemas/v1.Contact'
* - $ref: '#/components/schemas/v1.ContactWithLineage'
* '401':
* $ref: '#/components/responses/Unauthorized'
* '403':
* $ref: '#/components/responses/Forbidden'
* '404':
* $ref: '#/components/responses/NotFound'
*/
get: serverUtils.doOrError(async (req, res) => {
await auth.assertPermissions(req, { isOnline: true, hasAll: ['can_view_contacts'] });
const { params: { uuid }, query: { with_lineage } } = req;
Expand All @@ -20,6 +62,61 @@ module.exports = {

return res.json(contact);
}),

/**
* @openapi
* /api/v1/contact/uuid:
* get:
* summary: Get contact UUIDs
* operationId: v1ContactUuidGet
* description: >
* Returns a paginated array of contact identifier strings matching the given filter criteria.
* At least one of `type` or `freetext` must be provided.
* tags: [Contact]
* x-since: 4.18.0
* x-permissions:
* hasAll: [can_view_contacts]
* parameters:
* - in: query
* name: type
* schema:
* type: string
* description: >
* The contact_type id for the type of contacts to fetch. Required if `freetext` is not provided
* and may be combined with `freetext`.
* - in: query
* name: freetext
* schema:
* type: string
* minLength: 3
* description: >
* A search term for filtering contacts. Must be at least 3 characters and not contain whitespace.
* Required if `type` is not provided and may be combined with `type`.
* - $ref: '#/components/parameters/cursor'
* - $ref: '#/components/parameters/limitId'
* responses:
* '200':
* description: A page of contact UUIDs
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* type: array
* description: The results for this page
* items:
* type: string
* cursor:
* $ref: '#/components/schemas/PageCursor'
* required: [data, cursor]
* '400':
* $ref: '#/components/responses/BadRequest'
* '401':
* $ref: '#/components/responses/Unauthorized'
* '403':
* $ref: '#/components/responses/Forbidden'
*/
getUuids: serverUtils.doOrError(async (req, res) => {
await auth.assertPermissions(req, { isOnline: true, hasAll: ['can_view_contacts'] });
if (!req.query.freetext && !req.query.type) {
Expand Down
Loading
Loading