Skip to content
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

Add cht-datasource APIs for creation and update of contacts and reports #9835

Open
6 tasks
sugat009 opened this issue Mar 12, 2025 · 3 comments
Open
6 tasks
Labels
Type: Feature Add something new

Comments

@sugat009
Copy link
Member

sugat009 commented Mar 12, 2025

Description
We have an internal package called cht-datasource in shared-libs/cht-datasource used to query and fetch contacts(person or place) and reports from the database. There are APIs for fetching the list of contacts and reports UUIDs and fetching the detail of the contacts and reports using that UUID. We need to add APIs to create and update contacts and reports. Documentation

Goals

  • To have API that can create people.
  • To have API that can update people.
  • To have API that can create places.
  • To have API that can update places.
  • To have API that can create reports.
  • To have API that can update reports.

Expected Outcome
The library should be able to be used in the following ways.

  1. Person
    1. From code
      const {getLocalDataContext, getRemoteDataContext, Person} = require('@medic/cht-datasource');
      const dataContext = isOnlineOnly ? getRemoteDataContext(...)  : getLocalDataContext(...);
      const createPerson = Person.v1.create(dataContext);  // .update() for update functionality
      await createPerson({
        type: <valid_person_type>,
        name: <string_name_value>,
        reported_date: <valid_date_value>, // optional value, should be set to current time if not provided
        _id: <uuid>, // optional value for create, should be generated if not provided
        _rev: <uuid-ish> // required for update, not allowed for create
        ...
      });
    2. REST API
      1. Request type
        For Create:
        HTTP POST /api/v1/person
        
        For Update:
        HTTP PUT /api/v1/person/:uuid
        
      2. body
        {
          "type": "<valid_person_type>",
          "name": "<string_name_value>",
          "reported_date": "<valid_date_value>", // optional value, should be set to current time if not provided
          "_id": "<uuid>", // optional value for create. should be generated if not provided
          "_rev": "<uuid-ish>" // required for update, not allowed for create
          ...
        }
  2. Place
    1. From code
      const {getLocalDataContext, getRemoteDataContext, Place} = require('@medic/cht-datasource');
      const dataContext = isOnlineOnly ? getRemoteDataContext(...)  : getLocalDataContext(...);
      const createPlace = Place.v1.create(dataContext);  // .update() for update functionality
      await createPlace({
        type: <valid_place_type>,
        name: <string_name_value>,
        reported_date: <valid_date_value>, // optional value, should be set to current time if not provided
        _id: <uuid>, // optional value for create, should be generated if not provided
        _rev: <uuid-ish> // required for update, not allowed for create
        ...
      });
    2. REST API
      1. Request type
        For Create:
        HTTP POST /api/v1/place
        
        For Update:
        HTTP PUT /api/v1/place/:uuid
        
      2. body
        {
          "type": "<valid_place_type>",
          "name": "<string_name_value>",
          "reported_date": "<valid_date_value>", // optional value, should be set to current time if not provided
          "_id": "<uuid>", // optional value for create. should be generated if not provided
          "_rev": "<uuid-ish>" // required for update, not allowed for create
          ...
        }
  3. Report
    1. From code
      const {getLocalDataContext, getRemoteDataContext, Report} = require('@medic/cht-datasource');
      const dataContext = isOnlineOnly ? getRemoteDataContext(...)  : getLocalDataContext(...);
      const createReport = Report.v1.create(dataContext); // update() for update functionality
      await createReport({
        type: 'data_record',
        reported_date: <valid_date_value>,
        form: <form_name>,
        contact: {
          _id: <uuid>,
          ...
        }
        ...
      });
    2. REST API
      1. Request type
        For Create:
        HTTP POST /api/v1/report
        
        For Update:
        HTTP PATCH /api/v1/report/:uuid
        
      2. body
        {
          "type": "data_record",
          "reported_date": "<valid_date_value>",
          "form": "<valid_form_name>",
          "contact": <contact_object>
          ...
        }

Only the required fields have been listed out. A valid type for the contact can be found in the database in the settings doc under the contact_types key, whereas reported_date should be of format 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or <unix epoch>.

@sugat009 sugat009 added the Type: Feature Add something new label Mar 12, 2025
@jkuester
Copy link
Contributor

I like the direction we are going with this design! Couple of thoughts here:

  1. We had to support and API for reading generic contacts because there are certain application contexts where you have a contact _id value, but do not know if it is a person or place (so you need some way to get the data to check). However, I do not think we should support creating/updating via a generic contacts API. Instead these should be supported via the person and place APIs.
  2. You are currently modeling updates as a PATCH, which is definitely valid from an HTTP method perspective. However, CouchDB only supports updates via PUT. (The difference being, with the PUT you have to pass the whole version of the new doc whereas with the PATCH, you only pass the changes.) I am concerned that going with PATCH would add extra complexity to our API code (which would need to handle translating the PATCH changes to something Couch/Pouch can deal with). Additionally, the PATCH approach would be a pretty significant break in the current data model pattern in cht-core. It could make it more complex to consume the cht-datasource API in different parts of the code-base. I have happy to continue this discussion if you see particular value in going with PATCH, but otherwise I think I would lean towards supporting updates via PUT.
  3. You don't explicitly mention it, but one interesting detail to consider is the parameter type that the create/update functions will accept in the cht-datasource code. For example, for a person, the raw JSON data we want to end up sending to Couch should conform to the Person interface (where all the lineage is "de-hydrated"). So, it makes sense to accept that. However, when actually doing things with person data and using the cht-datasource apis, it can be pretty easy to end up with PersonWithLineage objects that contain "hydrated" lineage. A good example of this is when you go to write a new person, you probably already have a parent object containing the doc for the parent place. Then you want to create a new object for the new person with the parent doc set as the parent field on the new person doc. What you have done is just created a person with "hydrated" lineage (since the parent field contains more than just the _id and parent fields). We could lock down our create/update APIs so that they only accept de-hydrated data, but that will just force the consuming code to handle the de-hydration process. I think it would probably be work accepting either hydrated or de-hydrated data in our cht-datasource create/update APIs and then the cht-datasource code can handle properly de-hydrating before writing to Couch.

Validation rules

One of the most valuable things these new APIs can provide is consistent enforcement of some rules. The tricky part is going to be making the rules strict enough to be useful, but lax enough to not break everything. I think we can probably follow the pattern from our read APIs where we only enforce the rules necessary for basic functionality in v1 of these APIs. Then, in v2 we can take a more opinionated stance of how the data should look.

All persons/places/reports

_id

Allowed but not required for creates. If it is not provided, we will add. Required for updates (if we go with the PUT updates I have suggested above).

_rev

Not allowed for creates. Required for updates (if we go with the PUT updates I have suggested above).

type

Required. For contacts, we should validate this with the contact_type (if contact_type is set, then type should be "contact").

reported_date

If it is not provided, we should set to the current date.

Just persons/places

contact_type

Required if type is "contact". Not allowed if type is not "contact".

name

Required.

Just reports

form

Required.

@sugat009
Copy link
Member Author

Agreed to all. Thanks @jkuester . I'll update the description accordingly.

@jkuester
Copy link
Contributor

Before anyone actually starts working on this issue, @sugat009, I do think it would be helpful to capture the precise API function signatures and TSDoc here in the issue for the new code that we want to add to cht-datasource (similar to what I tried to do in #9586). You are probably a better judge of it than me 😅 , but I felt like that helped streamline the development process by clarifying as much as possible up front. You have already got a great start going in the above description, but the precise types/docs will be helpful too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Feature Add something new
Projects
None yet
Development

No branches or pull requests

2 participants