Export data to Notion

/**
 * Questmate Custom Completion Item - Add Notion Database Record
 *
 * Permissions:
 * @UseApp {NOTION}
 *
 * Changelog:
 * v0.1 Initial release
 * v0.2 Added ability to map submitter's name to Notion record
 *
 * eslint-disable
 */

enableDebugMode();

const questDataToPropertyTransformerMap = [
  {
    outputTypes: ["rich_text"],
    transformer: async ({ propertyType, value }) => {
      return { rich_text: [{ text: { content: await value.toString() } }] };
    },
  },
  {
    outputTypes: ["title"],
    transformer: async ({ propertyType, value }) => {
      return { title: [{ text: { content: await value.toString() } }] };
    },
  },
];

return defineCustomItem((Questmate) => {
  async function getSelectedDatabase(selectedDatabaseId) {
    const databases = await databasesDataSource.retrieve();
    return databases.find(
      (database) => !!selectedDatabaseId && database.id === selectedDatabaseId
    );
  }

  Questmate.registerItemRunHandler(async ({ useConfigData, useQuest }) => {
    const [selectedDatabaseId] = useConfigData("databaseId");
    const [propertyToQuestDataIdentifierMap] = useConfigData(
      "propertyToQuestDataIdentifierMap"
    );

    const quest = await useQuest();
    const questRun = await quest.getRun();

    const newRecordData = {};

    const database = await getSelectedDatabase(selectedDatabaseId);

    for (const propertyId of Object.keys(propertyToQuestDataIdentifierMap)) {
      const value = questRun.get(propertyToQuestDataIdentifierMap[propertyId]);
      const notionPropertyType = Object.values(database.properties).find(
        (property) => property.id === propertyId
      ).type;

      const transformerConfig = questDataToPropertyTransformerMap.find(
        (transformerConfig) =>
          transformerConfig.outputTypes.includes(notionPropertyType)
      );

      if (!transformerConfig) {
        continue;
      }

      newRecordData[propertyId] = await transformerConfig.transformer({
        value,
        propertyType: notionPropertyType,
      });
    }

    console.log("NEW RECORD DATA", JSON.stringify(newRecordData));

    const insertRecordRequest = await fetch(`https://api.notion.com/v1/pages`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Notion-Version": "2022-06-28",
      },
      body: JSON.stringify({
        parent: { database_id: selectedDatabaseId },
        properties: newRecordData,
      }),
    });

    const insertRecordResponseData = await insertRecordRequest.json();
    console.log(JSON.stringify(insertRecordResponseData));
  });

  Questmate.registerView("ITEM_CONFIG_VIEW", async ({ useConfigData }) => {
    const viewComponents = [];
    const [selectedDatabaseId, setSelectedDatabaseId] = useConfigData(
      "databaseId",
      null
    );

    const [
      propertyToQuestDataIdentifierMap,
      setPropertyToQuestDataIdentifierMap,
    ] = useConfigData("propertyToQuestDataIdentifierMap", {});

    viewComponents.push({
      id: "base",
      title: "Database",
      type: "dropdown",
      optionNoun: "Database",
      optionPluralNoun: "Databases",
      value: selectedDatabaseId,
      getOptions: async () =>
        (await databasesDataSource.retrieve()).map((base) => ({
          label: base.title[0].plain_text,
          value: base.id,
        })),
      onSelect: async (newDatabaseId) => {
        const changed = newDatabaseId !== selectedDatabaseId;
        if (changed) {
          const isValidDatabase =
            newDatabaseId &&
            (await databasesDataSource.retrieve()).some(
              (base) => base.id === newDatabaseId
            );

          setSelectedDatabaseId(isValidDatabase ? newDatabaseId : null);
        }
      },
    });

    if (selectedDatabaseId !== null) {
      const database = await getSelectedDatabase(selectedDatabaseId);
      const properties = database.properties;

      viewComponents.push({
        id: `TextBlock1`,
        type: "text",
        content: "Choose which Quest data to map to each property below.",
      });
      const supportedNotionPageTypes = questDataToPropertyTransformerMap
        .flatMap((transformer) => transformer.outputTypes)
        .filter((value, index, self) => self.indexOf(value) === index);
      Object.entries(properties).forEach(([, property]) => {
        if (!supportedNotionPageTypes.includes(property.type)) {
          viewComponents.push({
            id: property.id,
            optionNoun: "Compatible Quest Data",
            optionPluralNoun: "Compatible Quest Data",
            title: property.name,
            type: "dropdown",
            onSelect: () => {},
            value: null,
            getOptions: () => [],
          });
        } else {
          viewComponents.push({
            id: property.id,
            title: property.name,
            type: "QuestDataPicker",
            value: propertyToQuestDataIdentifierMap[property.id],
            onSelect: (questDataIdentifier) => {
              if (
                JSON.stringify(questDataIdentifier) !==
                JSON.stringify(propertyToQuestDataIdentifierMap[property.id])
              ) {
                setPropertyToQuestDataIdentifierMap({
                  ...propertyToQuestDataIdentifierMap,
                  [property.id]: questDataIdentifier,
                });
              }
            },
          });
        }
      });
    }

    return {
      components: viewComponents,
    };
  });

  const databasesDataSource = Questmate.registerDataSource({
    id: "databases",
    initialData: [],
    refreshInterval: 60,
    aggregator: ({ results, staleResults }) => {
      // combine stale pages with fresh pages to provide most up-to-date information
      return (
        [...results, ...staleResults]
          .flatMap((result) => result.data)
          // filter out duplicate row data
          .filter(
            (row, index, self) =>
              self.findIndex(({ id }) => id === row.id) === index
          )
      );
    },
    fetcher:
      ({ nextPageMarker }) =>
      async () => {
        const databasesData = await fetch("https://api.notion.com/v1/search", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Notion-Version": "2022-06-28",
          },
          body: JSON.stringify({
            filter: {
              value: "database",
              property: "object",
            },
            sort: {
              direction: "ascending",
              timestamp: "last_edited_time",
            },
            page_size: 100,
            ...(nextPageMarker && { start_cursor: nextPageMarker }),
          }),
        });
        const databases = await databasesData.json();
        if (!databases.results) {
          return {
            error: {
              message: "Failed to retrieve databases",
              details: { response: databasesData },
            },
          };
        }

        return {
          data: databases.results,
          nextPageMarker: databases.has_more && databases.next_cursor,
        };
      },
  });
});