diff --git a/lambdas/common/index.ts b/lambdas/common/index.ts deleted file mode 100644 index 31ef045..0000000 --- a/lambdas/common/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import AWS from "aws-sdk"; - -export const dynamo = new AWS.DynamoDB({ - apiVersion: "2012-08-10", - region: "us-east-1", -}); -export const s3 = new AWS.S3({ - apiVersion: "2006-03-01", - region: "us-east-1", -}); -export const headers = { - "Access-Control-Allow-Origin": "https://roamresearch.com", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE", -}; -export const ses = new AWS.SES({ - apiVersion: "2010-12-01", - region: "us-east-1", -}); - -export const toStatus = (s: string) => - process.env.NODE_ENV === "development" ? `${s} DEV` : s; -export const fromStatus = (s = "") => s.replace(/ DEV$/, ""); - -export const isInvalid = (workflow = '') => - /<%((J(A(VASCRIPT(ASYNC)?)?)?)|(ONBLOCKEXIT)|(IF(TRUE)?)):/.test(workflow); diff --git a/lambdas/smartblocks-store_delete.ts b/lambdas/smartblocks-store_delete.ts deleted file mode 100644 index b0016cd..0000000 --- a/lambdas/smartblocks-store_delete.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { APIGatewayProxyHandler } from "aws-lambda"; -import { dynamo, toStatus } from "./common"; -import { awsGetRoamJSUser } from "roamjs-components/backend/getRoamJSUser"; -import headers from "roamjs-components/backend/headers"; - -export const handler: APIGatewayProxyHandler = awsGetRoamJSUser( - async (user, { uuid, graph }: Record) => { - if (!uuid) { - return { - statusCode: 400, - body: "Argument `uuid` is required", - headers, - }; - } - if (!graph) { - return { - statusCode: 400, - body: "Argument `graph` is required", - headers, - }; - } - const expectedUserId = await dynamo - .getItem({ - TableName: "RoamJSSmartBlocks", - Key: { uuid: { S: graph } }, - }) - .promise() - .then((i) => i.Item?.token?.S); - if (expectedUserId !== user.id) { - return { - statusCode: 403, - body: "User does not have permission to remove this workflow.", - headers, - }; - } - return Promise.all([ - dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "name-status-index", - ExpressionAttributeNames: { - "#s": "status", - "#n": "name", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("INSTALLED") }, - ":n": { S: uuid }, - }, - KeyConditionExpression: "#n = :n AND #s = :s", - }) - .promise() - .then((q) => { - const uuids = (q.Items || []).map((u) => u.uuid.S); - const batches = Math.ceil(uuids.length / 25); - const requests = new Array(batches).fill(null).map((_, i, all) => - new Array(i === all.length - 1 ? uuids.length % 25 : 25) - .fill(null) - .map((_, j) => ({ - uuid: { S: uuids[i * 25 + j] }, - })) - ); - return Promise.all( - requests.map((req) => - dynamo - .batchWriteItem({ - RequestItems: { - RoamJSSmartBlocks: req.map((Key) => ({ - DeleteRequest: { Key }, - })), - }, - }) - .promise() - ) - ); - }), - dynamo - .deleteItem({ - TableName: "RoamJSSmartBlocks", - Key: { uuid: { S: uuid } }, - }) - .promise(), - ]) - .then(() => ({ - statusCode: 204, - body: "{}", - headers, - })) - .catch((e) => ({ - statusCode: 500, - body: e.message, - headers, - })); - } -); diff --git a/lambdas/smartblocks-store_get.ts b/lambdas/smartblocks-store_get.ts deleted file mode 100644 index 9c8f91c..0000000 --- a/lambdas/smartblocks-store_get.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { APIGatewayProxyHandler } from "aws-lambda"; -import { DynamoDB } from "aws-sdk"; -import nanoid from "nanoid"; -import { s3, dynamo, headers, toStatus, fromStatus, isInvalid } from "./common"; - -const getWorkflow = ({ - item, - graph, - installs = 0, -}: { - item?: DynamoDB.AttributeMap; - graph: string; - installs?: number; -}) => - s3 - .getObject({ - Bucket: "roamjs-smartblocks", - Key: `${item?.uuid.S}/${item?.workflow.S}.json`, - }) - .promise() - .then((r) => - dynamo - .putItem({ - TableName: "RoamJSSmartBlocks", - Item: { - uuid: { S: nanoid() }, - name: { S: item?.uuid.S }, - author: { S: graph }, - workflow: { S: item?.workflow.S }, - status: { S: toStatus("INSTALLED") }, - installed: { S: new Date().toJSON() }, // let's see if anyone is actually using this - }, - }) - .promise() - .then(() => - dynamo - .updateItem({ - TableName: "RoamJSSmartBlocks", - Key: { - uuid: { S: item?.uuid.S }, - }, - UpdateExpression: "SET #s = :s", - ExpressionAttributeNames: { - "#s": "score", - }, - ExpressionAttributeValues: { - ":s": { N: (installs + 1).toString() }, - }, - }) - .promise() - ) - .then(() => ({ - statusCode: 200, - body: JSON.stringify({ workflow: r.Body?.toString() }), - headers, - })) - ); - -const TAB_REGEX = new RegExp( - `^(${["Marketplace", "Installed", "Published"].join("|")})$`, - "i" -); - -export const handler: APIGatewayProxyHandler = async (event) => { - const { - uuid = "", - tab = "marketplace", - graph = "", - open, - } = event.queryStringParameters || {}; - const filterTab = TAB_REGEX.test(tab) ? tab.toLowerCase() : "marketplace"; - return uuid - ? dynamo - .getItem({ - TableName: "RoamJSSmartBlocks", - Key: { uuid: { S: uuid } }, - }) - .promise() - .then(async (r) => { - if (fromStatus(r.Item?.status?.S) !== "LIVE") { - return { - statusCode: 400, - body: `Invalid id ${uuid} doesn't represent a LIVE SmartBlock Workflow`, - headers, - }; - } - const installs = await dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "name-status-index", - ExpressionAttributeNames: { - "#s": "status", - "#n": "name", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("INSTALLED") }, - ":n": { S: uuid }, - }, - KeyConditionExpression: "#n = :n AND #s = :s", - }) - .promise() - .then((is) => is.Count); - if (!!open) { - const invalid = await s3 - .getObject({ - Bucket: "roamjs-smartblocks", - Key: `${r.Item?.uuid.S}/${r.Item?.workflow.S}.json`, - }) - .promise() - .then((d) => isInvalid(d.Body?.toString())); - if (graph === r.Item?.author.S) { - return { - statusCode: 200, - body: JSON.stringify({ - installed: true, - updatable: false, - count: installs, - invalid, - }), - headers, - }; - } - return Promise.all([ - dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "name-author-index", - ExpressionAttributeNames: { - "#s": "name", - "#a": "author", - }, - ExpressionAttributeValues: { - ":s": { S: uuid }, - ":a": { S: graph }, - }, - KeyConditionExpression: "#a = :a AND #s = :s", - }) - .promise(), - dynamo - .getItem({ - TableName: "RoamJSSmartBlocks", - Key: { uuid: { S: r.Item?.author.S } }, - }) - .promise(), - ]).then(async ([link, publisher]) => ({ - statusCode: 200, - body: JSON.stringify({ - invalid, - displayName: publisher.Item?.description?.S, - count: installs, - installed: !!link.Count, - updatable: - !!link.Count && - link.Items?.reduce( - (prev, cur) => - (cur.workflow.S || "").localeCompare( - prev.workflow.S || "" - ) > 0 - ? cur - : prev, - { workflow: { S: "0000" } } - ).workflow?.S !== r.Item?.workflow.S, - }), - headers, - })); - } - return getWorkflow({ item: r.Item, graph, installs }); - }) - .catch((e) => ({ - statusCode: 500, - body: JSON.stringify({ - success: false, - message: e.message, - }), - headers, - })) - : Promise.all([ - filterTab === "installed" - ? dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "status-author-index", - ExpressionAttributeNames: { - "#s": "status", - "#a": "author", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("INSTALLED") }, - ":a": { S: graph }, - }, - KeyConditionExpression: "#a = :a AND #s = :s", - }) - .promise() - .then((is) => { - const uuids = Array.from( - new Set((is.Items || []).map((u) => u.name.S)) - ); - const batches = Math.ceil(uuids.length / 100); - const requests = new Array(batches) - .fill(null) - .map((_, i, all) => - new Array(i === all.length - 1 ? uuids.length % 100 : 100) - .fill(null) - .map((_, j) => ({ - uuid: { S: uuids[i * 100 + j] }, - })) - ); - return Promise.all( - requests.map((Keys) => - dynamo - .batchGetItem({ - RequestItems: { RoamJSSmartBlocks: { Keys } }, - }) - .promise() - ) - ).then((all) => { - return all.flatMap( - (a) => a.Responses?.RoamJSSmartBlocks || [] - ); - }); - }) - : filterTab === "published" - ? dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "status-author-index", - ExpressionAttributeNames: { - "#s": "status", - "#a": "author", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("LIVE") }, - ":a": { S: graph }, - }, - KeyConditionExpression: "#a = :a AND #s = :s", - }) - .promise() - .then((r) => r.Items) - : dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "status-index", - ExpressionAttributeNames: { - "#s": "status", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("LIVE") }, - }, - KeyConditionExpression: "#s = :s", - }) - .promise() - .then((r) => r.Items), - dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "status-index", - ExpressionAttributeNames: { - "#s": "status", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("USER") }, - }, - KeyConditionExpression: "#s = :s", - }) - .promise() - .then((r) => r.Items), - ]) - .then(([items, users]) => ({ - statusCode: 200, - body: JSON.stringify({ - smartblocks: (items || []) - .sort((a, b) => { - const scoreDiff = - Number(b.score?.N || 0) - Number(a.score?.N || 0); - if (!scoreDiff) { - return (a.name?.S || "").localeCompare(b?.name.S || ""); - } - return Number(b.score?.N || 0) - Number(a.score?.N || 0); - }) - .map((i) => - Object.fromEntries( - Object.entries(i) - .filter(([k]) => k !== "workflow") - .map(([k, v]) => [k, v.N ? Number(v.N) : v.S || v.SS]) - ) - ), - users: (users || []).map((i) => ({ - author: i.uuid.S, - displayName: i.description?.S || "", - })), - }), - headers, - })) - .catch((e) => ({ - statusCode: 500, - body: JSON.stringify({ - success: false, - message: e.message, - }), - headers, - })); -}; diff --git a/lambdas/smartblocks-store_put.ts b/lambdas/smartblocks-store_put.ts deleted file mode 100644 index 6e5d1ef..0000000 --- a/lambdas/smartblocks-store_put.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { APIGatewayProxyHandler } from "aws-lambda"; -import nanoid from "nanoid"; -import format from "date-fns/format"; -import { awsGetRoamJSUser } from "roamjs-components/backend/getRoamJSUser"; -import headers from "roamjs-components/backend/headers"; -import emailCatch from "roamjs-components/backend/emailCatch"; -import { dynamo, s3, fromStatus, toStatus, isInvalid } from "./common"; - -export const handler: APIGatewayProxyHandler = awsGetRoamJSUser( - async (user, event) => { - const { - name, - tags = [], - img = "", - author, - description = "", - workflow, - uuid = nanoid(), - price: priceArg = "0", - displayName = "", - } = event as { - img?: string; - name: string; - tags?: string[]; - author: string; - description?: string; - workflow: string; - uuid?: string; - price?: string; - displayName?: string; - }; - const price = Number(priceArg) || 0; - if (price > 0) { - return { - statusCode: 400, - body: `We no longer support premium SmartBlock workflows. Please remove the price block and try again`, - headers, - }; - } - - if (isInvalid(workflow)) { - return { - statusCode: 400, - headers, - body: "Your workflow was rejected from being published to the SmartBlocks Store, because it contains some illegal commands. Please remove these commands and try again.", - }; - } - if (/\n/.test(name)) { - return { - statusCode: 400, - headers, - body: "Your workflow name has invalid characters. Please remove and try again.", - }; - } - if (name.length === 0) { - return { - statusCode: 400, - headers, - body: "Your workflow name is empty. Please add a name!", - }; - } - if (name.length > 64) { - return { - statusCode: 400, - headers, - body: `Your workflow name is ${name.length} characters long. The maximum length is 64.`, - }; - } - return dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "name-index", - ExpressionAttributeNames: { - "#n": "name", - }, - ExpressionAttributeValues: { - ":n": { S: name }, - }, - KeyConditionExpression: "#n = :n", - }) - .promise() - .then(async (r) => { - const existingWorkflow = (r.Items || []).find( - (i) => fromStatus(i.status.S) !== "USER" - ); - if (existingWorkflow && existingWorkflow?.uuid?.S !== uuid) { - return { - statusCode: 400, - body: `Workflow with name ${name} already exists in the store`, - headers, - }; - } - const version = format(new Date(), "yyyy-MM-dd-hh-mm-ss"); - const authorUuid = existingWorkflow?.author?.S || author; - const existingAuthor = await dynamo - .getItem({ - TableName: "RoamJSSmartBlocks", - Key: { uuid: { S: authorUuid } }, - }) - .promise() - .then((a) => a.Item); - const limit = Number(existingAuthor?.limit?.N) || 5; - const putItem = (existingDisplayName = "") => - dynamo - .query({ - TableName: "RoamJSSmartBlocks", - IndexName: "status-author-index", - ExpressionAttributeNames: { - "#s": "status", - "#a": "author", - }, - ExpressionAttributeValues: { - ":s": { S: toStatus("LIVE") }, - ":a": { S: author }, - }, - KeyConditionExpression: "#a = :a AND #s = :s", - }) - .promise() - .then((qr) => { - return (qr.Count || 0) >= limit - ? { - statusCode: 401, - body: `Not allowed to publish more than ${limit} workflows. Reach out to support@roamjs.com about increasing your limit.`, - headers, - } - : s3 - .upload({ - Bucket: "roamjs-smartblocks", - Body: workflow, - Key: `${uuid}/${version}.json`, - ContentType: "application/json", - }) - .promise() - .then(() => - displayName && displayName !== existingDisplayName - ? dynamo - .updateItem({ - TableName: "RoamJSSmartBlocks", - Key: { - uuid: { S: author }, - }, - UpdateExpression: "SET #d = :d", - ExpressionAttributeNames: { - "#d": "description", - }, - ExpressionAttributeValues: { - ":d": { S: displayName }, - }, - }) - .promise() - .then(() => Promise.resolve()) - : Promise.resolve() - ) - .then(() => - dynamo - .putItem({ - TableName: "RoamJSSmartBlocks", - Item: { - uuid: { S: uuid }, - name: { S: name }, - tags: { SS: tags?.length ? tags : ["Smartblock"] }, - img: { - S: img - .replace(/^!\[.*?\]\(/, "") - .replace(/\)$/, ""), - }, - author: { S: author }, - description: { S: description }, - workflow: { S: version }, - status: { S: toStatus("LIVE") }, - score: { N: existingWorkflow?.score?.N || "0" }, - }, - }) - .promise() - ) - .then(() => ({ - statusCode: 200, - body: JSON.stringify({ - uuid, - }), - headers, - })); - }); - if ( - !existingAuthor?.token?.S || - !existingAuthor?.token.S.startsWith("user_") - ) { - return dynamo - .updateItem({ - TableName: "RoamJSSmartBlocks", - Key: { - uuid: { S: authorUuid }, - }, - UpdateExpression: "SET #t = :t", - ExpressionAttributeNames: { - "#t": "token", - }, - ExpressionAttributeValues: { - ":t": { S: user.id }, - }, - }) - .promise() - .then(() => putItem(existingAuthor?.description?.S)); - } else if (existingAuthor?.token.S === user.id) { - return putItem(existingAuthor?.description?.S); - } - return { - statusCode: 403, - body: "User is not allowed to publish workflow", - headers, - }; - }) - .catch(emailCatch(`Failed to publish workflow: ${name}`)); - } -); diff --git a/main.tf b/main.tf deleted file mode 100644 index 8e28e72..0000000 --- a/main.tf +++ /dev/null @@ -1,178 +0,0 @@ -terraform { - backend "remote" { - hostname = "app.terraform.io" - organization = "VargasArts" - workspaces { - prefix = "roamjs-smartblocks" - } - } - required_providers { - github = { - source = "integrations/github" - version = "4.2.0" - } - } -} - -variable "aws_access_token" { - type = string -} - -variable "aws_secret_token" { - type = string -} - -variable "developer_token" { - type = string -} - -variable "github_token" { - type = string -} - -provider "aws" { - region = "us-east-1" - access_key = var.aws_access_token - secret_key = var.aws_secret_token -} - -provider "github" { - owner = "dvargas92495" - token = var.github_token -} - -module "roamjs_lambda" { - source = "dvargas92495/lambda/roamjs" - providers = { - aws = aws - github = github - } - - name = "smartblocks" - lambdas = [ - { - path = "smartblocks-store", - method = "get" - }, - { - path = "smartblocks-store", - method = "put" - }, - { - path = "smartblocks-store", - method = "delete" - }, - ] - aws_access_token = var.aws_access_token - aws_secret_token = var.aws_secret_token - github_token = var.github_token - developer_token = var.developer_token -} - -resource "aws_dynamodb_table" "store" { - name = "RoamJSSmartBlocks" - billing_mode = "PAY_PER_REQUEST" - hash_key = "uuid" - - attribute { - name = "uuid" - type = "S" - } - - attribute { - name = "status" - type = "S" - } - - attribute { - name = "name" - type = "S" - } - - attribute { - name = "author" - type = "S" - } - - global_secondary_index { - hash_key = "status" - name = "status-index" - non_key_attributes = [] - projection_type = "ALL" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - hash_key = "name" - name = "name-index" - non_key_attributes = [] - projection_type = "ALL" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - hash_key = "author" - name = "status-author-index" - non_key_attributes = [] - projection_type = "ALL" - range_key = "status" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - hash_key = "name" - name = "name-author-index" - non_key_attributes = [] - projection_type = "ALL" - range_key = "author" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - hash_key = "status" - name = "name-status-index" - non_key_attributes = [] - projection_type = "ALL" - range_key = "name" - read_capacity = 0 - write_capacity = 0 - } - - tags = { - Application = "Roam JS Extensions" - } -} - -data "aws_iam_role" "lambda_execution" { - name = "roam-js-extensions-lambda-execution" -} - -data "aws_iam_policy_document" "bucket_policy" { - statement { - actions = [ - "s3:GetObject", - "s3:PutObject", - ] - - resources = [ - "arn:aws:s3:::roamjs-smartblocks/*", - ] - - principals { - type = "AWS" - identifiers = [data.aws_iam_role.lambda_execution.arn] - } - } -} - -resource "aws_s3_bucket" "main" { - bucket = "roamjs-smartblocks" - policy = data.aws_iam_policy_document.bucket_policy.json - tags = { - Application = "Roam JS Extensions" - } -} diff --git a/package.json b/package.json index 8de9abd..c643bb2 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,5 @@ }, "samepage": { "extends": "node_modules/roamjs-components/package.json" - }, - "padawan": { - "webhookUrl": "https://samepage.ngrok.io/padawan" } } diff --git a/src/BulkTrigger.tsx b/src/BulkTrigger.tsx index 334e049..6bb034b 100644 --- a/src/BulkTrigger.tsx +++ b/src/BulkTrigger.tsx @@ -18,7 +18,7 @@ import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTit import createOverlayRender from "roamjs-components/util/createOverlayRender"; import PageInput from "roamjs-components/components/PageInput"; import useArrowKeyDown from "roamjs-components/hooks/useArrowKeyDown"; -import { getVisibleCustomWorkflows, sbBomb } from "./core"; +import { getVisibleCustomWorkflows, sbBomb } from "./utils/core"; import fuzzy from "fuzzy"; import AutocompleteInput from "roamjs-components/components/AutocompleteInput"; diff --git a/src/HotKeyPanel.tsx b/src/HotKeyPanel.tsx index 8b4b769..3d69f10 100644 --- a/src/HotKeyPanel.tsx +++ b/src/HotKeyPanel.tsx @@ -1,7 +1,7 @@ import { Button, InputGroup, Intent, Label } from "@blueprintjs/core"; import React, { useMemo, useState, useRef, useEffect } from "react"; import MenuItemSelect from "roamjs-components/components/MenuItemSelect"; -import { getCleanCustomWorkflows } from "./core"; +import { getCleanCustomWorkflows } from "./utils/core"; import type { OnloadArgs } from "roamjs-components/types/native"; const HotKeyEntry = ({ @@ -112,7 +112,8 @@ const HotKeyPanel = (extensionAPI: OnloadArgs["extensionAPI"]) => () => { [] ); const [keys, setKeys] = useState( - () => extensionAPI.settings.get("hot-keys") as Record || {} + () => + (extensionAPI.settings.get("hot-keys") as Record) || {} ); return (
[0]; diff --git a/src/dom.ts b/src/utils/dom.ts similarity index 100% rename from src/dom.ts rename to src/utils/dom.ts diff --git a/src/utils/scheduleNextDailyRun.ts b/src/utils/scheduleNextDailyRun.ts index 14abf67..3bae701 100644 --- a/src/utils/scheduleNextDailyRun.ts +++ b/src/utils/scheduleNextDailyRun.ts @@ -11,7 +11,7 @@ import isBefore from "date-fns/isBefore"; import dateFnsFormat from "date-fns/format"; import renderToast from "roamjs-components/components/Toast"; import parseRoamDateUid from "roamjs-components/date/parseRoamDateUid"; -import { getCleanCustomWorkflows, sbBomb } from "../core"; +import { getCleanCustomWorkflows, sbBomb } from "./core"; import isLiveBlock from "roamjs-components/queries/isLiveBlock"; import createPage from "roamjs-components/writes/createPage"; import localStorageGet from "roamjs-components/util/localStorageGet";