From 6bf96d9174aa194e9598a084e4d3b7b2eeb8b86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noem=C3=AD=20Bl=C3=A1zquez?= Date: Sat, 21 Sep 2024 20:16:01 +0200 Subject: [PATCH] Creates python api tpl and show it in automations page --- .../protolib/src/bundles/apis/adminPages.tsx | 23 +++--- packages/protolib/src/bundles/apis/api.ts | 71 +++++++++++-------- .../src/bundles/apis/templates/python-api.tpl | 8 +++ packages/protolib/src/bundles/templates.ts | 6 ++ 4 files changed, 70 insertions(+), 38 deletions(-) create mode 100644 packages/protolib/src/bundles/apis/templates/python-api.tpl diff --git a/packages/protolib/src/bundles/apis/adminPages.tsx b/packages/protolib/src/bundles/apis/adminPages.tsx index 8f7b40af0..e1bd265ee 100644 --- a/packages/protolib/src/bundles/apis/adminPages.tsx +++ b/packages/protolib/src/bundles/apis/adminPages.tsx @@ -1,6 +1,6 @@ import { APIModel } from '.' import { YStack, Text, Stack, XStack, Accordion, Spacer, Square, ScrollView, useToastController, Spinner, Paragraph, SizableText } from "@my/ui"; -import { ToyBrick, Eye, ChevronDown, UploadCloud, CheckCircle, Package, AlertTriangle} from '@tamagui/lucide-icons' +import { ToyBrick, Eye, ChevronDown, UploadCloud, CheckCircle, Package, AlertTriangle } from '@tamagui/lucide-icons' import { z, getPendingResult, API } from 'protobase' import { usePageParams } from '../../next' import { usePrompt } from '../../context/PromptAtom' @@ -9,7 +9,7 @@ import { DataTable2 } from '../../components/DataTable2' import { DataView, DataViewActionButton } from '../../components/DataView' import { AlertDialog } from '../../components/AlertDialog' import { AdminPage } from '../../components/AdminPage' -import {useWorkspaceEnv} from '../../lib/useWorkspaceEnv' +import { useWorkspaceEnv } from '../../lib/useWorkspaceEnv' import { useEffect, useState } from 'react' import Center from '../../components/Center' import { Tinted } from '../../components/Tinted'; @@ -164,7 +164,7 @@ export default { const [publishOpen, setPublishOpen] = useState(false) const [publishState, setPublishState] = useState<"confirm" | "publishing" | "published" | "error">("confirm") const [packageId, setPackageId] = useState("") - const {messages, setMessages} = useSubscription(notificationsTopicPrefix+'#') + const { messages, setMessages } = useSubscription(notificationsTopicPrefix + '#') const [data, setData] = useState(defaultData) const [error, setError] = useState('') const toast = useToastController() @@ -187,10 +187,10 @@ export default { }, [publishOpen]) const processMessages = async (messages) => { - if(packageId) { + if (packageId) { //search messages for messages directed at packageId - const done = messages.find(m => m.topic.startsWith(notificationsTopicPrefix+packageId+'/done')) - if(done) { + const done = messages.find(m => m.topic.startsWith(notificationsTopicPrefix + packageId + '/done')) + if (done) { await API.get('/adminapi/v1/services/api/restart') setPublishState("published") setPackageId("") @@ -337,17 +337,17 @@ export default { width={"600px"} integratedChat onAccept={async () => { - if(publishState == "confirm") { + if (publishState == "confirm") { setPublishState("publishing") const result = await API.get('/adminapi/v1/services/api/package') const currentPackageId = result?.data?.packageId - if(!currentPackageId) { + if (!currentPackageId) { setPublishState("error") return true } setPackageId(currentPackageId) return true - } + } setPublishOpen(false) setPublishState("confirm") }} @@ -397,7 +397,10 @@ export default { openMode={env === 'dev' ? 'edit' : 'view'} hideAdd={env !== 'dev'} disableItemSelection={env !== 'dev'} - onSelectItem={(item) => replace('editFile', '/packages/app/bundles/custom/apis/' + item.data.name + '.ts')} + onSelectItem={(item) => { + replace('editFile', item.data.filePath); + }} + rowIcon={ToyBrick} sourceUrl={sourceUrl} initialItems={initialItems} diff --git a/packages/protolib/src/bundles/apis/api.ts b/packages/protolib/src/bundles/apis/api.ts index 01d6d7152..e88be6a19 100644 --- a/packages/protolib/src/bundles/apis/api.ts +++ b/packages/protolib/src/bundles/apis/api.ts @@ -12,18 +12,28 @@ const indexFile = (root) => APIDir(root) + "index.ts" const indexFilePath = "/packages/app/bundles/custom/apis/index.ts" const getAPI = (apiPath, req) => { - const sourceFile = getSourceFile(APIDir(getRoot(req)) + apiPath) - const arg = getDefinition(sourceFile, '"type"') - const obj = getDefinition(sourceFile, '"object"') + const apiType = apiPath.endsWith('.py') ? 'python' : 'typescript' + let type = apiType + let object = "None" + const filePath = APIDir(getRoot(req)) + apiPath + if (apiType === 'typescript') { + const sourceFile = getSourceFile(filePath) + const arg = getDefinition(sourceFile, '"type"') + const obj = getDefinition(sourceFile, '"object"') + type = arg ? arg.getText().replace(/^['"]+|['"]+$/g, '') : type + object = obj ? obj.getText().replace(/^['"]+|['"]+$/g, '') : object + } return { name: apiPath.replace(/\.[^/.]+$/, ""), //remove extension - type: arg ? arg.getText().replace(/^['"]+|['"]+$/g, '') : "Unknown", - object: obj ? obj.getText().replace(/^['"]+|['"]+$/g, '') : "None" + type, + object, + filePath: APIDir("") + apiPath } } const deleteAPI = (req, value) => { - const api = getAPI(fspath.basename(value.name) + '.ts', req) + const extension = value.template === 'python-api' ? '.py' : '.ts' + const api = getAPI(fspath.basename(value.name) + extension, req) removeFileWithImports(getRoot(req), value, '"apis"', indexFilePath, req, fs); if (api.type === "AutoAPI") { const objectPath = fspath.join(getRoot(), ObjectModel.getDefaultSchemaFilePath(api.object)) @@ -45,7 +55,7 @@ async function checkFileExists(filePath) { const getDB = (path, req, session) => { const db = { async *iterator() { - const files = (await fs.readdir(APIDir(getRoot(req)))).filter(f => f != 'index.ts' && !fsSync.lstatSync(fspath.join(APIDir(getRoot(req)), f)).isDirectory() && f.endsWith('.ts')) + const files = (await fs.readdir(APIDir(getRoot(req)))).filter(f => f != 'index.ts' && !fsSync.lstatSync(fspath.join(APIDir(getRoot(req)), f)).isDirectory() && (f.endsWith('.ts') || f.endsWith('.py'))) const apis = await Promise.all(files.map(async f => getAPI(f, req))); for (const api of apis) { @@ -65,8 +75,9 @@ const getDB = (path, req, session) => { let ObjectSourceFile const template = fspath.basename(value.template ?? 'empty') + const extension = value.template === 'python-api' ? '.py' : '.ts' - const filePath = getRoot(req) + 'packages/app/bundles/custom/apis/' + fspath.basename(value.name) + '.ts'; + const filePath = getRoot(req) + 'packages/app/bundles/custom/apis/' + fspath.basename(value.name) + extension; exists = await checkFileExists(filePath); if (template.startsWith("automatic-crud")) { @@ -80,26 +91,28 @@ const getDB = (path, req, session) => { } } - if(template == "automatic-crud-google-sheet") { - const regex = /\/d\/([a-zA-Z0-9-_]+)/; - const match = value.param.match(regex); - const id = match ? match[1] : null; - value.param = id + if (template == "automatic-crud-google-sheet") { + const regex = /\/d\/([a-zA-Z0-9-_]+)/; + const match = value.param.match(regex); + const id = match ? match[1] : null; + value.param = id } const computedName = value.name const codeName = computedName.replace(/\s/g, "") const codeNameLowerCase = codeName.toLowerCase() const result = await API.post('/adminapi/v1/templates/file?token=' + getServiceToken(), { - name: value.name + '.ts', + name: value.name + extension, data: { - options: { template: `/packages/protolib/src/bundles/apis/templates/${template}.tpl`, variables: { - codeName: codeName, - name: computedName, - codeNameLowerCase: codeNameLowerCase, - object: value.object, - param: value.param, - }}, + options: { + template: `/packages/protolib/src/bundles/apis/templates/${template}.tpl`, variables: { + codeName: codeName, + name: computedName, + codeNameLowerCase: codeNameLowerCase, + object: value.object, + param: value.param, + } + }, path: '/packages/app/bundles/custom/apis' } }) @@ -114,15 +127,17 @@ const getDB = (path, req, session) => { await addFeature(ObjectSourceFile, '"AutoAPI"', "true") } //link in index.ts - const sourceFile = getSourceFile(indexFile(getRoot(req))) - addImportToSourceFile(sourceFile, codeName + 'Api', ImportType.DEFAULT, './' + codeName) + if (extension == '.ts') { + const sourceFile = getSourceFile(indexFile(getRoot(req))) + addImportToSourceFile(sourceFile, codeName + 'Api', ImportType.DEFAULT, './' + codeName) - const arg = getDefinition(sourceFile, '"apis"') - if (!arg) { - throw "No link definition schema marker found for file: " + path + const arg = getDefinition(sourceFile, '"apis"') + if (!arg) { + throw "No link definition schema marker found for file: " + path + } + addObjectLiteralProperty(arg, codeName, codeName + 'Api') + sourceFile.saveSync(); } - addObjectLiteralProperty(arg, codeName, codeName + 'Api') - sourceFile.saveSync(); }, async get(key) { diff --git a/packages/protolib/src/bundles/apis/templates/python-api.tpl b/packages/protolib/src/bundles/apis/templates/python-api.tpl new file mode 100644 index 000000000..101f858d8 --- /dev/null +++ b/packages/protolib/src/bundles/apis/templates/python-api.tpl @@ -0,0 +1,8 @@ +from flask import Blueprint + +#blueprint with same name as the file +{{codeNameLowerCase}}_bp = Blueprint('{{codeNameLowerCase}}_bp', __name__) + +@test_bp.route("/pyapi/v1/{{codeNameLowerCase}}") +def {{codeNameLowerCase}}_run(): + return "ok" \ No newline at end of file diff --git a/packages/protolib/src/bundles/templates.ts b/packages/protolib/src/bundles/templates.ts index 0ab1bf9d4..33de634e5 100644 --- a/packages/protolib/src/bundles/templates.ts +++ b/packages/protolib/src/bundles/templates.ts @@ -29,6 +29,12 @@ export const apiTemplates = { return } }, + "python-api": { + id: "python-api", + name: "Python API", + description: 'Create python automations that react to events and perform actions (when ..., do ...)', + icon: PencilRuler + }, "automatic-crud-storage": { id: "automatic-crud-storage", name: "Object storage (custom database)",