Skip to content

Starting intake form backend#56

Open
leahjlou wants to merge 6 commits intomainfrom
intake-form-backend
Open

Starting intake form backend#56
leahjlou wants to merge 6 commits intomainfrom
intake-form-backend

Conversation

@leahjlou
Copy link
Contributor

@leahjlou leahjlou commented Nov 9, 2021

Models for intake form stuff, and GET api endpoints

The naming of some of the fields in the response could use some more work (i.e. couldn't figure out how to get sectionChildren and pageChildren to just both be named children) but this has gotten big enough and I think it's a good start

Some SQL to seed your DB to test this. This will create an intake form with three fields in it, two of them within a section

insert into "Nouns"("tableName", "slug", "friendlyName", "createdAt", "updatedAt") values ('clients', 'clients', 'Clients', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "Fields"("nounId", "type", "columnName", "friendlyName", "createdAt", "updatedAt") values (1, 'text', 'firstName', 'First Name', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "Fields"("nounId", "type", "columnName", "friendlyName", "createdAt", "updatedAt") values (1, 'text', 'lastName', 'Last Name', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "Fields"("nounId", "type", "columnName", "friendlyName", "createdAt", "updatedAt") values (1, 'text', 'email', 'Email', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "IntakeForms"("title", "nounId", "createdAt", "updatedAt") values ('Client Intake Form', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "IntakeFormQuestions"("label", "placeholderText", "createdAt", "updatedAt") values ('First Name', 'Jane', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "IntakeFormQuestions"("label", "placeholderText", "createdAt", "updatedAt") values ('Last Name', 'Doe', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "IntakeFormQuestions"("label", "placeholderText", "createdAt", "updatedAt") values ('Email', 'example@website.com', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);

insert into "IntakeFormItems"("type", "createdAt", "updatedAt", "intakeFormId", "orderIndex") values ('Page', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 0);

insert into "IntakeFormItems"("type", "createdAt", "updatedAt", "intakeFormId", "orderIndex", "pageId") values ('Section', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 0, 1);

insert into "IntakeFormItems"("type", "createdAt", "updatedAt", "intakeFormId", "orderIndex", "pageId", "sectionId", "fieldId", "intakeFormQuestionId") values ('Field', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 0, 1, 2, 1, 1);

insert into "IntakeFormItems"("type", "createdAt", "updatedAt", "intakeFormId", "orderIndex", "pageId", "sectionId", "fieldId", "intakeFormQuestionId") values ('Field', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 1, 1, 2, 2, 2);

insert into "IntakeFormItems"("type", "createdAt", "updatedAt", "intakeFormId", "orderIndex", "pageId", "fieldId", "intakeFormQuestionId") values ('Field', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 2, 1, 3, 3);

Here is the API response for GET /api/intake-forms/{id}

[
  {
    "id": 1,
    "type": "Page",
    "intakeFormId": 1,
    "fieldId": null,
    "intakeFormQuestionId": null,
    "pageId": null,
    "sectionId": null,
    "orderIndex": 0,
    "createdAt": "2021-11-09T01:10:12.441Z",
    "updatedAt": "2021-11-09T01:10:12.441Z",
    "pageChildren": [
      {
        "id": 2,
        "type": "Section",
        "intakeFormId": 1,
        "fieldId": null,
        "intakeFormQuestionId": null,
        "pageId": 1,
        "sectionId": null,
        "orderIndex": 0,
        "createdAt": "2021-11-09T01:10:12.444Z",
        "updatedAt": "2021-11-09T01:10:12.444Z",
        "sectionChildren": [
          {
            "id": 3,
            "type": "Field",
            "intakeFormId": 1,
            "fieldId": 1,
            "intakeFormQuestionId": 1,
            "pageId": 1,
            "sectionId": 2,
            "orderIndex": 0,
            "createdAt": "2021-11-09T01:10:12.445Z",
            "updatedAt": "2021-11-09T01:10:12.445Z",
            "IntakeFormQuestion": {
              "id": 1,
              "label": "First Name",
              "placeholderText": "Jane",
              "required": null,
              "createdAt": "2021-11-09T01:10:12.350Z",
              "updatedAt": "2021-11-09T01:10:12.350Z"
            },
            "Field": {
              "id": 1,
              "nounId": 1,
              "type": "text",
              "columnName": "firstName",
              "friendlyName": "First Name",
              "activeStatus": null,
              "createdAt": "2021-11-09T01:10:12.344Z",
              "updatedAt": "2021-11-09T01:10:12.344Z"
            }
          },
          {
            "id": 4,
            "type": "Field",
            "intakeFormId": 1,
            "fieldId": 2,
            "intakeFormQuestionId": 2,
            "pageId": 1,
            "sectionId": 2,
            "orderIndex": 1,
            "createdAt": "2021-11-09T01:10:13.072Z",
            "updatedAt": "2021-11-09T01:10:13.072Z",
            "IntakeFormQuestion": {
              "id": 2,
              "label": "Last Name",
              "placeholderText": "Doe",
              "required": null,
              "createdAt": "2021-11-09T01:10:12.352Z",
              "updatedAt": "2021-11-09T01:10:12.352Z"
            },
            "Field": {
              "id": 2,
              "nounId": 1,
              "type": "text",
              "columnName": "lastName",
              "friendlyName": "Last Name",
              "activeStatus": null,
              "createdAt": "2021-11-09T01:10:12.346Z",
              "updatedAt": "2021-11-09T01:10:12.346Z"
            }
          }
        ],
        "IntakeFormQuestion": null,
        "Field": null
      },
      {
        "id": 5,
        "type": "Field",
        "intakeFormId": 1,
        "fieldId": 3,
        "intakeFormQuestionId": 3,
        "pageId": 1,
        "sectionId": null,
        "orderIndex": 2,
        "createdAt": "2021-11-09T01:39:04.053Z",
        "updatedAt": "2021-11-09T01:39:04.053Z",
        "sectionChildren": [],
        "IntakeFormQuestion": {
          "id": 3,
          "label": "Email",
          "placeholderText": "example@website.com",
          "required": null,
          "createdAt": "2021-11-09T01:38:58.927Z",
          "updatedAt": "2021-11-09T01:38:58.927Z"
        },
        "Field": {
          "id": 3,
          "nounId": 1,
          "type": "text",
          "columnName": "email",
          "friendlyName": "Email",
          "activeStatus": null,
          "createdAt": "2021-11-09T01:38:50.045Z",
          "updatedAt": "2021-11-09T01:38:50.045Z"
        }
      }
    ]
  }
]

"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("IntakeForms", {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we'll need a way to distinguish between internal intake forms and external intake forms, or other kinds of intake forms when there are multiple for the same noun. Not sure if we need to do anything for that now, or not.

Comment on lines +32 to +34
fieldId: {
type: Sequelize.INTEGER,
references: {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about sequelize nullability, but this should be nullable in the database since not all intake items are fields. Is it nullable?

key: "id",
},
},
textContent: {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is textContent for? Which kinds of intake items use it?

key: "id",
},
},
sectionId: {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So sections are not stored in the database, they are just an integer on the intake items? How is nesting represented?

router.get("/api/intake-forms", async (req, res) => {
const intakeForms = await sequelize.models.IntakeForm.findAll();
res.send({
intakeForms,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly am not a huge fan of how sequelize includes capital case object keys in JSON. Also, I might have named pageChildren to be intakeItems so that other intake items that contain children intake items (like sections) have the same key as pages for their children. Additionally, it seems unnecessary to me for each intake item to send intakeFormId since the requester already asked only for intake items for that intake form.

I'm not sure how much of that could be customized in the sequelize up migrations, or if addressing it would require implementing a transformer to convert the internal sequelize representation to an external json api representation. I don't want to cause you a ton of work needlessly by insisting on having such a transformation layer - what are your thoughts?

Comment on lines +38 to +39
// Only include sections or items with no sections
[s.Op.or]: [{ sectionId: null }, { type: "section" }],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the idea here that we're omitting intake items with type: "page" since pages can't contain pages? Or what are we trying to omit? I'm not sure I fully understand the purpose of this.

order: ["orderIndex"],
});

res.send(intakeFormItems);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the http response header content-type set to application/json for this endpoint? I know that sometimes res.send() assumes content-type: text/html but that res.json() always sets the json content type. So calling res.json() might be safer, although perhaps express is configured in such a way where that doesn't matter in this project, I'm not sure.

Suggested change
res.send(intakeFormItems);
res.json(intakeFormItems);


router.get("/api/intake-forms", async (req, res) => {
const intakeForms = await sequelize.models.IntakeForm.findAll();
res.send({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here about maybe switching to res.json()

import "./Fields/BatchPostFields.js";
import "./Fields/GetNounFields.js";
import "./IntakeForms/GetIntakeForms";
import "./IntakeForms/PutIntakeForms";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little unusual to include an empty file in a PR - maybe move it to another PR? Or is this PR incomplete and will include the Put endpoint, too?

export interface IntakePageItem {
type: IntakeItemType.Page;
id: number;
intakeItems: IntakeItem[];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not match the api response json that has pageChildren rather than intakeItems

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants