From 2e6e2c431c099d168ec855de712fb86af6de2af7 Mon Sep 17 00:00:00 2001 From: TheHadiAhmadi Date: Fri, 6 Sep 2024 20:32:07 +0430 Subject: [PATCH] add functions --- functions/test.js | 22 +++++++++++++--------- src/app.js | 15 +++++++++++---- src/handlers/ai/createModule.js | 3 +++ src/handlers/ai/updateModule.js | 3 +++ src/handlers/module.js | 14 +++++++------- src/middlewares/context.js | 4 +++- src/pages/collections.js | 20 +++++++++++++++----- src/pages/dataTable.js | 1 + src/pages/fields.js | 1 + 9 files changed, 57 insertions(+), 26 deletions(-) diff --git a/functions/test.js b/functions/test.js index 267de75..2852d52 100644 --- a/functions/test.js +++ b/functions/test.js @@ -1,15 +1,19 @@ +import { db } from "#services" + export default { name: 'Test function', slug: 'test_function', - mode: 'action', - props: [ - { - slug: 'collection', - type: 'collection', - name: 'Collection' - } - ], - action(req, context) { + // props: [ + // { + // slug: 'collection', + // type: 'collection', + // name: 'Collection' + // } + // ], + action(body, context) { + + console.log(body, context) // + return {} } } \ No newline at end of file diff --git a/src/app.js b/src/app.js index cb04c95..046b63e 100644 --- a/src/app.js +++ b/src/app.js @@ -27,7 +27,7 @@ export function createApp({functions, db}) { app.use(express.static(path.join(__dirname, '../public'))) app.use(express.static('./public')) - app.use('/', contextMiddleware()) + app.use('/', contextMiddleware({functions})) // File upload app.use('/files', express.static('./uploads')) @@ -91,9 +91,16 @@ export function createApp({functions, db}) { //#region Functions for (let key in functions) { - // app.post('/api/ + key, (req, res) => { - // call function with req.context, req.body, ... - // }) + if(functions[key].action) { + + app.post('/api/fn/' + key, async (req, res) => { + // call function with req.context, req.body, ... + + const resp = await functions[key].action(req.body, req.context) + + return res.json(resp) + }) + } } // OR? diff --git a/src/handlers/ai/createModule.js b/src/handlers/ai/createModule.js index 5365d84..338fe26 100644 --- a/src/handlers/ai/createModule.js +++ b/src/handlers/ai/createModule.js @@ -35,6 +35,9 @@ function generateCreateModuleSystemPrompt({ collections, name }) { type CheckboxProp = { "type": "checkbox", "slug": "string", "label": "string" } type SelectProp = { "type": "select", "slug": "string", "label": "string", "multiple": "boolean", "items": "string[]" } type RelationProp = { "type": "relation", "slug": "string", "label": "string", "collectionId": "string", "multiple": "boolean" } + type FunctionProp = { "type": "function", "slug": "string", "label": "string" } + + Function types are used for form submission. form actions should be /api/fn/{{function}}. and always use POST method. Available Handlebars helpers: eq, ifCond, truncateText, formatDate, json, uppercase, lowercase, times, join, safeString, default diff --git a/src/handlers/ai/updateModule.js b/src/handlers/ai/updateModule.js index 367d45a..5736159 100644 --- a/src/handlers/ai/updateModule.js +++ b/src/handlers/ai/updateModule.js @@ -23,7 +23,10 @@ function generateUpdateModuleSystemPrompt({ collections, definition }) { - SelectProp: { "type": "select", "slug": "string", "label": "string", "multiple": boolean, "items": string[] } - RichTextProp: { "type": "rich-text", "slug": "string", "label": "string" } - RelationProp: { "type": "relation", "slug": "string", "label": "string", "collectionId": "string", "multiple": boolean } + - FunctionProp: { "type": "function", "slug": "string", "label": "string" } + Function types are used for form submission. form actions should be /api/fn/{{function}}. and always use POST method. + - **Available Handlebars helpers**: eq, ifCond, truncateText, formatDate, json, uppercase, lowercase, times, join, safeString, default diff --git a/src/handlers/module.js b/src/handlers/module.js index bd2548a..be4a2c9 100644 --- a/src/handlers/module.js +++ b/src/handlers/module.js @@ -4,7 +4,7 @@ import { html } from "svelite-html" import { Form } from "#components" import { FieldInput } from "../pages/collections.js" -function DynamicFieldInput(field, fields, linked, module, collections) { +function DynamicFieldInput(field, fields, linked, module, collections, functions = {}) { function getLinkedText(key) { return fields.find(x => x.slug === key)?.label ?? key } @@ -85,10 +85,10 @@ function DynamicFieldInput(field, fields, linked, module, collections) { options.type = 'hidden' } - return FieldInput(options, collections) + return FieldInput(options, collections, functions) } -function sidebarModuleSettings(definition, module, collection, collections) { +function sidebarModuleSettings(definition, module, collection, collections, functions) { const fields = [] fields.push({slug: 'settings.logo', label: 'Site\'s Logo', type: 'file', multiple: false, file_type: 'image'}) fields.push({slug: 'settings.favicon', label: 'Site\'s Favicon', type: 'file', multiple: false, file_type: 'image'}) @@ -120,7 +120,7 @@ function sidebarModuleSettings(definition, module, collection, collections) { fields: [ ``, ``, - (definition.props ?? []).map(prop => DynamicFieldInput(prop, fields, module.links?.[prop.slug], module, collections)).join('') + (definition.props ?? []).map(prop => DynamicFieldInput(prop, fields, module.links?.[prop.slug], module, collections, functions)).join('') ], cancelAction: 'close-module-settings' }): ` @@ -183,7 +183,7 @@ export default { links: {} }) }, - async getSettingsTemplate(body) { + async getSettingsTemplate(body, {functions} = {}) { const moduleId = body.id const module = await db('modules').query().filter('id', '=', moduleId).first(); const page = await getPageFromModule(module); @@ -200,7 +200,7 @@ export default { } - const res = sidebarModuleSettings(definition, module, collection, collections) + const res = sidebarModuleSettings(definition, module, collection, collections, functions) return res; } @@ -208,7 +208,7 @@ export default { } - const res = sidebarModuleSettings(definition, module, null, collections) + const res = sidebarModuleSettings(definition, module, null, collections, functions) return res }, async loadSettings(body) { diff --git a/src/middlewares/context.js b/src/middlewares/context.js index 07bc45e..fc759f3 100644 --- a/src/middlewares/context.js +++ b/src/middlewares/context.js @@ -6,7 +6,8 @@ import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -export function contextMiddleware() { +export function contextMiddleware({functions}) { + console.log('functions: ', functions) return async (req, res, next) => { let user = await db('users').query().filter('id', '=', req.cookies.userId).first() @@ -19,6 +20,7 @@ export function contextMiddleware() { } let context = { + functions, user: user ? { id: user.id, username: user.username, diff --git a/src/pages/collections.js b/src/pages/collections.js index 3f253e6..977cc6f 100644 --- a/src/pages/collections.js +++ b/src/pages/collections.js @@ -66,7 +66,7 @@ function RichText(field) { }) } -export function FieldInput(field, collections = []) { +export function FieldInput(field, collections = [], functions = {}) { let options = { name: field.slug, label: field.label, @@ -112,6 +112,15 @@ export function FieldInput(field, collections = []) { return Select({name, placeholder, label, items}) } + + function FunctionField({name, label, placeholder}) { + return Select({ + name, + placeholder: 'Choose a Function', + label, + items: Object.keys(functions).map(x => ({value: x, text: functions[x].name})) + }) + } const inputs = { input: Input, @@ -122,6 +131,7 @@ export function FieldInput(field, collections = []) { relation: Relation, 'rich-text': RichText, collection: CollectionField, + function: FunctionField, hidden: (options) => Label({text: options.label}) } @@ -212,7 +222,7 @@ export async function CollectionDataListPage({query}) { }) } -export async function CollectionDataCreatePage({query}) { +export async function CollectionDataCreatePage({query, functions}) { const collection = await db('collections').query().filter('id', '=', query.id).first() const collections = await db('collections').query().all() @@ -230,14 +240,14 @@ export async function CollectionDataCreatePage({query}) { cancelHref: back, fields: [ ``, - collection.fields.filter(x => !x.hidden).map(field => FieldInput(field, collections)).join('') + collection.fields.filter(x => !x.hidden).map(field => FieldInput(field, collections, functions)).join('') ], }), ] }) } -export async function CollectionDataUpdatePage({query}) { +export async function CollectionDataUpdatePage({query, functions}) { const data = await db('contents').query().filter('id', '=', query.id).first() const collection = await db('collections').query().filter('id', '=', data._type).first() const collections = await db('collections').query().first() @@ -261,7 +271,7 @@ export async function CollectionDataUpdatePage({query}) { fields: [ '', '', - collection.fields.filter(x => !x.hidden).map(field => FieldInput(field, collections)).join('') + collection.fields.filter(x => !x.hidden).map(field => FieldInput(field, collections, functions)).join('') ], }), ] diff --git a/src/pages/dataTable.js b/src/pages/dataTable.js index 799c10c..5b8e509 100644 --- a/src/pages/dataTable.js +++ b/src/pages/dataTable.js @@ -173,6 +173,7 @@ export function DataTable({filters = [], selectable, items, collectionId, fields return renderFile(item[field.slug]) } if(field.type === 'rich-text') return item[field.slug] ? '...' : '' + if(field.type === 'function') return item[field.slug] ?? '' if(field.type === 'relation') { if(!item[field.slug]) return '' diff --git a/src/pages/fields.js b/src/pages/fields.js index 67c4c71..3f07dbb 100644 --- a/src/pages/fields.js +++ b/src/pages/fields.js @@ -10,6 +10,7 @@ const fieldTypes = [ { text: 'Rich Text', value: 'rich-text' }, { text: 'Relation', value: 'relation' }, { text: 'Collection', value: 'collection', hidden: true }, + { text: 'Function', value: 'function' }, ] function fieldTypeText(key) {