diff --git a/packages/server/package.json b/packages/server/package.json index 95990ac88f..156a25aa0b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -24,7 +24,7 @@ }, "scripts": { "test": "jest routes --runInBand", - "test:integration": "jest routes --runInBand", + "test:integration": "jest workflow --runInBand", "test:watch": "jest -w", "initialise": "node ../cli/bin/budi init -b local -q", "budi": "node ../cli/bin/budi", diff --git a/packages/server/src/api/controllers/workflow.js b/packages/server/src/api/controllers/workflow.js new file mode 100644 index 0000000000..216ca6afd2 --- /dev/null +++ b/packages/server/src/api/controllers/workflow.js @@ -0,0 +1,68 @@ +const CouchDB = require("../../db") +const Ajv = require("ajv") +const newid = require("../../db/newid") + +const ajv = new Ajv() + +exports.create = async function(ctx) { + const db = new CouchDB(ctx.params.instanceId) + const workflow = ctx.request.body + + workflow._id = newid() + + // TODO: Possibly validate the workflow against a schema + + // // validation with ajv + // const model = await db.get(record.modelId) + // const validate = ajv.compile({ + // properties: model.schema, + // }) + // const valid = validate(record) + + // if (!valid) { + // ctx.status = 400 + // ctx.body = { + // status: 400, + // errors: validate.errors, + // } + // return + // } + + + workflow.type = "workflow" + const response = await db.post(workflow) + workflow._rev = response.rev + + ctx.status = 200 + ctx.body = { + message: "Workflow created successfully", + workflow: { + ...workflow, + ...response + } + }; +} + +exports.update = async function(ctx) { + const db = new CouchDB(ctx.params.instanceId) + ctx.body = await db.get(ctx.params.recordId) +} + +exports.fetch = async function(ctx) { + const db = new CouchDB(ctx.params.instanceId) + const response = await db.query(`database/by_type`, { + type: "workflow", + include_docs: true, + }) + ctx.body = response.rows.map(row => row.doc) +} + +exports.find = async function(ctx) { + const db = new CouchDB(ctx.params.instanceId) + ctx.body = await db.get(ctx.params.id) +} + +exports.destroy = async function(ctx) { + const db = new CouchDB(ctx.params.instanceId) + ctx.body = await db.remove(ctx.params.id, ctx.params.rev) +} diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 176eaaff96..360cf8f827 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -15,6 +15,7 @@ const { viewRoutes, staticRoutes, componentRoutes, + workflowRoutes } = require("./routes") const router = new Router() @@ -75,6 +76,9 @@ router.use(recordRoutes.allowedMethods()) router.use(instanceRoutes.routes()) router.use(instanceRoutes.allowedMethods()) + +router.use(workflowRoutes.routes()) +router.use(workflowRoutes.allowedMethods()) // end auth routes router.use(pageRoutes.routes()) diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index a8f57c0e14..e0737ffa6c 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -9,6 +9,7 @@ const modelRoutes = require("./model") const viewRoutes = require("./view") const staticRoutes = require("./static") const componentRoutes = require("./component") +const workflowRoutes = require("./workflow"); module.exports = { authRoutes, @@ -22,4 +23,5 @@ module.exports = { viewRoutes, staticRoutes, componentRoutes, + workflowRoutes } diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 7018ec24b2..85674e11d5 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -76,14 +76,9 @@ exports.createUser = async ( exports.insertDocument = async (databaseId, document) => { const { id, ...documentFields } = document - await new CouchDB(databaseId).put({ _id: id, ...documentFields }) + return await new CouchDB(databaseId).put({ _id: id, ...documentFields }) } -exports.createSchema = async (request, instanceId, schema) => { - for (let model of schema.models) { - await request.post(`/api/${instanceId}/models`).send(model) - } - for (let view of schema.views) { - await request.post(`/api/${instanceId}/views`).send(view) - } -} +exports.destroyDocument = async (databaseId, documentId) => { + return await new CouchDB(databaseId).destroy(documentId); +} \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/workflow.spec.js b/packages/server/src/api/routes/tests/workflow.spec.js new file mode 100644 index 0000000000..5b09009479 --- /dev/null +++ b/packages/server/src/api/routes/tests/workflow.spec.js @@ -0,0 +1,114 @@ +const { + createClientDatabase, + createApplication, + createInstance, + defaultHeaders, + supertest, + insertDocument, + destroyDocument +} = require("./couchTestUtils") + +const TEST_WORKFLOW = { + _id: "Test Workflow", + name: "My Workflow", + pageId: "123123123", + screenId: "kasdkfldsafkl", + live: true, + uiTree: { + + }, + definition: { + triggers: [ + + ], + next: { + actionId: "abc123", + type: "SERVER", + conditions: { + } + } + } +} + +describe("/workflows", () => { + let request + let server + let app + let instance + let workflow + + beforeAll(async () => { + ({ request, server } = await supertest()) + await createClientDatabase(request) + app = await createApplication(request) + }) + + beforeEach(async () => { + instance = await createInstance(request, app._id) + if (workflow) await destroyDocument(workflow.id); + }) + + afterAll(async () => { + server.close() + }) + + const createWorkflow = async () => { + workflow = await insertDocument(instance._id, { + type: "workflow", + ...TEST_WORKFLOW + }); + } + + describe("create", () => { + it("returns a success message when the workflow is successfully created", async () => { + const res = await request + .post(`/api/${instance._id}/workflows`) + .set(defaultHeaders) + .send(TEST_WORKFLOW) + .expect('Content-Type', /json/) + .expect(200) + + expect(res.body.message).toEqual("Workflow created successfully"); + expect(res.body.workflow.name).toEqual("My Workflow"); + }) + }) + + describe("fetch", () => { + it("return all the workflows for an instance", async () => { + await createWorkflow(); + const res = await request + .get(`/api/${instance._id}/workflows`) + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(res.body[0]).toEqual(expect.objectContaining(TEST_WORKFLOW)); + }) + }) + + describe("find", () => { + it("returns a workflow when queried by ID", async () => { + await createWorkflow(); + const res = await request + .get(`/api/${instance._id}/workflows/${workflow.id}`) + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(res.body).toEqual(expect.objectContaining(TEST_WORKFLOW)); + }) + }) + + describe("destroy", () => { + it("deletes a workflow by its ID", async () => { + await createWorkflow(); + const res = await request + .delete(`/api/${instance._id}/workflows/${workflow.id}/${workflow.rev}`) + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(res.body.id).toEqual(TEST_WORKFLOW._id); + }) + }) +}); diff --git a/packages/server/src/api/routes/workflow.js b/packages/server/src/api/routes/workflow.js new file mode 100644 index 0000000000..86332e89aa --- /dev/null +++ b/packages/server/src/api/routes/workflow.js @@ -0,0 +1,13 @@ +const Router = require("@koa/router") +const controller = require("../controllers/workflow") + +const router = Router() + +router + .get("/api/:instanceId/workflows", controller.fetch) + .get("/api/:instanceId/workflows/:id", controller.find) + .post("/api/:instanceId/workflows", controller.create) + .put("/api/:instanceId/workflows/:id", controller.update) + .delete("/api/:instanceId/workflows/:id/:rev", controller.destroy) + +module.exports = router diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js new file mode 100644 index 0000000000..23a839bf97 --- /dev/null +++ b/packages/server/src/schemas/index.js @@ -0,0 +1,38 @@ +const WORKFLOW_SCHEMA = { + properties: { + type: "workflow", + pageId: { + type: "string" + }, + screenId: { + type: "string" + }, + live: { + type: "boolean" + }, + uiTree: { + type: "object" + }, + definition: { + type: "object", + properties: { + triggers: { type: "array" }, + next: { + type: "object", + properties: { + type: { type: "string" }, + actionId: { type: "string" }, + args: { type: "object" }, + conditions: { type: "array" }, + errorHandling: { type: "object" }, + next: { type: "object" } + } + }, + } + } + } +}; + +module.exports = { + WORKFLOW_SCHEMA +}; \ No newline at end of file