From 4891c6de801f77c71454883822343cf268368c4b Mon Sep 17 00:00:00 2001 From: michael shanks Date: Thu, 25 Jul 2019 07:31:54 +0100 Subject: [PATCH] adding in routes for components and pages --- packages/builder/src/builderStore/store.js | 111 ++++++++++++++++-- .../createProps.js | 0 .../pagesParsing/defaultPagesObject.js | 16 +++ .../types.js | 0 .../validatePages.js | 0 .../validateProps.js | 0 packages/builder/tests/validatePages.spec.js | 33 +++++- .../appPackages/testApp/dist/package.tar.gz | Bin 1577 -> 1710 bytes .../testApp/myComponents/components.json | 11 ++ .../testApp/myComponents/textbox/index.js | 0 .../server/appPackages/testApp/pages.json | 19 +++ .../{ui/main/public => public/main}/app.js | 0 .../main/public => public/main}/index.html | 0 .../public => public/unauthenticated}/app.js | 0 .../unauthenticated}/index.html | 0 packages/server/middleware/routers.js | 23 +++- packages/server/tests/serveui.js | 8 +- packages/server/utilities/builder.js | 85 +++++++++++++- packages/server/utilities/createAppPackage.js | 4 +- 19 files changed, 286 insertions(+), 24 deletions(-) rename packages/builder/src/userInterface/{propsDefinitionParsing => pagesParsing}/createProps.js (100%) create mode 100644 packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js rename packages/builder/src/userInterface/{propsDefinitionParsing => pagesParsing}/types.js (100%) rename packages/builder/src/userInterface/{propsDefinitionParsing => pagesParsing}/validatePages.js (100%) rename packages/builder/src/userInterface/{propsDefinitionParsing => pagesParsing}/validateProps.js (100%) create mode 100644 packages/server/appPackages/testApp/myComponents/components.json create mode 100644 packages/server/appPackages/testApp/myComponents/textbox/index.js create mode 100644 packages/server/appPackages/testApp/pages.json rename packages/server/appPackages/testApp/{ui/main/public => public/main}/app.js (100%) rename packages/server/appPackages/testApp/{ui/main/public => public/main}/index.html (100%) rename packages/server/appPackages/testApp/{ui/unauthenticated/public => public/unauthenticated}/app.js (100%) rename packages/server/appPackages/testApp/{ui/unauthenticated/public => public/unauthenticated}/index.html (100%) diff --git a/packages/builder/src/builderStore/store.js b/packages/builder/src/builderStore/store.js index 0633893f71..49f6c0308a 100644 --- a/packages/builder/src/builderStore/store.js +++ b/packages/builder/src/builderStore/store.js @@ -1,10 +1,14 @@ import {hierarchy as hierarchyFunctions, common, getTemplateApi } from "budibase-core"; -import {filter, cloneDeep, sortBy, map, last, +import {filter, cloneDeep, sortBy, map, last, keys, + cloneDeep, keyBy, find, isEmpty, groupBy, reduce} from "lodash/fp"; import {chain, getNode, validate, constructHierarchy, templateApi} from "../common/core"; import {writable} from "svelte/store"; +import { defaultPagesObject } from "../userInterface/pagesParsing/defaultPagesObject" + +const pipe = common.$; export const getStore = () => { @@ -14,6 +18,11 @@ export const getStore = () => { hierarchy: {}, actions: [], triggers: [], + pages:defaultPagesObject(), + mainUi:{}, + unauthenticatedUi:{}, + derivedComponents:[], + rootComponents:[], currentNodeIsNew: false, errors: [], activeNav: "database", @@ -41,6 +50,9 @@ export const getStore = () => { store.saveLevel = saveLevel(store); store.deleteLevel = deleteLevel(store); store.setActiveNav = setActiveNav(store); + store.saveDerivedComponent = saveDerivedComponent(store); + store.refreshComponents = refreshComponents(store); + store.addComponentLibrary = addComponentLibrary(store); return store; } @@ -312,30 +324,107 @@ const deleteLevel = store => level => { }); } -const setActiveNav = databaseStore => navName => { - databaseStore.update(db => { - db.activeNav = navName; - return db; +const setActiveNav = store => navName => { + store.update(s => { + s.activeNav = navName; + return s; }); } const createShadowHierarchy = hierarchy => constructHierarchy(JSON.parse(JSON.stringify(hierarchy))); -const savePackage = (store, db) => { +const saveDerivedComponent = store => (derivedComponent) => { + + store.update(s => { + + const derivedComponents = pipe(s.derivedComponents, [ + filter(c => c._name !== derivedComponent._name) + ]); + + s.derivedComponents = derivedComponents; + + const forSave = pipe(derivedComponents, [ + cloneDeep, + keyBy("_name") + ]); + + for(let c of forSave) { + delete c._name; + } + + s.pages.derivedComponents = forSave; + savePackage(store, s); + + return s; + }) + +}; + +const addComponentLibrary = store => async lib => { + + const response = + await fetch(`/_builder/api/${db.appname}/components?${encodeURI(lib)}`); + + const success = response.status === 200; + + const error = response.status === 404 + ? `Could not find library ${lib}` + : success + ? "" + : response.statusText; + + const components = success + ? await response.json() + : []; + + store.update(s => { + s.componentsErrors.addComponent = error; + if(success) { + s.pages.componentLibraries.push(lib); + s.rootComponents = [...s.rootComponents, components]; + } + + return s; + }) + + +} + +const refreshComponents = store => async () => { + + const components = + await fetch(`/_builder/api/${db.appname}/components`) + .then(r => jQuery.json()); + + const rootComponents = pipe(components, [ + keys, + map(k => ({...components[k], _name:k})) + ]); + + store.update(s => { + s.rootComponents = rootComponents; + return s; + }); +}; + +const savePackage = (store, s) => { const appDefinition = { - hierarchy:db.hierarchy, - triggers:db.triggers, - actions: groupBy("name")(db.actions) + hierarchy:s.hierarchy, + triggers:s.triggers, + actions: groupBy("name")(s.actions), + pages:s.pages, + mainUi: s.mainUi, + unauthenticatedUi: s.unauthenticatedUi }; const data = { appDefinition, - accessLevels:db.accessLevels + accessLevels:s.accessLevels } - fetch(`/_builder/api/${db.appname}/appPackage`, { + fetch(`/_builder/api/${s.appname}/appPackage`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/createProps.js b/packages/builder/src/userInterface/pagesParsing/createProps.js similarity index 100% rename from packages/builder/src/userInterface/propsDefinitionParsing/createProps.js rename to packages/builder/src/userInterface/pagesParsing/createProps.js diff --git a/packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js b/packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js new file mode 100644 index 0000000000..4fbeb98a86 --- /dev/null +++ b/packages/builder/src/userInterface/pagesParsing/defaultPagesObject.js @@ -0,0 +1,16 @@ +export const defaultPagesObject = () => ({ + main: { + index: { + _component : "./components/indexHtml" + }, + appBody: "bbapp.main.json" + }, + unauthenticated: { + index: { + _component : "./components/indexHtml" + }, + appBody: "bbapp.unauthenticated.json" + }, + componentLibraries: ["./components"], + derivedComponents:[] +}); \ No newline at end of file diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/types.js b/packages/builder/src/userInterface/pagesParsing/types.js similarity index 100% rename from packages/builder/src/userInterface/propsDefinitionParsing/types.js rename to packages/builder/src/userInterface/pagesParsing/types.js diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/validatePages.js b/packages/builder/src/userInterface/pagesParsing/validatePages.js similarity index 100% rename from packages/builder/src/userInterface/propsDefinitionParsing/validatePages.js rename to packages/builder/src/userInterface/pagesParsing/validatePages.js diff --git a/packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js b/packages/builder/src/userInterface/pagesParsing/validateProps.js similarity index 100% rename from packages/builder/src/userInterface/propsDefinitionParsing/validateProps.js rename to packages/builder/src/userInterface/pagesParsing/validateProps.js diff --git a/packages/builder/tests/validatePages.spec.js b/packages/builder/tests/validatePages.spec.js index 3305a6bf01..29de0c73fb 100644 --- a/packages/builder/tests/validatePages.spec.js +++ b/packages/builder/tests/validatePages.spec.js @@ -1,7 +1,7 @@ import { validatePages, validatePage -} from "../src/userInterface/propsDefinitionParsing/validatePages"; +} from "../src/userInterface/pagesParsing/validatePages"; const validPages = () => ({ "main" : { @@ -85,8 +85,37 @@ describe("validate pages", () => { expect(errors).toEqual([]); }); - it("should return error when index is not set, or set incorrectly", () => { + it("should return error when component libraries not set", () => { + const pages = validPages(); + + delete pages.componentLibraries; + let errors = validatePages(pages, getComponent); + expect(errors.length).toBe(1); + + pages.componentLibraries = []; + errors = validatePages(pages, getComponent); + expect(errors.length).toBe(1); }); + it("should return error when no main or unauthenticated page", () => { + + let pages = validPages(); + delete pages.main; + let errors = validatePages(pages, getComponent); + expect(errors.length).toBe(1); + + pages = validPages(); + delete pages.unauthenticated; + errors = validatePages(pages, getComponent); + expect(errors.length).toBe(1); + + }); + + it("should return error when page is invalid", () => { + const pages = validPages(); + delete pages.main.index; + const errors = validatePages(pages, getComponent); + expect(errors.length).toBe(1); + }); }); diff --git a/packages/server/appPackages/testApp/dist/package.tar.gz b/packages/server/appPackages/testApp/dist/package.tar.gz index b3335d1d3d1ae9aa6d408e6645f54419d1d2e66a..43818edf6a67364141acdd7a3a175630308c8e24 100644 GIT binary patch literal 1710 zcmV;f22uGRiwFP!000003hi3$Z`wE*-{(pDAAHeCqdEo&A#lfDq+Yja)oopMrT1m3 zI+GaiWa3=xv_)0^_j~LRNJ4&rrGIqkop8FekNZm z+p(NhyW2H8$TYjQ<)A0d9&CFO35i26<$L86o%@dvc3mRGB_mgaiN-g<{q5`65P!>Q zcXa$)@QZER$h2Br$AbCoo3czlc>HgTB=kn_%dO$#2{C#GzxGLJ)Qy0I6DkDdeljhm zftq$hf+ayAae3$go6kM+2bsc5(}msS6B3H1_2QO#u@Tqj%RIu!1C~XB_YiGhqj&E` z_wcMq{VPtPcABm4F?pSsdu-S`F?ZgHbwaK~3Z_Uh2^hF0N;WMLkTaQKK_m*W5Z6lY zGP@GRLmW;~(t)xp#K!|h5D$?@h5ofDJTWI(iDn6$nPyZ-y=HlI<(dih@rWSmd*r6* zhZ41MrOO#3u7qka;*0pAZd`$W^{r>dk6q=f2?xPjGNe9LhCfdKZ?~PU?*F#a>bAOV z-T&=w)&KWFx6hs;WQ-|+T6D+I7myx-vE(p7M##A|>a)Ql#AF%kIW#t?^O+S{<}7c#_;QgAMOlaytqR8MD4BB3SH@&H;(EF~>Lto@ z3h{(T{*vQ4eSS0<~~8OZB%~Xq8P_)$}CMn*-!vUu;G73OuN0zqoB&Mr*@<`qYvp7tDxej zC(gz=^xk7v@=%rTl5O*|`XryQVksS|ID^i?NLk6Qs+JAjEj-H_w#utgk2>vKbuPIx z$9B1fy2_WCAE{O&z;gTv2RT#9%v(5Os(~a;4QREaky1S`;dwzBIAv8{x0~7^#oLR(^0zOh~dXu2T$SCyDEmGoDr|3Ob zxHCa=&(SY{*?dWEWJ3YGhGM8EL%!^#Qy~$M{SCG52)sq{%Zy53@-%W%+I~ux%wccB zmsamKccGuc=R@twPB!FZ&3>%ehSY2IktE8@-%M*Q`rJc>Ey{HERoeqB12V=}lt^K4#aa;bb3E~`Y#6Z4>|4?lMG z{O}9AX+Wow*?mWa;$-cs@;!G}qBwIk;Lt#usdDdcM3V@kTTX{3gwQkF_p+C*XT6l6Dmlp28ffhD^d^1H@M`KEmAZT4gD&t zwAQ{hni48rG#hXY!_A-P%Hywd^>uVhe3yH0BkCEE9R6y=bwt8&g?vSHSObOwlvq+z zn^eX|;A(|tR_g^Nd~tCwH?RPn@xgB6A4CI2-QDWAJN|2%9sT}mw(Sc4?S+!~>yvwD zI_?wyu5GFQZ>zumR`K6G&Ej8xBiq$+ulPG%h`-%wwW|2Fw&+SpOY+ zQU8|i{~G_9+cl${e(?H#rNaFtxo*4~aBuqN)8X+6lz;!-Ssxvqo+9g2Q;$D;`l_j! zAKT=Lwn*jpZ@W-nWBogwMg6xG{&!5PUDf|SX!ZGDe6LmS6OV0eMcbwo^}kgpu(AHT znfiAW{&%{1eha!c(hpw$%jf_3$>Dkb!@HCI@zG)b-P;NZR8&z#75y*iAD%JhSO7Qx E0QkaY>;M1& literal 1577 zcmV+^2G;o>iwFP!000003hi0zZ`(Ey@8<&jA2?w^9n7^YS++MfEf6Fv7Hmy}#o3o4 zXc>vNSx}-TQh6ZI|Gpz-*?QTITie+~7X-4&<2`uyJ09iMv)xmt)9Ls7NIijy&*Y2Q z?U}aK@0#`ibvlEtX`*MnJ=pdv5)y}C%J<4CI`=Oj>^MY-&y3sn(@?vYTm%~uc9jg7cDU*-}fdk**lAneWAZ978*JDrF^}GfRYGn<3Z_Wn1q@sh6`K|b$eGNrAQA;wh+Cz% z%&tW75Qq0DsX$p4;^S*Z5D$?{h5lF+o|u!YM6(3WOfxE^UQ-@jy2isko)AQRm)sTo zP@>kZbU0(gk$@H>zKXA!#trCK--ey|sZqY1a1fl3G4-i3{AudHZTAMc{&#!Uz#7=P z{&xp;{oe;Yym*d~F{K2s=uDw5AUy(O$zgzukaKA?XM;(~7Y3avWo}3y)6u772&GbT zlG1{cF3d}uRjAC=m=flStki&%}F@AeO9EL;U{;7FC`b=%}7&Afg{BhAdhOY@*>f40KQ-xd1 zv|z>Ok}-~$tYW={!Ui~>S&?PV^2UoVSJ_Zhg;>$DV9bt^d(QdNn3N-~r>mo0p)98m zdpz<>j_36Gk$0U>z8uSnJkUHI_0z5UWkD6vlDv5|uS*>2VRp{2qZ7$0a}^UqubU^w z3fCW>A}Rah(nK4TAGj*UF`F`_Nq`N7CJ8qDkBDiv%RH)8Id#>J6le4yy<`<2j=JJv zibMB3b|eoKbW66)&+4;$!irKlQgH#5gOMu8Ev8m3a$CI%*(EQv*uvWTI5hAv_0^fpb>!NCuJoEcxd#WtjqJ zK#%3ilnY04dKA!M(m7s2FE-@v-(@zh1|(;UD^c`0Lm2 z|Mo!vizd`pZ~k_tV_p3Fwl%x|bJ{8VmFWh2*b@hNNMPg>mp& z)jW^8(7&|E9h@~%u{3~vOeY8E&0Ex%6>C*-b3Jpmz@j0-;@)?FJ3VsdM@p>46q;>( zi>X9Dxkcw8_oyI^2AnvY-H>KTzCx2zwMfxewDemcrM2;;)s{p^(QZK-hTA{RmB(M^ z>YM0+_zriWBN`f!9RF;@O+>-*n6NZtQ9-0>KnGz@iTLXZN; zM6dL{4(@?ZZD371r2_u-Oa9hWdTfh2*5be4UEm)kp#H0pP~(3Obf{weHhJA}c+K7W zw;zs9&j9|TcNZgcbbgM^!?qrO@%*r@nV;I^nzl$w`@b!=0&DT#>(BAuGZp@OwpG9X z-3u*0{|jlMUkN|8wKZ* { ctx.request.body); ctx.response.status = StatusCodes.OK; }) + .get("/_builder/api/:appname/components", async (ctx) => { + if(!config.dev) { + ctx.request.status = StatusCodes.FORBIDDEN; + ctx.body = "run in dev mode to access builder"; + return; + } + + try { + ctx.body = getComponents( + config, + ctx.params.appname, + ctx.query.lib); + ctx.response.status = StatusCodes.OK; + } catch(e) { + if(e.status) { + ctx.response.status = e.status; + } else { + throw e; + } + } + }) .get("/:appname", async (ctx) => { await send(ctx, "/index.html", { root: ctx.publicPath }); }) diff --git a/packages/server/tests/serveui.js b/packages/server/tests/serveui.js index c8ef0d5644..6bb6855b91 100644 --- a/packages/server/tests/serveui.js +++ b/packages/server/tests/serveui.js @@ -7,7 +7,7 @@ module.exports = (app) => { const response = await app.get("/testApp") .expect(statusCodes.OK); - const expectedIndexHtml = await readFile("appPackages/testApp/ui/unauthenticated/public/index.html", "utf8"); + const expectedIndexHtml = await readFile("appPackages/testApp/public/unauthenticated/index.html", "utf8"); expect(response.text).toBe(expectedIndexHtml); @@ -17,7 +17,7 @@ module.exports = (app) => { const response = await app.get("/testApp/app.js") .expect(statusCodes.OK); - const expectedFile = await readFile("appPackages/testApp/ui/unauthenticated/public/app.js", "utf8"); + const expectedFile = await readFile("appPackages/testApp/public/unauthenticated/app.js", "utf8"); expect(response.text).toBe(expectedFile); @@ -28,7 +28,7 @@ module.exports = (app) => { .set("cookie", app.credentials.testAppUser1.cookie) .expect(statusCodes.OK); - const expectedIndexHtml = await readFile("appPackages/testApp/ui/main/public/index.html", "utf8"); + const expectedIndexHtml = await readFile("appPackages/testApp/public/main/index.html", "utf8"); expect(response.text).toBe(expectedIndexHtml); @@ -39,7 +39,7 @@ module.exports = (app) => { .set("cookie", app.credentials.testAppUser1.cookie) .expect(statusCodes.OK); - const expectedFile = await readFile("appPackages/testApp/ui/main/public/app.js", "utf8"); + const expectedFile = await readFile("appPackages/testApp/public/main/app.js", "utf8"); expect(response.text).toBe(expectedFile); diff --git a/packages/server/utilities/builder.js b/packages/server/utilities/builder.js index ec23a9da0a..f98ca2df87 100644 --- a/packages/server/utilities/builder.js +++ b/packages/server/utilities/builder.js @@ -1,6 +1,22 @@ -const { appPackageFolder, appsFolder } = require("./createAppPackage"); -const { writeFile, readFile, readdir } = require("./fsawait"); -const { pipe : $ } = require("budibase-core").common; +const { + appPackageFolder, + appsFolder +} = require("./createAppPackage"); +const { + writeFile, + readFile, + readdir, + exists +} = require("./fsawait"); +const { resolve } = require("path"); +const { $ } = require("budibase-core").common; +const { + keys, + reduce, + map, + flatten, + some +} = require("lodash/fp"); module.exports.getPackageForBuilder = async (config, appname) => { const appPath = appPackageFolder(config, appname); @@ -11,12 +27,15 @@ module.exports.getPackageForBuilder = async (config, appname) => { accessLevels: JSON.parse(await readFile( `${appPath}/access_levels.json`, + "utf8")), + + pages: JSON.parse(await readFile( + `${appPath}/pages.json`, "utf8")) }) } - module.exports.savePackage = async (config, appname, pkg) => { const appPath = appPackageFolder(config, appname); await writeFile( @@ -29,10 +48,68 @@ module.exports.savePackage = async (config, appname, pkg) => { JSON.stringify(pkg.accessLevels), "utf8"); + await writeFile( + `${appPath}/pages.json`, + JSON.stringify(pkg.accessLevels), + "utf8"); } module.exports.getApps = async (config) => await readdir(appsFolder(config)); +module.exports.getComponents = async (config, appname, lib) => { + + const componentsInLibrary = (libname) => { + const isRelative = some(c => c === libname.substring(0,1)) + ("./~\\".split("")); + + const componentsPath = isRelative + ? resolve(appPath, libname, "components.json") + : resolve(libname, "components.json"); + + if(!await exists(componentsPath)) { + const e = new Error(`could not find components definition file at ${componentsPath}`); + e.statusCode = 404; + throw e; + } + + let components; + try { + components = JSON.parse( + readFile(componentsPath, "utf8")); + } catch(e) { + const e = new Error(`could not parse JSON - ${componentsPath} `); + throw e; + } + + return $(components, [ + keys, + reduce((obj, k) => { + obj[`${libname}/${k}`] = components[k] + return obj; + }, {}) + ]) + } + + let libs; + if(!lib) { + const appPath = appPackageFolder(config, appname); + + const pages = JSON.parse(await readFile( + `${appPath}/pages.json`, + "utf8")); + + if(!pages.componentLibraries) return []; + + libs = pages.componentLibraries; + } else { + libs = [lib]; + } + + return $(libs, [ + map(componentsInLibrary), + flatten + ]); +} diff --git a/packages/server/utilities/createAppPackage.js b/packages/server/utilities/createAppPackage.js index 68a8ee4edf..baf98a8051 100644 --- a/packages/server/utilities/createAppPackage.js +++ b/packages/server/utilities/createAppPackage.js @@ -65,9 +65,9 @@ const applictionVersionPath = (appname, versionId) => const publicPaths = (appPath) => ({ mainUiPath: resolve(join( - __dirname, appPath, "ui", "main", "public")), + __dirname, appPath, "public", "main")), unauthenticatedUiPath: resolve(join( - __dirname, appPath, "ui", "unauthenticated", "public")) + __dirname, appPath, "public", "unauthenticated")) });