From 3496c13f36d3aced332059e0ab8bd19e8e91b682 Mon Sep 17 00:00:00 2001 From: Michael Shanks Date: Mon, 23 Sep 2019 06:08:06 +0100 Subject: [PATCH] state event handlers - API --- AUTHORS.md | 2 +- packages/client/src/api/index.js | 61 +++++++++++++++++++++ packages/client/src/api/listRecords.js | 18 +++++++ packages/client/src/api/loadRecord.js | 21 ++++++++ packages/client/src/api/saveRecord.js | 29 ++++++++++ packages/client/src/core/createCoreApp.js | 15 ++++++ packages/client/src/core/index.js | 17 ++++++ packages/client/src/createApp.js | 6 ++- packages/client/src/index.js | 8 ++- packages/client/src/state/coreHandlers.js | 63 ++++++++++++++++++++++ packages/client/src/state/eventHandlers.js | 31 +++++++++-- packages/client/src/state/standardState.js | 2 + packages/client/src/state/stateBinding.js | 4 +- 13 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 packages/client/src/api/index.js create mode 100644 packages/client/src/api/listRecords.js create mode 100644 packages/client/src/api/loadRecord.js create mode 100644 packages/client/src/api/saveRecord.js create mode 100644 packages/client/src/core/createCoreApp.js create mode 100644 packages/client/src/core/index.js create mode 100644 packages/client/src/state/coreHandlers.js create mode 100644 packages/client/src/state/standardState.js diff --git a/AUTHORS.md b/AUTHORS.md index be4466f15f..5407fe480a 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -2,5 +2,5 @@ Contributors === * Michael Shanks - [@mjashanks](https://github.com/mjashanks) -* Dan - [@danbudi](https://github.com/danbudi) +* Daniel Loudon - [@danbudi](https://github.com/marblekirby) * Joe - [@joebudi](https://github.com/joebudi) \ No newline at end of file diff --git a/packages/client/src/api/index.js b/packages/client/src/api/index.js new file mode 100644 index 0000000000..d20c1a619f --- /dev/null +++ b/packages/client/src/api/index.js @@ -0,0 +1,61 @@ +import { ERROR } from "../state/standardState"; +import {loadRecord} from "./loadRecord"; +import {listRecords} from "./listRecords"; + +export const createApi = ({rootPath, setState, getState}) => { + + const apiCall = (method) => ({url, body, notFound, badRequest, forbidden}) => { + fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json', + }, + body: body && JSON.stringify(body), + credentials: "same-origin" + }).then(r => { + switch (r.status) { + case 200: + return r.json(); + case 404: + return error(notFound || `${url} Not found`); + case 400: + return error(badRequest || `${url} Bad Request`); + case 403: + return error(forbidden || `${url} Forbidden`); + default: + if(r.status.toString().startsWith("2") + || r.status.toString().startsWith("3")) + return r.json() + else + return error(`${url} - ${r.statusText}`); + } + }); + } + + const post = apiCall("POST"); + const get = apiCall("GET"); + const patch = apiCall("PATCH"); + const del = apiCall("DELETE"); + + const ERROR_MEMBER = "##error"; + const error = message => { + const e = {}; + e[ERROR_MEMBER] = message; + setState(ERROR, message); + return e; + } + + const isSuccess = obj => !!obj[ERROR_MEMBER]; + + const apiOpts = { + rootPath, setState, getState, isSuccess, error, + post, get, patch, delete:del + }; + + return { + loadRecord:loadRecord(apiOpts), + listRecords: listRecords(api) + } +} + + diff --git a/packages/client/src/api/listRecords.js b/packages/client/src/api/listRecords.js new file mode 100644 index 0000000000..afb0010e71 --- /dev/null +++ b/packages/client/src/api/listRecords.js @@ -0,0 +1,18 @@ +export const listRecords = api => async ({indexKey, statePath}) => { + if(!recordKey) { + api.error("Load Record: record key not set"); + return; + } + + if(!statePath) { + api.error("Load Record: state path not set"); + return; + } + + const records = get({ + url:`${rootPath}/api/listRecords/${indexKey}` + }); + + if(api.isSuccess(records)) + api.setState(statePath, records); +} \ No newline at end of file diff --git a/packages/client/src/api/loadRecord.js b/packages/client/src/api/loadRecord.js new file mode 100644 index 0000000000..b1a4dd0b1c --- /dev/null +++ b/packages/client/src/api/loadRecord.js @@ -0,0 +1,21 @@ + + + export const loadRecord = (api) => async ({recordKey, statePath}) => { + + if(!recordKey) { + api.error("Load Record: record key not set"); + return; + } + + if(!statePath) { + api.error("Load Record: state path not set"); + return; + } + + const record = await get({ + url:`${rootPath}/api/record/${key}` + }); + + if(api.isSuccess(record)) + api.setState(statePath, record); +} \ No newline at end of file diff --git a/packages/client/src/api/saveRecord.js b/packages/client/src/api/saveRecord.js new file mode 100644 index 0000000000..10dd354aac --- /dev/null +++ b/packages/client/src/api/saveRecord.js @@ -0,0 +1,29 @@ + + + export const saveRecord = (api) => async ({statePath}) => { + + if(!statePath) { + api.error("Load Record: state path not set"); + return; + } + + const recordtoSave = api.getState(statePath); + + if(!recordtoSave) { + api.error(`there is no record in state: ${statePath}`); + return; + } + + if(!recordtoSave.key) { + api.error(`item in state does not appear to be a record - it has no key (${statePath})`); + return; + } + + const savedRecord = await post({ + url:`${rootPath}/api/record/${recordtoSave.key}`, + body: recordtoSave + }); + + if(api.isSuccess(record)) + api.setState(statePath, savedRecord); +} \ No newline at end of file diff --git a/packages/client/src/core/createCoreApp.js b/packages/client/src/core/createCoreApp.js new file mode 100644 index 0000000000..6dc857a79f --- /dev/null +++ b/packages/client/src/core/createCoreApp.js @@ -0,0 +1,15 @@ + + +export const createCoreApp = (appDefinition, user) => { + const app = { + datastore: null, + crypto:null, + publish: () => {}, + hierarchy: appDefinition.hierarchy, + actions: appDefinition.actions + }; + + app.asUser(user); + + return app; +} \ No newline at end of file diff --git a/packages/client/src/core/index.js b/packages/client/src/core/index.js new file mode 100644 index 0000000000..79fbefe5c3 --- /dev/null +++ b/packages/client/src/core/index.js @@ -0,0 +1,17 @@ +import { createCoreApp } from "./createCoreApp" +import { + getNew, getNewChild +} from "../../../core/src/recordApi/getNew"; + +export const createCoreApi = (appDefinition, user) => { + + const app = createCoreApp(appDefinition, user); + + return { + recordApi: { + getNew: getNew(app), + getNewChild: getNewChild(app) + } + } + +} \ No newline at end of file diff --git a/packages/client/src/createApp.js b/packages/client/src/createApp.js index cbb58d989f..513f5b1216 100644 --- a/packages/client/src/createApp.js +++ b/packages/client/src/createApp.js @@ -5,8 +5,9 @@ import { import {writable} from "svelte/store"; import { $ } from "./core/common"; import { setupBinding } from "./state/stateBinding"; +import { createCoreApi } from "./core"; -export const createApp = componentLibraries => { +export const createApp = (componentLibraries, appDefinition, user) => { const initialiseComponent = (props, htmlElement) => { @@ -14,7 +15,7 @@ export const createApp = componentLibraries => { if(!componentName || !libName) return; - const {initialProps, bind} = setupBinding(store, props); + const {initialProps, bind} = setupBinding(store, props, coreApi); const component = new (componentLibraries[libName][componentName])({ target: htmlElement, @@ -25,6 +26,7 @@ export const createApp = componentLibraries => { } + const coreApi = createCoreApi(appDefinition, user); const store = writable({}); const _app = { diff --git a/packages/client/src/index.js b/packages/client/src/index.js index e8903e0927..24e4c85469 100644 --- a/packages/client/src/index.js +++ b/packages/client/src/index.js @@ -4,6 +4,12 @@ import { createApp } from "./createApp"; export const loadBudibase = async (componentLibraries, props) => { const appDefinition = window["##BUDIBASE_APPDEFINITION##"]; + const user = localStorage.getItem("budibase:user") || { + name: "annonymous", + permissions : [], + isUser:false, + temp:false + } if(!componentLibraries) { @@ -22,7 +28,7 @@ export const loadBudibase = async (componentLibraries, props) => { props = appDefinition.props; } - const _app = createApp(componentLibraries); + const _app = createApp(componentLibraries, user); _app.initialiseComponent( props, document.body); diff --git a/packages/client/src/state/coreHandlers.js b/packages/client/src/state/coreHandlers.js new file mode 100644 index 0000000000..2d601aa768 --- /dev/null +++ b/packages/client/src/state/coreHandlers.js @@ -0,0 +1,63 @@ +import { ERROR } from "./standardState"; + +export const getNewChildRecordToState = (store, coreApi) => + ({recordKey, collectionName,childRecordType,statePath}) => { + const error = errorHandler(setState); + try { + if(!recordKey) { + error("getNewChild > recordKey not set"); + return; + } + + if(!collectionName) { + error("getNewChild > collectionName not set"); + return; + } + + if(!childRecordType) { + error("getNewChild > childRecordType not set"); + return; + } + + if(!statePath) { + error("getNewChild > statePath not set"); + return; + } + + const rec = coreApi.recordApi.getNewChild(recordKey, collectionName, childRecordType); + setState(store, statePath, rec); + } + catch(e) { + error(e.message); + } +} + + +export const getNewRecordToState = (store, coreApi) => + ({collectionKey,childRecordType,statePath}) => { + const error = errorHandler(setState); + try { + if(!collectionKey) { + error("getNewChild > collectionKey not set"); + return; + } + + if(!childRecordType) { + error("getNewChild > childRecordType not set"); + return; + } + + if(!statePath) { + error("getNewChild > statePath not set"); + return; + } + + const rec = coreApi.recordApi.getNew(collectionKey, childRecordType); + setState(store, statePath, rec); + } + catch(e) { + error(e.message); + } +} + +const errorHandler = setState => message => setState(ERROR, message); \ No newline at end of file diff --git a/packages/client/src/state/eventHandlers.js b/packages/client/src/state/eventHandlers.js index 452bf07fd0..718f61b69b 100644 --- a/packages/client/src/state/eventHandlers.js +++ b/packages/client/src/state/eventHandlers.js @@ -1,22 +1,43 @@ -import { - setState -} from "./setState"; +import { setState } from "./setState"; +import { getState } from "./getState"; import { isArray, isUndefined } from "lodash/fp"; +import { createApi } from "../api"; +import { + getNewChildRecordToState, getNewRecordToState +} from "./coreHandlers"; + export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType"; -export const eventHandlers = store => { +export const eventHandlers = (store,coreApi) => { const handler = (parameters, execute) => ({ execute, parameters }); + const api = createApi({ + rootPath:"", + setState: (path, value) => setState(store, path, value), + getState: (path, fallback) => getState(store, path, fallback) + }); + const setStateHandler = ({path, value}) => setState(store, path, value); return { - "Set State": handler(["path", "value"], setStateHandler) + "Set State": handler(["path", "value"], setStateHandler), + "Load Record": handler(["recordKey", "statePath"], api.loadRecord), + "List Records": handler(["indexKey", "statePath"], api.listRecords), + "Save Record": handler(["statePath"], api.saveRecord), + + "Get New Child Record": handler( + ["recordKey", "collectionName", "childRecordType", "statePath"], + getNewChildRecordToState(store, coreApi)), + + "Get New Record": handler( + ["collectionKey", "childRecordType", "statePath"], + getNewRecordToState(store, coreApi)), }; }; diff --git a/packages/client/src/state/standardState.js b/packages/client/src/state/standardState.js new file mode 100644 index 0000000000..ad74b7fb3b --- /dev/null +++ b/packages/client/src/state/standardState.js @@ -0,0 +1,2 @@ + +export const ERROR = "##error_message"; \ No newline at end of file diff --git a/packages/client/src/state/stateBinding.js b/packages/client/src/state/stateBinding.js index 4b9b5a9e8b..f70dc05847 100644 --- a/packages/client/src/state/stateBinding.js +++ b/packages/client/src/state/stateBinding.js @@ -9,7 +9,7 @@ import { export const BB_STATE_BINDINGPATH = "##bbstate"; export const BB_STATE_FALLBACK = "##bbstatefallback"; const doNothing = () => {}; -export const setupBinding = (store, props) => { +export const setupBinding = (store, props, coreApi) => { const initialProps = {...props}; const boundProps = []; @@ -50,7 +50,7 @@ export const setupBinding = (store, props) => { if(boundProps.length === 0 && componentEventHandlers.length === 0) return; - const handlerTypes = eventHandlers(store); + const handlerTypes = eventHandlers(store, coreApi); const unsubscribe = store.subscribe(s => { const newProps = {};