From 8c6a97198fc8317073953b1ff38b6b6fcb138496 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Wed, 4 Nov 2020 12:36:38 +0000 Subject: [PATCH] Main work to get screens into the DB, fixing up issue with async page updates not being handled in order. --- .../builder/src/builderStore/store/index.js | 81 +++++++++---------- .../builder/src/builderStore/storeUtils.js | 19 +++-- .../server/src/api/controllers/application.js | 1 + packages/server/src/api/controllers/page.js | 25 ++---- packages/server/src/api/controllers/screen.js | 36 ++++----- packages/server/src/api/index.js | 4 + packages/server/src/api/routes/index.js | 4 +- packages/server/src/api/routes/pages.new.js | 27 +------ packages/server/src/api/routes/screen.js | 30 ++++++- packages/server/src/db/utils.js | 2 +- 10 files changed, 107 insertions(+), 122 deletions(-) diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 897dbd96d5..9f1ce9e555 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -77,23 +77,18 @@ export const getStore = () => { export default getStore const setPackage = (store, initial) => async pkg => { - const [main_screens, unauth_screens] = await Promise.all([ - api - .get(`/_builder/api/${pkg.application._id}/pages/main/screens`) - .then(r => r.json()), - api - .get(`/_builder/api/${pkg.application._id}/pages/unauthenticated/screens`) - .then(r => r.json()), - ]) + const screens = await api.get("/api/screens").then(r => r.json()) + const mainScreens = screens.filter(screen => screen._id.includes(pkg.pages.main._id)), + unauthScreens = screens.filter(screen => screen._id.includes(pkg.pages.unauthenticated._id)) pkg.pages = { main: { ...pkg.pages.main, - _screens: Object.values(main_screens), + _screens: mainScreens, }, unauthenticated: { ...pkg.pages.unauthenticated, - _screens: Object.values(unauth_screens), + _screens: unauthScreens, }, } @@ -107,7 +102,7 @@ const setPackage = (store, initial) => async pkg => { regenerateCssForScreen(screen) } - await api.post(`/_builder/api/${pkg.application._id}/pages/${name}`, { + await api.post(`/api/pages/${page._id}`, { page: { componentLibraries: pkg.application.componentLibraries, ...page, @@ -115,8 +110,8 @@ const setPackage = (store, initial) => async pkg => { screens: page._screens, }) } - generateInitialPageCss("main") - generateInitialPageCss("unauthenticated") + await generateInitialPageCss("main") + await generateInitialPageCss("unauthenticated") pkg.justCreated = false } @@ -128,8 +123,8 @@ const setPackage = (store, initial) => async pkg => { initial.pages = pkg.pages initial.hasAppPackage = true initial.screens = [ - ...Object.values(main_screens), - ...Object.values(unauth_screens), + ...Object.values(mainScreens), + ...Object.values(unauthScreens), ] initial.builtins = [getBuiltin("##builtin/screenslot")] initial.appInstance = pkg.application.instance @@ -139,40 +134,36 @@ const setPackage = (store, initial) => async pkg => { return initial } -const saveScreen = store => screen => { - store.update(state => { - return _saveScreen(store, state, screen) - }) -} - -const _saveScreen = async (store, s, screen) => { - const pageName = s.currentPageName || "main" - const currentPageScreens = s.pages[pageName]._screens +const saveScreen = store => async screen => { + const storeContents = get(store) + const pageName = storeContents.currentPageName || "main" + const currentPage = storeContents.pages[pageName] + const currentPageScreens = currentPage._screens + let savePromise await api - .post(`/_builder/api/${s.appId}/pages/${pageName}/screen`, screen) - .then(() => { - if (currentPageScreens.includes(screen)) return + .post(`/api/${currentPage._id}/screens`, screen) + .then(() => { + if (currentPageScreens.includes(screen)) return - const screens = [...currentPageScreens, screen] + const screens = [...currentPageScreens, screen] - store.update(innerState => { - innerState.pages[pageName]._screens = screens - innerState.screens = screens - innerState.currentPreviewItem = screen - const safeProps = makePropsSafe( - innerState.components[screen.props._component], - screen.props - ) - innerState.currentComponentInfo = safeProps - screen.props = safeProps - - _savePage(innerState) - return innerState + // TODO: should carry out all server updates to screen in a single call + store.update(state => { + state.pages[pageName]._screens = screens + state.screens = screens + state.currentPreviewItem = screen + const safeProps = makePropsSafe( + state.components[screen.props._component], + screen.props + ) + state.currentComponentInfo = safeProps + screen.props = safeProps + savePromise = _savePage(state) + return state + }) }) - }) - - return s + await savePromise } const createScreen = store => async screen => { @@ -182,7 +173,7 @@ const createScreen = store => async screen => { state.currentComponentInfo = screen.props state.currentFrontEndType = "screen" regenerateCssForCurrentScreen(state) - savePromise = _saveScreen(store, state, screen) + savePromise = saveScreen(store)(screen) return state }) await savePromise diff --git a/packages/builder/src/builderStore/storeUtils.js b/packages/builder/src/builderStore/storeUtils.js index b19df1976e..108bff0e63 100644 --- a/packages/builder/src/builderStore/storeUtils.js +++ b/packages/builder/src/builderStore/storeUtils.js @@ -3,6 +3,7 @@ import { getBuiltin, } from "components/userInterface/pagesParsing/createProps" import api from "./api" +import { store } from "builderStore" import { generate_screen_css } from "./generate_css" import { uuid } from "./uuid" import getNewComponentName from "./getNewComponentName" @@ -35,14 +36,20 @@ export const saveCurrentPreviewItem = s => ? savePage(s) : saveScreenApi(s.currentPreviewItem, s) -export const savePage = async s => { - const pageName = s.currentPageName || "main" - const page = s.pages[pageName] - await api.post(`/api/pages/${pageName}`, { - page: { componentLibraries: s.pages.componentLibraries, ...page }, - uiFunctions: s.currentPageFunctions, +export const savePage = async state => { + const pageName = state.currentPageName || "main" + const page = state.pages[pageName] + + const response = await api.post(`/api/pages/${page._id}`, { + page: { componentLibraries: state.pages.componentLibraries, ...page }, + uiFunctions: state.currentPageFunctions, screens: page._screens, + }).then(response => response.json()) + store.update(innerState => { + innerState.pages[pageName]._rev = response.rev + return innerState }) + return state } export const saveScreenApi = (screen, s) => { diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 40c59755b6..8d07e52f4e 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -140,6 +140,7 @@ exports.delete = async function(ctx) { const result = await db.destroy() // remove top level directory + // TODO: look into why this isn't a callback await fs.rmdir(join(budibaseAppsDir(), ctx.params.appId), { recursive: true, }) diff --git a/packages/server/src/api/controllers/page.js b/packages/server/src/api/controllers/page.js index 4707a66193..06d427f546 100644 --- a/packages/server/src/api/controllers/page.js +++ b/packages/server/src/api/controllers/page.js @@ -8,27 +8,12 @@ exports.save = async function(ctx) { const appPackage = ctx.request.body // TODO: rename to something more descriptive - await buildPage( - ctx.config, - ctx.user.appId, - ctx.params.pageId, - ctx.request.body - ) + await buildPage(ctx.user.appId, ctx.params.pageId, ctx.request.body) - // write the page data to couchDB - // if (pkg.page._css) { - // delete pkg.page._css - // } - - // if (pkg.page.name) { - // delete pkg.page.name - // } - - // if (pkg.page._screens) { - // delete pkg.page._screens - // } + // remove special doc props which couch will complain about + delete appPackage.page._css + delete appPackage.page._screens appPackage.page._id = appPackage.page._id || generatePageID() - await db.put(appPackage.page) - ctx.message = "Page saved successfully." + ctx.body = await db.put(appPackage.page) ctx.status = 200 } diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index 6500ea48f3..fa7142a42a 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -10,7 +10,19 @@ exports.fetch = async ctx => { const db = new CouchDB(ctx.user.appId) const screens = await db.allDocs( - getScreenParams(ctx.params.pageId, null, { + getScreenParams(null, { + include_docs: true, + }) + ) + + ctx.body = screens.rows.map(element => element.doc) +} + +exports.find = async ctx => { + const db = new CouchDB(ctx.user.appId) + + const screens = await db.allDocs( + getScreenParams(ctx.params.pageId, { include_docs: true, }) ) @@ -18,33 +30,15 @@ exports.fetch = async ctx => { ctx.body = screens.response.rows } -exports.create = async ctx => { - const db = new CouchDB(ctx.user.appId) - const screen = { - // name: ctx.request.body.name, - // _rev: ctx.request.body._rev, - // permissions: ctx.request.body.permissions || [], - // _id: generateAccessLevelID(), - // type: "accesslevel", - } - - const response = await db.put(screen) - ctx.body = { - ...screen, - ...response, - } - ctx.message = `Screen '${screen.name}' created successfully.` -} - exports.save = async ctx => { const appId = ctx.user.appId const db = new CouchDB(appId) const screen = ctx.request.body if (!screen._id) { - screen._id = generateScreenID() + screen._id = generateScreenID(ctx.params.pageId) } - + delete screen._css const response = await db.put(screen) ctx.message = `Screen ${screen.name} saved.` diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 0fbd65501b..631e5a18e4 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -7,6 +7,7 @@ const { isDev } = require("../utilities") const { authRoutes, pageRoutes, + screenRoutes, userRoutes, deployRoutes, applicationRoutes, @@ -97,6 +98,9 @@ router.use(templatesRoutes.allowedMethods()) router.use(pageRoutes.routes()) router.use(pageRoutes.allowedMethods()) +router.use(screenRoutes.routes()) +router.use(screenRoutes.allowedMethods()) + router.use(applicationRoutes.routes()) router.use(applicationRoutes.allowedMethods()) diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 0025c5fabf..599edaa3e2 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -1,5 +1,6 @@ const authRoutes = require("./auth") -const pageRoutes = require("./pages") +const pageRoutes = require("./pages.new") +const screenRoutes = require("./screen") const userRoutes = require("./user") const applicationRoutes = require("./application") const tableRoutes = require("./table") @@ -19,6 +20,7 @@ module.exports = { deployRoutes, authRoutes, pageRoutes, + screenRoutes, userRoutes, applicationRoutes, rowRoutes, diff --git a/packages/server/src/api/routes/pages.new.js b/packages/server/src/api/routes/pages.new.js index e87ac49e9c..1ec01dc780 100644 --- a/packages/server/src/api/routes/pages.new.js +++ b/packages/server/src/api/routes/pages.new.js @@ -1,35 +1,10 @@ const Router = require("@koa/router") -const joiValidator = require("../../middleware/joi-validator") -const Joi = require("joi") const authorized = require("../../middleware/authorized") const { BUILDER } = require("../../utilities/accessLevels") const controller = require("../controllers/page") const router = Router() -function generateSaveValidation() { - // prettier-ignore - return joiValidator.body(Joi.object({ - _css: Joi.string().allow(""), - name: Joi.string().required(), - route: Joi.string().required(), - props: Joi.object({ - _id: Joi.string().required(), - _component: Joi.string().required(), - _children: Joi.array().required(), - _instanceName: Joi.string().required(), - _styles: Joi.object().required(), - type: Joi.string().optional(), - table: Joi.string().optional(), - }).required().unknown(true), - }).unknown(true)) -} - -router.post( - "/api/pages/:pageId", - authorized(BUILDER), - generateSaveValidation(), - controller.save -) +router.post("/api/pages/:pageId", authorized(BUILDER), controller.save) module.exports = router diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 23da51e616..e36a09d610 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -2,12 +2,38 @@ const Router = require("@koa/router") const controller = require("../controllers/screen") const authorized = require("../../middleware/authorized") const { BUILDER } = require("../../utilities/accessLevels") +const joiValidator = require("../../middleware/joi-validator") +const Joi = require("joi") const router = Router() +function generateSaveValidation() { + // prettier-ignore + return joiValidator.body(Joi.object({ + _css: Joi.string().allow(""), + name: Joi.string().required(), + route: Joi.string().required(), + props: Joi.object({ + _id: Joi.string().required(), + _component: Joi.string().required(), + _children: Joi.array().required(), + _instanceName: Joi.string().required(), + _styles: Joi.object().required(), + type: Joi.string().optional(), + table: Joi.string().optional(), + }).required().unknown(true), + }).unknown(true)) +} + router - .get("/api/:pageId/screens", authorized(BUILDER), controller.fetch) - .post("/api/:pageId/screens", authorized(BUILDER), controller.save) + .get("/api/screens", authorized(BUILDER), controller.fetch) + .get("/api/:pageId/screens", authorized(BUILDER), controller.find) + .post( + "/api/:pageId/screens", + authorized(BUILDER), + generateSaveValidation(), + controller.save + ) .delete("/api/:screenId/:revId", authorized(BUILDER), controller.destroy) module.exports = router diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 6c5beee009..a213dc9066 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -204,7 +204,7 @@ exports.generateScreenID = pageId => { * Gets parameters for retrieving screens for a particular page, this is a utility function for the getDocParams function. */ exports.getScreenParams = (pageId = null, otherProps = {}) => { - return getDocParams(DocumentType.SCREEN, pageId, otherProps) + return getDocParams(DocumentTypes.SCREEN, pageId, otherProps) } /**