From 615000187b319b2272d24c58dd5f4aa97eed7b3f Mon Sep 17 00:00:00 2001
From: Michael Taylor <mtaylor@michaeltaylor.dev>
Date: Mon, 24 Jan 2022 07:40:17 -0500
Subject: [PATCH] feat: datalayer organization setup

---
 .../20220122141750-create-simulator-table.cjs |  13 ++
 ...0220122164836-create-organizatin-tablec.js |  13 ++
 package-lock.json                             |  11 +
 package.json                                  |   1 +
 .../20220121232631-add-test-organization.cjs  |  26 +++
 src/controllers/organization.controller.js    |   9 +-
 src/controllers/staging.controller.js         | 199 +-----------------
 src/controllers/units.controller.js           |   2 +-
 src/fullnode/data-layer-utils.js              |  28 +++
 src/fullnode/dataLayerReadService.js          |  69 ++++++
 src/fullnode/dataLayerWriteService.js         |  37 ++++
 ...Service.js => dataLayerWriteServiceOld.js} |  84 ++------
 src/fullnode/index.js                         |   4 +-
 src/fullnode/persistance.js                   |  19 +-
 src/fullnode/simulatorV2.js                   |  47 ++++-
 src/models/co-benefits/co-benefits.model.js   |  11 +
 src/models/index.js                           |   1 +
 src/models/locations/locations.model.js       |  11 +
 src/models/meta/meta.model.js                 |   1 +
 .../organizations/organizations.model.js      | 144 +++++++++----
 .../organizations.modeltypes.cjs              |  28 ++-
 src/models/projects/projects.model.js         |  85 +++++++-
 .../qualifications/qualifications.model.js    |  11 +
 src/models/ratings/ratings.model.js           |  11 +
 .../related-projects.model.js                 |  11 +
 src/models/simulator/index.js                 |   2 +
 src/models/simulator/simulator.mock.js        |   8 +
 src/models/simulator/simulator.model.js       |  20 ++
 src/models/simulator/simulator.modeltypes.cjs |  14 ++
 src/models/simulator/simulator.stub.json      |   1 +
 src/models/staging/staging.model.js           | 144 +++++++++++++
 src/models/units/units.model.js               |  68 +++++-
 src/models/vintages/vintages.model.js         |  11 +
 src/models/vintages/vintages.stub.json        |  34 +--
 src/routes/v1/resources/organization.js       |  17 +-
 src/routes/v1/resources/staging.js            |   2 +-
 src/server.js                                 |   3 +
 src/validations/index.js                      |   1 +
 src/validations/organizations.validations.js  |  10 +
 39 files changed, 854 insertions(+), 357 deletions(-)
 create mode 100644 migrations/20220122141750-create-simulator-table.cjs
 create mode 100644 migrations/20220122164836-create-organizatin-tablec.js
 create mode 100644 seeders/20220121232631-add-test-organization.cjs
 create mode 100644 src/fullnode/data-layer-utils.js
 create mode 100644 src/fullnode/dataLayerReadService.js
 create mode 100644 src/fullnode/dataLayerWriteService.js
 rename src/fullnode/{dataLayerService.js => dataLayerWriteServiceOld.js} (57%)
 create mode 100644 src/models/simulator/index.js
 create mode 100644 src/models/simulator/simulator.mock.js
 create mode 100644 src/models/simulator/simulator.model.js
 create mode 100644 src/models/simulator/simulator.modeltypes.cjs
 create mode 100644 src/models/simulator/simulator.stub.json
 create mode 100644 src/validations/organizations.validations.js

diff --git a/migrations/20220122141750-create-simulator-table.cjs b/migrations/20220122141750-create-simulator-table.cjs
new file mode 100644
index 00000000..a19dc2f5
--- /dev/null
+++ b/migrations/20220122141750-create-simulator-table.cjs
@@ -0,0 +1,13 @@
+'use strict';
+
+const modelTypes = require('../src/models/simulator/simulator.modeltypes.cjs');
+
+module.exports = {
+  async up(queryInterface) {
+    await queryInterface.createTable('simulator', modelTypes);
+  },
+
+  async down(queryInterface) {
+    await queryInterface.dropTable('simulator');
+  },
+};
diff --git a/migrations/20220122164836-create-organizatin-tablec.js b/migrations/20220122164836-create-organizatin-tablec.js
new file mode 100644
index 00000000..e94b78c2
--- /dev/null
+++ b/migrations/20220122164836-create-organizatin-tablec.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const modelTypes = require('../src/models/organizations/organizations.modeltypes.cjs');
+
+module.exports = {
+  async up(queryInterface) {
+    await queryInterface.createTable('organizations', modelTypes);
+  },
+
+  async down(queryInterface) {
+    await queryInterface.dropTable('organizations');
+  },
+};
diff --git a/package-lock.json b/package-lock.json
index 25a5a4e9..a8869a8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
         "joi": "^17.5.0",
         "lodash": "^4.17.21",
         "mysql2": "^2.3.3",
+        "random-hash": "^4.0.1",
         "request-promise": "^4.2.6",
         "rxjs": "^7.5.1",
         "sequelize": "^6.12.0-alpha.1",
@@ -12640,6 +12641,11 @@
       "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
       "dev": true
     },
+    "node_modules/random-hash": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/random-hash/-/random-hash-4.0.1.tgz",
+      "integrity": "sha1-NC19FFAeZk8L7i2aE+6unJc8t1U="
+    },
     "node_modules/randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -25082,6 +25088,11 @@
       "integrity": "sha512-SbiLPU40JuJniHexQSAgad32hfwd+DRUdwF2PlVuI5RZD0/vahUco7R8vD86J/tcEKKF9vZrUVwgtmGCqlCKyA==",
       "dev": true
     },
+    "random-hash": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/random-hash/-/random-hash-4.0.1.tgz",
+      "integrity": "sha1-NC19FFAeZk8L7i2aE+6unJc8t1U="
+    },
     "randombytes": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
diff --git a/package.json b/package.json
index 96fde78b..eb85127d 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "joi": "^17.5.0",
     "lodash": "^4.17.21",
     "mysql2": "^2.3.3",
+    "random-hash": "^4.0.1",
     "request-promise": "^4.2.6",
     "rxjs": "^7.5.1",
     "sequelize": "^6.12.0-alpha.1",
diff --git a/seeders/20220121232631-add-test-organization.cjs b/seeders/20220121232631-add-test-organization.cjs
new file mode 100644
index 00000000..b725cf72
--- /dev/null
+++ b/seeders/20220121232631-add-test-organization.cjs
@@ -0,0 +1,26 @@
+'use strict';
+const dotenv = require('dotenv');
+dotenv.config();
+
+const metaStub = [
+  {
+    metaKey: 'organizationId',
+    metaValue: 'f1c54511-865e-4611-976c-7c3c1f704662',
+  },
+  {
+    metaKey: 'organizationName',
+    metaValue: 'Demo Org',
+  },
+];
+
+module.exports = {
+  up: async (queryInterface) => {
+    if (process.env.USE_SIMULATOR === 'true') {
+      await queryInterface.bulkInsert('meta', metaStub, {});
+    }
+  },
+
+  down: async (queryInterface) => {
+    await queryInterface.bulkDelete('meta');
+  },
+};
diff --git a/src/controllers/organization.controller.js b/src/controllers/organization.controller.js
index c7bf2ce2..846643e5 100644
--- a/src/controllers/organization.controller.js
+++ b/src/controllers/organization.controller.js
@@ -1,13 +1,16 @@
 import { Organization } from '../models/organizations';
-import { updateOrganization } from '../fullnode/dataLayerService';
+
 export const findAll = async (req, res) => {
   return res.json(await Organization.getOrgsMap());
 };
 
 export const create = async (req, res) => {
-  const { name, icon, website } = req.body;
+  const { name, icon } = req.body;
   return res.json({
     message: 'New organization created successfully.',
-    orgId: await updateOrganization(name, icon, website),
+    orgId: await Organization.createHomeOrganization(name, icon, 'v1'),
   });
 };
+
+// eslint-disable-next-line
+export const importOrg = async (req, res) => {};
diff --git a/src/controllers/staging.controller.js b/src/controllers/staging.controller.js
index f078240b..5ded4cb2 100644
--- a/src/controllers/staging.controller.js
+++ b/src/controllers/staging.controller.js
@@ -1,12 +1,7 @@
 import _ from 'lodash';
-import * as fullNode from '../fullnode';
 
 import { Staging, Project, Unit } from '../models';
-import {
-  assertStagingRecordExists,
-  assertUnitRecordExists,
-  assertProjectRecordExists,
-} from '../utils/data-assertions';
+import { assertStagingRecordExists } from '../utils/data-assertions';
 
 export const findAll = async (req, res) => {
   try {
@@ -76,69 +71,15 @@ export const findAll = async (req, res) => {
 
 export const commit = async (req, res) => {
   try {
-    const queryResponses = await Staging.findAll();
-
-    await Promise.all(
-      queryResponses.map(async (queryResponse) => {
-        const stagingRecord = queryResponse.dataValues;
-
-        const {
-          id: stagingRecordId,
-          uuid,
-          table,
-          action,
-          commited,
-          data: rawData,
-        } = stagingRecord;
-        let data = JSON.parse(rawData);
-
-        if (table === 'Projects' && !commited) {
-          const customAssertionMessage = `The project record for the warehouseProjectId: ${uuid} does not exist. Please remove ${uuid} from the staging table and try to commit again.`;
-          switch (action) {
-            case 'INSERT':
-              data.warehouseUnitId = uuid;
-              fullNode.createProjectRecord(uuid, data, stagingRecordId);
-              break;
-            case 'UPDATE':
-              await assertProjectRecordExists(uuid, customAssertionMessage);
-              fullNode.updateProjectRecord(uuid, data, stagingRecordId);
-              break;
-            case 'DELETE':
-              await assertProjectRecordExists(uuid, customAssertionMessage);
-              fullNode.deleteProjectRecord(uuid, stagingRecordId);
-              break;
-          }
-        } else if (table === 'Units' && !commited) {
-          const customAssertionMessage = `The unit record for the warehouseUnitId: ${uuid} does not exist. Please remove ${uuid} from the staging table and try to commit again.`;
-          switch (action) {
-            case 'INSERT':
-              fullNode.createUnitRecord(uuid, data, stagingRecordId);
-              break;
-            case 'UPDATE':
-              await assertUnitRecordExists(uuid, customAssertionMessage);
-              fullNode.updateUnitRecord(uuid, data, stagingRecordId);
-              break;
-            case 'DELETE':
-              await assertUnitRecordExists(uuid, customAssertionMessage);
-              fullNode.deleteUnitRecord(uuid, stagingRecordId);
-              break;
-          }
-        }
-
-        // set the commited flag to true
-        await Staging.update(
-          { commited: true },
-          { where: { id: stagingRecordId } },
-        );
-      }),
-    );
-
+    await Staging.pushToDataLayer();
     res.json({ message: 'Staging Table committed to full node' });
   } catch (error) {
     res.status(400).json({
       message: 'Error commiting staging table',
       error: error.message,
     });
+
+    console.trace(error);
   }
 };
 
@@ -176,135 +117,3 @@ export const clean = async (req, res) => {
     });
   }
 };
-
-export const commitV2 = async (req, res) => {
-  try {
-    const queryResponses = await Staging.findAll();
-
-    const changeList = [];
-
-    await Promise.all(
-      queryResponses.map(async (queryResponse) => {
-        const stagingRecord = queryResponse.dataValues;
-
-        const {
-          id: stagingRecordId,
-          uuid,
-          table,
-          action,
-          commited,
-          data: rawData,
-        } = stagingRecord;
-        let data = JSON.parse(rawData);
-
-        if (table === 'Projects' && !commited) {
-          const customAssertionMessage = `The project record for the warehouseProjectId: ${uuid} does not exist. Please remove ${uuid} from the staging table and try to commit again.`;
-          const projectRecord = _.omit(data, 'qualifications', 'vintages');
-          // const qualificationsRecords = data.qualifications;
-          // const vintageRecords = data.vintages;
-
-          console.log('!@@');
-
-          switch (action) {
-            case 'INSERT':
-              data.warehouseProjectId = uuid;
-
-              changeList.push([
-                {
-                  action: 'delete',
-                  key: new Buffer(`project_${uuid}`).toString('hex'),
-                },
-                {
-                  action: 'insert',
-                  key: new Buffer(`project_${uuid}`).toString('hex'),
-                  value: new Buffer(JSON.stringify(projectRecord)).toString(
-                    'hex',
-                  ),
-                },
-              ]);
-              break;
-            case 'UPDATE':
-              await assertProjectRecordExists(uuid, customAssertionMessage);
-              changeList.push([
-                {
-                  action: 'delete',
-                  key: new Buffer(`project_${uuid}`).toString('hex'),
-                },
-                {
-                  action: 'insert',
-                  key: new Buffer(`project_${uuid}`).toString('hex'),
-                  value: new Buffer(JSON.stringify(data)).toString('hex'),
-                },
-              ]);
-              break;
-            case 'DELETE':
-              await assertProjectRecordExists(uuid, customAssertionMessage);
-              changeList.push([
-                {
-                  action: 'delete',
-                  key: new Buffer(`project_${uuid}`).toString('hex'),
-                },
-              ]);
-              break;
-          }
-        } else if (table === 'Units' && !commited) {
-          const customAssertionMessage = `The unit record for the warehouseUnitId: ${uuid} does not exist. Please remove ${uuid} from the staging table and try to commit again.`;
-          const unitRecord = _.omit(data, 'qualifications', 'vintage');
-          switch (action) {
-            case 'INSERT':
-              changeList.push({
-                action: 'delete',
-                key: new Buffer(`unit_${uuid}`).toString('hex'),
-              });
-              changeList.push({
-                action: 'insert',
-                key: new Buffer(`unit_${uuid}`).toString('hex'),
-                value: new Buffer(JSON.stringify(unitRecord)).toString('hex'),
-              });
-
-              break;
-            case 'UPDATE':
-              await assertUnitRecordExists(uuid, customAssertionMessage);
-              changeList.push([
-                {
-                  action: 'delete',
-                  key: new Buffer(`unit_${uuid}`).toString('hex'),
-                },
-                {
-                  action: 'insert',
-                  key: new Buffer(`unit_${uuid}`).toString('hex'),
-                  value: new Buffer(JSON.stringify(unitRecord)).toString('hex'),
-                },
-              ]);
-              break;
-            case 'DELETE':
-              await assertUnitRecordExists(uuid, customAssertionMessage);
-              changeList.push([
-                {
-                  action: 'delete',
-                  key: new Buffer(`unit_${uuid}`).toString('hex'),
-                },
-              ]);
-              break;
-          }
-        }
-
-        // set the commited flag to true
-        await Staging.update(
-          { commited: true },
-          { where: { id: stagingRecordId } },
-        );
-      }),
-    );
-
-    console.log(changeList);
-    await fullNode.pushChangeListToRegistryTable('units', changeList);
-
-    res.json({ message: 'Staging Table committed to full node' });
-  } catch (error) {
-    res.status(400).json({
-      message: 'Error commiting staging table',
-      error: error.message,
-    });
-  }
-};
diff --git a/src/controllers/units.controller.js b/src/controllers/units.controller.js
index 55e20b2f..29f31ec6 100644
--- a/src/controllers/units.controller.js
+++ b/src/controllers/units.controller.js
@@ -36,7 +36,7 @@ export const create = async (req, res) => {
     newRecord.warehouseUnitId = uuid;
 
     // All new units are assigned to the home orgUid
-    const orgUid = _.head(Object.keys(await Organization.getHomeOrg()));
+    const { orgUid } = await Organization.getHomeOrg();
     newRecord.orgUid = orgUid;
     newRecord.unitOwnerOrgUid = orgUid;
 
diff --git a/src/fullnode/data-layer-utils.js b/src/fullnode/data-layer-utils.js
new file mode 100644
index 00000000..9b88894c
--- /dev/null
+++ b/src/fullnode/data-layer-utils.js
@@ -0,0 +1,28 @@
+export const changeListFactory = (action, id, record) => {
+  console.log({ action, id, record });
+  switch (action) {
+    case 'INSERT':
+      return {
+        action: 'insert',
+        key: Buffer.from(id).toString('hex'),
+        value: Buffer.from(JSON.stringify(record)).toString('hex'),
+      };
+    case 'UPDATE':
+      return [
+        {
+          action: 'delete',
+          key: Buffer.from(id).toString('hex'),
+        },
+        {
+          action: 'insert',
+          key: Buffer.from(id).toString('hex'),
+          value: Buffer.from(JSON.stringify(record)).toString('hex'),
+        },
+      ];
+    case 'DELETE':
+      return {
+        action: 'delete',
+        key: Buffer.from(id).toString('hex'),
+      };
+  }
+};
diff --git a/src/fullnode/dataLayerReadService.js b/src/fullnode/dataLayerReadService.js
new file mode 100644
index 00000000..74fcc75d
--- /dev/null
+++ b/src/fullnode/dataLayerReadService.js
@@ -0,0 +1,69 @@
+import _ from 'lodash';
+import { Sequelize } from 'sequelize';
+
+import { Meta } from '../models';
+import * as dataLayer from './persistance';
+import * as simulator from './simulatorV2';
+
+const Op = Sequelize.Op;
+
+let updateInterval;
+
+export const startDataLayerUpdatePolling = () => {
+  console.log('Start Dataayer Update Polling');
+  /* updateInterval = setInterval(async () => {
+    const tablesToUpdate = await dataLayerWasUpdated();
+    _.keys(tablesToUpdate).forEach((tableMetaId) => {});
+  }, 10000);*/
+};
+
+export const stopDataLayerUpdatePolling = () => {
+  clearInterval(updateInterval);
+};
+
+export const dataLayerWasUpdated = async () => {
+  const tableIdsMeta = await Meta.findAll({
+    where: {
+      metaKey: {
+        [Op.like]: '%TableStoreId',
+      },
+    },
+    raw: true,
+  });
+
+  const tableHashesMeta = await Meta.findAll({
+    where: {
+      metaKey: {
+        [Op.like]: '%TableStoreHash',
+      },
+    },
+    raw: true,
+  });
+
+  const tableHashMap = {};
+
+  tableIdsMeta.forEach((meta) => {
+    tableHashMap[meta.metaKey] = null;
+  });
+
+  tableHashesMeta.forEach((meta) => {
+    const tableKey = meta.metaKey.replace('Hash', 'Id');
+    tableHashMap[tableKey] = meta.metaValue;
+  });
+
+  let newHashes;
+  if (process.env.USE_SIMULATOR === 'true') {
+    newHashes = await simulator.getRoots(_.keys(tableHashMap));
+  } else {
+    newHashes = await dataLayer.getRoots(_.keys(tableHashMap));
+  }
+
+  console.log(newHashes);
+
+  const tablesWereUpdatedMap = {};
+  _.keys(tableHashMap).map((key, index) => {
+    tablesWereUpdatedMap[key] = tableHashMap[key] !== newHashes[index];
+  });
+
+  return tablesWereUpdatedMap;
+};
diff --git a/src/fullnode/dataLayerWriteService.js b/src/fullnode/dataLayerWriteService.js
new file mode 100644
index 00000000..57326f8a
--- /dev/null
+++ b/src/fullnode/dataLayerWriteService.js
@@ -0,0 +1,37 @@
+import * as dataLayer from './persistance';
+import * as simulator from './simulatorV2';
+
+export const createDataLayerStore = async () => {
+  let storeId;
+  if (process.env.USE_SIMULATOR === 'true') {
+    storeId = await simulator.createDataLayerStore();
+  } else {
+    storeId = await dataLayer.createDataLayerStore();
+  }
+
+  return storeId;
+};
+
+export const syncDataLayer = async (storeId, data) => {
+  const changeList = Object.keys(data).map((key) => {
+    return {
+      action: 'insert',
+      key,
+      value: data[key],
+    };
+  });
+
+  if (process.env.USE_SIMULATOR === 'true') {
+    return simulator.pushChangeListToDataLayer(storeId, changeList);
+  } else {
+    return dataLayer.pushChangeListToDataLayer(storeId, changeList);
+  }
+};
+
+export const pushDataLayerChangeList = async (storeId, changeList) => {
+  if (process.env.USE_SIMULATOR === 'true') {
+    return simulator.pushChangeListToDataLayer(storeId, changeList);
+  } else {
+    return dataLayer.pushChangeListToDataLayer(storeId, changeList);
+  }
+};
diff --git a/src/fullnode/dataLayerService.js b/src/fullnode/dataLayerWriteServiceOld.js
similarity index 57%
rename from src/fullnode/dataLayerService.js
rename to src/fullnode/dataLayerWriteServiceOld.js
index 35156490..5bda33d1 100644
--- a/src/fullnode/dataLayerService.js
+++ b/src/fullnode/dataLayerWriteServiceOld.js
@@ -1,14 +1,12 @@
-import { Meta } from '../models';
+import { Meta, Organization } from '../models';
 import * as dataLayer from './persistance';
 import * as simulator from './simulatorV2';
 
 const strToHex = (str) => {
-  console.log('str', str);
   return new Buffer(str).toString('hex');
 };
 
 const createChangeObject = (type, key, value) => {
-  console.log(type, key, value);
   return { action: type, key: strToHex(key), value: strToHex(value) };
 };
 
@@ -17,8 +15,6 @@ const ensureDataLayerStore = async (metaKey) => {
     where: { metaKey },
   });
 
-  console.log('@@@', storeMeta);
-
   if (!storeMeta) {
     let storeId;
     if (process.env.USE_SIMULATOR === 'true') {
@@ -27,9 +23,7 @@ const ensureDataLayerStore = async (metaKey) => {
       storeId = await dataLayer.createDataLayerStore();
     }
 
-    console.log({ [metaKey]: storeId });
-
-    await Meta.create({ [metaKey]: storeId });
+    await Meta.create({ metaKey, metaValue: storeId });
     return storeId;
   }
 
@@ -37,74 +31,42 @@ const ensureDataLayerStore = async (metaKey) => {
 };
 
 const ensureRegistryStore = async (orgUid) => {
-  const storeId = await ensureDataLayerStore('registryId');
+  const storeId = await ensureDataLayerStore(`registryId`);
   const changeList = [createChangeObject('insert', 'registryId', storeId)];
 
-  console.log('#####orgUid', orgUid, changeList);
-
-  let registryId;
   if (process.env.USE_SIMULATOR === 'true') {
-    registryId = await simulator.pushChangeListToDataLayer(orgUid, changeList);
+    await simulator.pushChangeListToDataLayer(orgUid, changeList);
   } else {
-    registryId = await dataLayer.pushChangeListToDataLayer(orgUid, changeList);
+    await dataLayer.pushChangeListToDataLayer(orgUid, changeList);
   }
-  await Meta.create({ registryId });
-  return registryId;
+
+  return storeId;
 };
 
 const ensureRegistryTableStore = async (tableName) => {
-  const orgUid = await ensureOrganizationStore();
+  const myOrganization = await Organization.findOne({
+    where: { isHome: true },
+    raw: true,
+  });
+
+  const orgUid = myOrganization.orgUid;
   const registryId = await ensureRegistryStore(orgUid);
-  const metaKey = `${tableName}StoreId`;
+  const metaKey = `${tableName}TableStoreId`;
   const storeId = await ensureDataLayerStore(metaKey);
   const changeList = [createChangeObject('insert', metaKey, storeId)];
 
-  let registryTableStoreId;
   if (process.env.USE_SIMULATOR === 'true') {
-    registryTableStoreId = await simulator.pushChangeListToDataLayer(
-      registryId,
-      changeList,
-    );
+    await simulator.pushChangeListToDataLayer(registryId, changeList);
   } else {
-    registryTableStoreId = await dataLayer.pushChangeListToDataLayer(
-      registryId,
-      changeList,
-    );
+    await dataLayer.pushChangeListToDataLayer(registryId, changeList);
   }
 
-  await Meta.create({ [metaKey]: registryTableStoreId });
-
-  console.log('3############', registryTableStoreId);
-  return registryTableStoreId;
-};
-
-const ensureOrganizationStore = async () => {
-  const metaKey = 'organizationId';
-  const storeMeta = await Meta.findOne({
-    where: { metaKey },
-  });
-
-  if (!storeMeta) {
-    let storeId;
-
-    if (process.env.USE_SIMULATOR === 'true') {
-      storeId = await simulator.createDataLayerStore();
-    } else {
-      storeId = await dataLayer.createDataLayerStore();
-    }
-
-    await Meta.upsert({ metaKey: 'organizationId', metaValue: storeId });
-    return storeId;
-  }
-
-  return storeMeta.metaValue;
+  return storeId;
 };
 
 export const pushChangeListToRegistryTable = async (tableName, changeList) => {
   const storeId = await ensureRegistryTableStore(tableName);
 
-  console.log('########', storeId);
-
   if (storeId) {
     if (process.env.USE_SIMULATOR === 'true') {
       return simulator.pushChangeListToDataLayer(storeId, changeList);
@@ -116,18 +78,8 @@ export const pushChangeListToRegistryTable = async (tableName, changeList) => {
   throw new Error('Could not create datalayer store');
 };
 
-export const getRegistryTableData = async (tableName) => {
-  const storeId = await ensureDataLayerStore(tableName);
-
-  if (process.env.USE_SIMULATOR === 'true') {
-    return simulator.getStoreData(storeId);
-  } else {
-    return dataLayer.getStoreData(storeId);
-  }
-};
-
 export const updateOrganization = async (orgName, orgIconUrl, orgWebSite) => {
-  const orgUid = await ensureOrganizationStore();
+  const orgUid = await ensureDataLayerStore();
 
   const changeList = [];
   const metaUpdateList = [];
diff --git a/src/fullnode/index.js b/src/fullnode/index.js
index 5b1d0cd6..e9e13781 100644
--- a/src/fullnode/index.js
+++ b/src/fullnode/index.js
@@ -1,2 +1,2 @@
-export * from './dispatcher';
-export * from './dataLayerService';
+export * from './dataLayerWriteService';
+export * from './dataLayerReadService';
diff --git a/src/fullnode/persistance.js b/src/fullnode/persistance.js
index 9e52b6dd..25be8c6e 100644
--- a/src/fullnode/persistance.js
+++ b/src/fullnode/persistance.js
@@ -34,11 +34,11 @@ export const createDataLayerStore = async () => {
   throw new Error('Error creating new datalayer store');
 };
 
-export const pushChangeListToDataLayer = async (storeId, changeList) => {
+export const pushChangeListToDataLayer = async (storeId, changelist) => {
   const options = {
     url: `${rpcUrl}/update_data_store`,
     body: JSON.stringify({
-      changelist: changeList,
+      changelist,
       id: storeId,
     }),
   };
@@ -52,6 +52,21 @@ export const pushChangeListToDataLayer = async (storeId, changeList) => {
   throw new Error('Error updating datalayer store');
 };
 
+export const getRoot = async (storeId) => {
+  const options = {
+    url: `${rpcUrl}/get_root`,
+    body: JSON.stringify({
+      id: storeId,
+    }),
+  };
+
+  const response = request(Object.assign({}, baseOptions, options));
+
+  if (response.body && response.body.keys_values) {
+    return response.body.keys_values;
+  }
+};
+
 export const getStoreData = async (storeId) => {
   if (storeId) {
     const options = {
diff --git a/src/fullnode/simulatorV2.js b/src/fullnode/simulatorV2.js
index 9b1477a4..0c5642ea 100644
--- a/src/fullnode/simulatorV2.js
+++ b/src/fullnode/simulatorV2.js
@@ -1,25 +1,33 @@
 import { uuid as uuidv4 } from 'uuidv4';
-import { Meta } from '../models';
+import { Simulator } from '../models';
 import { Sequelize } from 'sequelize';
+import { RandomHash } from 'random-hash';
+import { randomBytes } from 'crypto';
 
 const Op = Sequelize.Op;
 
+const generateHash = new RandomHash({
+  length: 55,
+  charset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_',
+  rng: randomBytes,
+});
+
 export const createDataLayerStore = async () => {
   return uuidv4();
 };
 
-export const pushChangeListToDataLayer = (storeId, changeList) => {
-  console.log('!!!!!', storeId, changeList);
-  return Promise.all(
+export const pushChangeListToDataLayer = async (storeId, changeList) => {
+  console.log(storeId, changeList);
+  await Promise.all(
     changeList.map(async (change) => {
       if (change.action === 'insert') {
-        await Meta.create({
-          metaKey: `${storeId}_${change.key}`,
-          metaValue: change.value,
+        await Simulator.upsert({
+          key: `simulator_${storeId}_${change.key}`,
+          value: change.value,
         });
       } else if (change.action === 'delete') {
-        await Meta.destroy({
-          where: { metaKey: `simulator_${storeId}_${change.key}` },
+        await Simulator.destroy({
+          where: { key: `simulator_${storeId}_${change.key}` },
         });
       }
     }),
@@ -28,14 +36,15 @@ export const pushChangeListToDataLayer = (storeId, changeList) => {
 
 export const getStoreData = async (storeId) => {
   if (storeId) {
-    const results = await await Meta.findAll({
+    const results = await await Simulator.findAll({
       where: {
-        metaKey: { [Op.like]: `simulator_${storeId}%` },
+        key: { [Op.like]: `${storeId}%` },
       },
     });
 
     // return the store data in a form that mirrors that datalayer response
     return {
+      root: `0x${generateHash()}`,
       keys_values: results.map((result) => {
         const simulatedResult = result;
         simulatedResult.hash = result.metaValue.split('').reduce((a, b) => {
@@ -52,3 +61,19 @@ export const getStoreData = async (storeId) => {
 
   return new Error('Error getting datalayer store data');
 };
+
+// eslint-disable-next-line
+export const getRoot = (storeId) => {
+  return Promise.resolve({
+    // fake hash
+    hash: `0x${generateHash()}`,
+    success: true,
+  });
+};
+
+export const getRoots = (storeIds) => {
+  return Promise.resolve({
+    hash: storeIds.map(() => `0x${generateHash()}`),
+    success: true,
+  });
+};
diff --git a/src/models/co-benefits/co-benefits.model.js b/src/models/co-benefits/co-benefits.model.js
index 28003c25..d307f7b8 100644
--- a/src/models/co-benefits/co-benefits.model.js
+++ b/src/models/co-benefits/co-benefits.model.js
@@ -33,6 +33,17 @@ class CoBenefit extends Model {
     safeMirrorDbHandler(() => CoBenefitMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 CoBenefit.init(ModelTypes, {
diff --git a/src/models/index.js b/src/models/index.js
index 19262ccf..db521fb3 100644
--- a/src/models/index.js
+++ b/src/models/index.js
@@ -27,3 +27,4 @@ export * from './vintages';
 export * from './staging';
 export * from './organizations';
 export * from './meta';
+export * from './simulator';
diff --git a/src/models/locations/locations.model.js b/src/models/locations/locations.model.js
index 2ae636fa..16e042ed 100644
--- a/src/models/locations/locations.model.js
+++ b/src/models/locations/locations.model.js
@@ -35,6 +35,17 @@ class ProjectLocation extends Model {
     safeMirrorDbHandler(() => ProjectLocationMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 ProjectLocation.init(ModelTypes, {
diff --git a/src/models/meta/meta.model.js b/src/models/meta/meta.model.js
index fc478237..1b076ada 100644
--- a/src/models/meta/meta.model.js
+++ b/src/models/meta/meta.model.js
@@ -11,6 +11,7 @@ class Meta extends Model {}
 Meta.init(ModelTypes, {
   sequelize,
   modelName: 'meta',
+  freezeTableName: true,
   timestamps: false,
   createdAt: false,
   updatedAt: false,
diff --git a/src/models/organizations/organizations.model.js b/src/models/organizations/organizations.model.js
index 17cf72c7..dd7a1d2e 100644
--- a/src/models/organizations/organizations.model.js
+++ b/src/models/organizations/organizations.model.js
@@ -3,60 +3,126 @@
 import Sequelize from 'sequelize';
 const { Model } = Sequelize;
 import { sequelize } from '../database';
-import { Meta } from '../../models';
+import { createDataLayerStore, syncDataLayer } from '../../fullnode';
 
 import ModelTypes from './organizations.modeltypes.cjs';
 
 class Organization extends Model {
   static async getHomeOrg() {
-    const organizationRecord = await Meta.findOne({
-      where: { metaKey: 'organizationId' },
-    });
-    const organizationNameRecord = await Meta.findOne({
-      where: { metaKey: 'organizationName' },
-    });
-    const organizationIconRecord = await Meta.findOne({
-      where: { metaKey: 'organizationIconUrl' },
+    const myOrganization = await Organization.findOne({
+      attributes: ['orgUid', 'name', 'icon'],
+      where: { isHome: true },
+      raw: true,
     });
 
-    if (organizationRecord) {
-      let homeOrg = { [organizationRecord.metaValue]: { writeAccess: true } };
+    if (myOrganization) {
+      return myOrganization;
+    }
 
-      if (organizationNameRecord) {
-        homeOrg[organizationRecord.metaValue].name =
-          organizationNameRecord.metaValue;
-      }
+    return undefined;
+  }
 
-      if (organizationIconRecord) {
-        homeOrg[organizationRecord.metaValue].icon =
-          organizationIconRecord.metaValue;
+  static async getOrgsMap() {
+    const organizations = Organization.findOne({
+      attributes: ['orgUid', 'name', 'icon', 'isHome', 'subscribed'],
+      raw: true,
+    });
+
+    return organizations.reduce((map, current) => {
+      if (map) {
+        map = {};
       }
 
-      return homeOrg;
+      map[current.orgUid] = current;
+    });
+  }
+
+  static async createHomeOrganization(name, icon, dataVersion = 'v1') {
+    const myOrganization = await Organization.getHomeOrg();
+
+    if (myOrganization) {
+      return myOrganization.orgUid;
     }
 
-    return undefined;
-  }
+    const newOrganizationId = await createDataLayerStore();
+    const newRegistryId = await createDataLayerStore();
+    const registryVersionId = await createDataLayerStore();
 
-  static async getOrgsMap() {
-    const homeOrg = await Organization.getHomeOrg();
-    const stubbedOrganizations = {
-      ...homeOrg,
-      '35f92331-c8d7-4e9e-a8d2-cd0a86cbb2cf': {
-        name: 'chili',
-        icon: 'https://climate-warehouse.s3.us-west-2.amazonaws.com/public/orgs/chili.svg',
-      },
-      'fbffae6b-0203-4ac0-a08b-1551b730783b': {
-        name: 'belgium',
-        icon: 'https://climate-warehouse.s3.us-west-2.amazonaws.com/public/orgs/belgium.svg',
-      },
-      '70150fde-57f6-44a6-9486-1fef49528475': {
-        name: 'bulgaria',
-        icon: 'https://climate-warehouse.s3.us-west-2.amazonaws.com/public/orgs/bulgaria.svg',
-      },
-    };
-    return stubbedOrganizations;
+    // sync the organization store
+    await syncDataLayer(newOrganizationId, {
+      registryId: newRegistryId,
+      name,
+      icon,
+    });
+
+    //sync the registry store
+    await syncDataLayer(newRegistryId, {
+      [dataVersion]: registryVersionId,
+    });
+
+    // Create the TableStores
+    const coBenefitsStoreId = await createDataLayerStore();
+    const projectLocationStoreId = await createDataLayerStore();
+    const projectsStoreId = await createDataLayerStore();
+    const projectRatingStoreId = await createDataLayerStore();
+    const relatedProjectsStoreId = await createDataLayerStore();
+    const qualificationsStoreId = await createDataLayerStore();
+    const unitsStoreId = await createDataLayerStore();
+    const vintagesStoreId = await createDataLayerStore();
+    const qualificationUnitJunctionStoreId = await createDataLayerStore();
+
+    await syncDataLayer(registryVersionId, {
+      coBenefitsStoreId,
+      projectLocationStoreId,
+      projectsStoreId,
+      projectRatingStoreId,
+      relatedProjectsStoreId,
+      qualificationsStoreId,
+      unitsStoreId,
+      vintagesStoreId,
+      qualificationUnitJunctionStoreId,
+    });
+
+    await Organization.create({
+      orgUid: newOrganizationId,
+      registryId: registryVersionId,
+      coBenefitsStoreId,
+      projectLocationStoreId,
+      projectsStoreId,
+      projectRatingStoreId,
+      relatedProjectsStoreId,
+      qualificationsStoreId,
+      qualificationUnitJunctionStoreId,
+      unitsStoreId,
+      vintagesStoreId,
+      isHome: true,
+      subscribed: true,
+      name,
+      icon,
+    });
+
+    return newOrganizationId;
   }
+
+  // eslint-disable-next-line
+  static appendNewRegistry = (dataVersion) => {
+    throw new Error('Not implemented yet');
+  };
+
+  // eslint-disable-next-line
+  static importHomeOrganization = (orgUid) => {
+    throw new Error('Not implemented yet');
+  };
+
+  // eslint-disable-next-line
+  static subscribeToOrganization = (orgUid) => {
+    throw new Error('Not implemented yet');
+  };
+
+  // eslint-disable-next-line
+  static unsubscribeToOrganization = (orgUid) => {
+    throw new Error('Not implemented yet');
+  };
 }
 
 Organization.init(ModelTypes, {
diff --git a/src/models/organizations/organizations.modeltypes.cjs b/src/models/organizations/organizations.modeltypes.cjs
index 6d742c24..c7dae3ab 100644
--- a/src/models/organizations/organizations.modeltypes.cjs
+++ b/src/models/organizations/organizations.modeltypes.cjs
@@ -1,4 +1,3 @@
-const { uuid: uuidv4 } = require('uuidv4');
 const Sequelize = require('sequelize');
 
 module.exports = {
@@ -13,6 +12,33 @@ module.exports = {
   },
   name: Sequelize.STRING,
   icon: Sequelize.STRING,
+  registryId: Sequelize.STRING,
+  projectLocationStoreId: Sequelize.STRING,
+  projectLocationStoreHash: Sequelize.STRING,
+  projectRatingStoreId: Sequelize.STRING,
+  projectRatingStoreHash: Sequelize.STRING,
+  coBenefitsStoreId: Sequelize.STRING,
+  coBenefitsStoreHash: Sequelize.STRING,
+  projectsStoreId: Sequelize.STRING,
+  projectsStoreIdHash: Sequelize.STRING,
+  relatedProjectsStoreId: Sequelize.STRING,
+  relatedProjectsStoreHash: Sequelize.STRING,
+  vintagesStoreId: Sequelize.STRING,
+  vintagesStoreHash: Sequelize.STRING,
+  qualificationsStoreId: Sequelize.STRING,
+  qualificationsStoreHash: Sequelize.STRING,
+  qualificationUnitJunctionStoreId: Sequelize.STRING,
+  qualificationUnitJunctionStoreHash: Sequelize.STRING,
+  unitsStoreId: Sequelize.STRING,
+  unitsStoreHash: Sequelize.STRING,
+  subscribed: {
+    type: Sequelize.BOOLEAN,
+    defaultValue: false,
+  },
+  isHome: {
+    type: Sequelize.BOOLEAN,
+    defaultValue: false,
+  },
   createdAt: Sequelize.DATE,
   updatedAt: Sequelize.DATE,
 };
diff --git a/src/models/projects/projects.model.js b/src/models/projects/projects.model.js
index 50bccf40..f8fcbfcb 100644
--- a/src/models/projects/projects.model.js
+++ b/src/models/projects/projects.model.js
@@ -1,10 +1,15 @@
 'use strict';
 
+import _ from 'lodash';
 import Sequelize from 'sequelize';
 import rxjs from 'rxjs';
 const { Model } = Sequelize;
 
-import {sequelize, safeMirrorDbHandler, sanitizeSqliteFtsQuery} from '../database';
+import {
+  sequelize,
+  safeMirrorDbHandler,
+  sanitizeSqliteFtsQuery,
+} from '../database';
 
 import {
   RelatedProject,
@@ -12,8 +17,11 @@ import {
   Qualification,
   ProjectLocation,
   CoBenefit,
+  Rating,
 } from '../';
 
+import { changeListFactory } from '../../fullnode/data-layer-utils';
+
 import ModelTypes from './projects.modeltypes.cjs';
 import { ProjectMirror } from './projects.model.mirror';
 
@@ -170,18 +178,19 @@ class Project extends Model {
     if (columns.length) {
       fields = columns.join(', ');
     }
-  
+
     searchStr = sanitizeSqliteFtsQuery(searchStr);
-    
-    if (searchStr === '*') { // * isn't a valid matcher on its own. return empty set
+
+    if (searchStr === '*') {
+      // * isn't a valid matcher on its own. return empty set
       return {
         count: 0,
         rows: [],
-      }
+      };
     }
-  
+
     if (searchStr.startsWith('+')) {
-      searchStr = searchStr.replace('+', '') // If query starts with +, replace it
+      searchStr = searchStr.replace('+', ''); // If query starts with +, replace it
     }
 
     let sql = `SELECT ${fields} FROM projects_fts WHERE projects_fts MATCH :search`;
@@ -213,6 +222,68 @@ class Project extends Model {
       }),
     };
   }
+
+  static async generateChangeListFromStagedData(
+    action,
+    warehouseProjectId,
+    stagedData,
+  ) {
+    const foreignKeys = [
+      'projectLocations',
+      'qualifications',
+      'vintages',
+      'coBenefits',
+      'relatedProjects',
+    ];
+
+    return Promise.resolve(
+      changeListFactory(
+        action,
+        warehouseProjectId,
+        _.omit(stagedData, foreignKeys),
+      ),
+    );
+  }
+
+  static async generateFullProjectModelChangeListFromStagedRecord(data) {
+    const promises = [Project.generateChangeListFromStagedData(data)];
+
+    if (data.projectLocations) {
+      promises.push(
+        ProjectLocation.generateChangeListFromStagedData(data.projectLocations),
+      );
+    }
+
+    if (data.qualifications) {
+      promises.push(
+        Qualification.generateChangeListFromStagedData(data.qualifications),
+      );
+    }
+
+    if (data.relatedProjects) {
+      promises.push(
+        Rating.generateChangeListFromStagedData(data.relatedProjects),
+      );
+    }
+
+    if (data.coBenefits) {
+      promises.push(
+        CoBenefit.generateChangeListFromStagedData(data.coBenefits),
+      );
+    }
+
+    if (data.vintages) {
+      promises.push(Vintage.generateChangeListFromStagedData(data.vintages));
+    }
+
+    if (data.relatedProjects) {
+      promises.push(
+        RelatedProject.generateChangeListFromStagedData(data.relatedProjects),
+      );
+    }
+
+    return Promise.all(promises);
+  }
 }
 
 Project.init(ModelTypes, {
diff --git a/src/models/qualifications/qualifications.model.js b/src/models/qualifications/qualifications.model.js
index f10abd41..a8a70c09 100644
--- a/src/models/qualifications/qualifications.model.js
+++ b/src/models/qualifications/qualifications.model.js
@@ -47,6 +47,17 @@ class Qualification extends Model {
     safeMirrorDbHandler(() => QualificationMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 Qualification.init(ModelTypes, {
diff --git a/src/models/ratings/ratings.model.js b/src/models/ratings/ratings.model.js
index 147f96f6..ce5efe11 100644
--- a/src/models/ratings/ratings.model.js
+++ b/src/models/ratings/ratings.model.js
@@ -33,6 +33,17 @@ class Rating extends Model {
     safeMirrorDbHandler(() => RatingMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 Rating.init(ModelTypes, {
diff --git a/src/models/related-projects/related-projects.model.js b/src/models/related-projects/related-projects.model.js
index e14e745f..cbb5e1ab 100644
--- a/src/models/related-projects/related-projects.model.js
+++ b/src/models/related-projects/related-projects.model.js
@@ -34,6 +34,17 @@ class RelatedProject extends Model {
     safeMirrorDbHandler(() => RelatedProjectMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 RelatedProject.init(ModelTypes, {
diff --git a/src/models/simulator/index.js b/src/models/simulator/index.js
new file mode 100644
index 00000000..fbbfc7b0
--- /dev/null
+++ b/src/models/simulator/index.js
@@ -0,0 +1,2 @@
+export * from './simulator.model.js';
+export * from './simulator.mock.js';
diff --git a/src/models/simulator/simulator.mock.js b/src/models/simulator/simulator.mock.js
new file mode 100644
index 00000000..fe199fcb
--- /dev/null
+++ b/src/models/simulator/simulator.mock.js
@@ -0,0 +1,8 @@
+import stub from './simulator.stub.json';
+
+export const MetaMock = {
+  findAll: () => stub,
+  findOne: (id) => {
+    return stub.find((record) => record.id == id);
+  },
+};
diff --git a/src/models/simulator/simulator.model.js b/src/models/simulator/simulator.model.js
new file mode 100644
index 00000000..04ce48f4
--- /dev/null
+++ b/src/models/simulator/simulator.model.js
@@ -0,0 +1,20 @@
+'use strict';
+
+import Sequelize from 'sequelize';
+const { Model } = Sequelize;
+import { sequelize } from '../database';
+
+import ModelTypes from './simulator.modeltypes.cjs';
+
+class Simulator extends Model {}
+
+Simulator.init(ModelTypes, {
+  sequelize,
+  modelName: 'simulator',
+  freezeTableName: true,
+  timestamps: false,
+  createdAt: false,
+  updatedAt: false,
+});
+
+export { Simulator };
diff --git a/src/models/simulator/simulator.modeltypes.cjs b/src/models/simulator/simulator.modeltypes.cjs
new file mode 100644
index 00000000..3d05fdc7
--- /dev/null
+++ b/src/models/simulator/simulator.modeltypes.cjs
@@ -0,0 +1,14 @@
+const Sequelize = require('sequelize');
+
+module.exports = {
+  id: {
+    type: Sequelize.INTEGER,
+    primaryKey: true,
+    autoIncrement: true,
+  },
+  key: {
+    type: Sequelize.STRING,
+    unique: true,
+  },
+  value: Sequelize.STRING,
+};
diff --git a/src/models/simulator/simulator.stub.json b/src/models/simulator/simulator.stub.json
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/src/models/simulator/simulator.stub.json
@@ -0,0 +1 @@
+[]
diff --git a/src/models/staging/staging.model.js b/src/models/staging/staging.model.js
index d2f2dff2..da39d540 100644
--- a/src/models/staging/staging.model.js
+++ b/src/models/staging/staging.model.js
@@ -1,6 +1,9 @@
 'use strict';
+
 import Sequelize from 'sequelize';
 const { Model } = Sequelize;
+import { pushDataLayerChangeList } from '../../fullnode';
+import { Project, Unit, Organization } from '../../models';
 
 import rxjs from 'rxjs';
 import { sequelize } from '../database';
@@ -19,6 +22,147 @@ class Staging extends Model {
     Staging.changes.next(['staging']);
     return super.destroy(values);
   }
+
+  static async pushToDataLayer() {
+    const coBenefitsChangeList = [];
+    const projectLocationChangeList = [];
+    const projectsChangeList = [];
+    const projectRatingChangeList = [];
+    const relatedProjectsChangeList = [];
+    const qualificationsChangeList = [];
+    const unitsChangeList = [];
+    const vintagesChangeList = [];
+
+    const stagedChangeList = await Staging.findAll();
+
+    await Promise.all(
+      stagedChangeList.map(async (stagingRecord) => {
+        const {
+          // eslint-disable-next-line
+          id: stagingRecordId,
+          uuid,
+          table,
+          action,
+          commited,
+          data: rawData,
+        } = stagingRecord;
+        let dataSet = JSON.parse(rawData);
+
+        await Promise.all(
+          dataSet.map(async (data) => {
+            if (table === 'Projects' && !commited) {
+              const [
+                thisProjectChangeList,
+                thisProjectLocationChangeList,
+                thisQualificationsChangeList,
+                thisProjectRatingChangeList,
+                thisCoBenefitsChangeList,
+                thisVintagesChangeList,
+                thisRelatedProjectsChangeList,
+              ] = await Project.generateFullProjectModelChangeListFromStagedRecord(
+                action,
+                uuid,
+                data,
+              );
+
+              if (thisProjectChangeList) {
+                projectsChangeList.push(thisProjectChangeList);
+              }
+
+              if (thisProjectLocationChangeList) {
+                projectLocationChangeList.push(thisProjectLocationChangeList);
+              }
+
+              if (thisQualificationsChangeList) {
+                qualificationsChangeList.push(thisQualificationsChangeList);
+              }
+
+              if (thisProjectRatingChangeList) {
+                projectRatingChangeList.push(thisProjectRatingChangeList);
+              }
+
+              if (thisCoBenefitsChangeList) {
+                coBenefitsChangeList.push(thisCoBenefitsChangeList);
+              }
+
+              if (thisVintagesChangeList) {
+                vintagesChangeList.push(thisVintagesChangeList);
+              }
+
+              if (thisRelatedProjectsChangeList) {
+                relatedProjectsChangeList.push(thisRelatedProjectsChangeList);
+              }
+            } else if (table === 'Units' && !commited) {
+              const [
+                thisUnitsChangeList,
+                thisVintagesChangeList,
+                thisQualificationsChangeList,
+              ] = await Unit.generateUnitModelChangeListFromStagedRecord(
+                action,
+                uuid,
+                data,
+              );
+
+              if (thisUnitsChangeList) {
+                unitsChangeList.push(thisUnitsChangeList);
+              }
+
+              if (thisVintagesChangeList) {
+                vintagesChangeList.push(thisVintagesChangeList);
+              }
+
+              if (thisQualificationsChangeList) {
+                qualificationsChangeList.push(thisQualificationsChangeList);
+              }
+            }
+          }),
+        );
+      }),
+    );
+
+    const {
+      projectLocationStoreId,
+      projectRatingStoreId,
+      coBenefitsStoreId,
+      projectsStoreId,
+      relatedProjectsStoreId,
+      vintagesStoreId,
+      qualificationsStoreId,
+      //  qualificationUnitJunctionStoreId,
+      unitsStoreId,
+    } = await Organization.findOne({
+      where: { isHome: true },
+      raw: true,
+    });
+
+    await Promise.all([
+      coBenefitsChangeList.length &&
+        pushDataLayerChangeList(coBenefitsStoreId, coBenefitsChangeList),
+      projectLocationChangeList.length &&
+        pushDataLayerChangeList(
+          projectLocationStoreId,
+          projectLocationChangeList,
+        ),
+      projectsChangeList.length &&
+        pushDataLayerChangeList(projectsStoreId, projectsChangeList),
+      projectRatingChangeList.length &&
+        pushDataLayerChangeList(projectRatingStoreId, projectRatingChangeList),
+      relatedProjectsChangeList.length &&
+        pushDataLayerChangeList(
+          relatedProjectsStoreId,
+          relatedProjectsChangeList,
+        ),
+      qualificationsChangeList.length &&
+        pushDataLayerChangeList(
+          qualificationsStoreId,
+          qualificationsChangeList,
+        ),
+      unitsChangeList.length &&
+        pushDataLayerChangeList(unitsStoreId, unitsChangeList),
+      vintagesChangeList.length &&
+        pushDataLayerChangeList(vintagesStoreId, vintagesChangeList),
+    ]);
+  }
 }
 
 Staging.init(ModelTypes, {
diff --git a/src/models/units/units.model.js b/src/models/units/units.model.js
index a517028f..f2a63bd7 100644
--- a/src/models/units/units.model.js
+++ b/src/models/units/units.model.js
@@ -1,11 +1,17 @@
 'use strict';
 
+import _ from 'lodash';
 import Sequelize from 'sequelize';
-import {sequelize, safeMirrorDbHandler, sanitizeSqliteFtsQuery} from '../database';
+import rxjs from 'rxjs';
+import {
+  sequelize,
+  safeMirrorDbHandler,
+  sanitizeSqliteFtsQuery,
+} from '../database';
 import { Qualification, Vintage } from '../../models';
 import { UnitMirror } from './units.model.mirror';
 import ModelTypes from './units.modeltypes.cjs';
-import rxjs from 'rxjs';
+import { changeListFactory } from '../../fullnode/data-layer-utils';
 
 const { Model } = Sequelize;
 
@@ -215,20 +221,21 @@ class Unit extends Model {
     if (columns.length) {
       fields = columns.join(', ');
     }
-  
+
     searchStr = sanitizeSqliteFtsQuery(searchStr);
-    
-    if (searchStr === '*') { // * isn't a valid matcher on its own. return empty set
+
+    if (searchStr === '*') {
+      // * isn't a valid matcher on its own. return empty set
       return {
         count: 0,
         rows: [],
-      }
+      };
     }
-    
+
     if (searchStr.startsWith('+')) {
-      searchStr = searchStr.replace('+', '') // If query starts with +, replace it
+      searchStr = searchStr.replace('+', ''); // If query starts with +, replace it
     }
-    
+
     let sql = `SELECT ${fields} FROM units_fts WHERE units_fts MATCH :search`;
 
     if (orgUid) {
@@ -258,6 +265,49 @@ class Unit extends Model {
       }),
     };
   }
+
+  static async generateChangeListFromStagedData(
+    action,
+    warehouseUnitId,
+    stagedData,
+  ) {
+    const foreignKeys = ['qualifications', 'vintage'];
+    if (_.get(stagedData, 'vintage.id')) {
+      stagedData.vintageId = stagedData.vintage.id;
+    }
+
+    return Promise.resolve(
+      changeListFactory(
+        action,
+        warehouseUnitId,
+        _.omit(stagedData, foreignKeys),
+      ),
+    );
+  }
+
+  static generateUnitModelChangeListFromStagedRecord(action, uuid, data) {
+    const promises = [
+      Unit.generateChangeListFromStagedData(action, uuid, data),
+    ];
+
+    if (data.vintage) {
+      promises.push(
+        Vintage.generateChangeListFromStagedData(action, uuid, [data.vintage]),
+      );
+    }
+
+    if (data.qualifications) {
+      promises.push(
+        Qualification.generateChangeListFromStagedData(
+          action,
+          uuid,
+          data.qualifications,
+        ),
+      );
+    }
+
+    return Promise.all(promises);
+  }
 }
 
 Unit.init(Object.assign({}, ModelTypes, virtualFields), {
diff --git a/src/models/vintages/vintages.model.js b/src/models/vintages/vintages.model.js
index 9650f4db..87273407 100644
--- a/src/models/vintages/vintages.model.js
+++ b/src/models/vintages/vintages.model.js
@@ -40,6 +40,17 @@ class Vintage extends Model {
     safeMirrorDbHandler(() => VintageMirror.destroy(values));
     return super.destroy(values);
   }
+
+  static async generateChangeListFromStagedData(
+    // eslint-disable-next-line
+    action,
+    // eslint-disable-next-line
+    id,
+    // eslint-disable-next-line
+    stagedData,
+  ) {
+    return {};
+  }
 }
 
 Vintage.init(ModelTypes, {
diff --git a/src/models/vintages/vintages.stub.json b/src/models/vintages/vintages.stub.json
index 5e418553..abea6a80 100644
--- a/src/models/vintages/vintages.stub.json
+++ b/src/models/vintages/vintages.stub.json
@@ -2,9 +2,9 @@
   {
     "id": "a6745831-5d5e-45ed-b9fe-fd6aa129df25",
     "orgUid": "f1c54511-865e-4611-976c-7c3c1f704662",
-    "startDate": "2021-01-18",
-    "endDate": "2022-01-18",
-    "verificationDate": "2022-01-18 ",
+    "startDate": "2019-02-03 00:05:45.701 +00:00",
+    "endDate": "2029-03-12 00:05:45.701 +00:00",
+    "verificationDate": "2022-01-18 00:05:45.701 +00:00",
     "verificationBody": "This is verified",
     "warehouseProjectId": "81e05bfa-e93f-458f-b907-96bf170e52cd",
     "createdAt": "2022-01-18 00:05:45.701 +00:00",
@@ -13,9 +13,9 @@
   {
     "id": "3d5a8ed2-e5a7-4275-a36e-3456812e39b7",
     "orgUid": "f1c54511-865e-4611-976c-7c3c1f704662",
-    "startDate": "2021-01-18",
-    "endDate": "2022-01-18",
-    "verificationDate": "2022-01-18",
+    "startDate": "2004-03-21 00:05:45.701 +00:00",
+    "endDate": "2005-08-11 00:05:45.701 +00:00",
+    "verificationDate": "2022-01-18 00:05:45.701 +00:00",
     "verificationBody": "This is verified",
     "warehouseProjectId": "81e05bfa-e93f-458f-b907-96bf170e52cd",
     "createdAt": "2022-01-18 00:05:45.701 +00:00",
@@ -24,9 +24,9 @@
   {
     "id": "57c1859d-6aa4-4c57-9dfb-6438e0d4653e",
     "orgUid": "f1c54511-865e-4611-976c-7c3c1f704662",
-    "startDate": "2021-01-18",
-    "endDate": "2022-01-18",
-    "verificationDate": "2022-01-18",
+    "startDate": "2012-01-18 00:05:45.701 +00:00",
+    "endDate": "2013-06-22 00:05:45.701 +00:00",
+    "verificationDate": "2022-01-18 00:05:45.701 +00:00",
     "verificationBody": "This is verified",
     "warehouseProjectId": "81e05bfa-e93f-458f-b907-96bf170e52cd",
     "createdAt": "2022-01-18 00:05:45.701 +00:00",
@@ -35,20 +35,20 @@
   {
     "id": "74887b22-da3b-4c2b-b945-670319193cdd",
     "orgUid": "f1c54511-865e-4611-976c-7c3c1f704662",
-    "startDate": "2021-01-18",
-    "endDate": "2022-01-18",
-    "verificationDate": "2022-01-18",
+    "startDate": "2014-02-18 00:05:45.701 +00:00",
+    "endDate": "2016-04-18 00:05:45.701 +00:00",
+    "verificationDate": "2022-01-18 00:05:45.701 +00:00",
     "verificationBody": "This is verified",
     "warehouseProjectId": "81e05bfa-e93f-458f-b907-96bf170e52cd",
-    "createdAt": "2022-01-18 00:05:45.701 +00:00",
-    "updatedAt": "2022-01-18 00:05:45.701 +00:00"
+    "createdAt": "2022-02-18 00:05:45.701 +00:00",
+    "updatedAt": "2022-08-18 00:05:45.701 +00:00"
   },
   {
     "id": "7f7f23a5-3e1a-43b4-82d8-b4156b158f88",
     "orgUid": "f1c54511-865e-4611-976c-7c3c1f704662",
-    "startDate": "2021-01-18",
-    "endDate": "2022-01-18",
-    "verificationDate": "2022-01-18",
+    "startDate": "2020-05-18 00:05:45.701 +00:00",
+    "endDate": "2021-03-22 00:05:45.701 +00:00",
+    "verificationDate": "2022-01-18 00:05:45.701 +00:00",
     "verificationBody": "This is verified",
     "warehouseProjectId": "81e05bfa-e93f-458f-b907-96bf170e52cd",
     "createdAt": "2022-01-18 00:05:45.701 +00:00",
diff --git a/src/routes/v1/resources/organization.js b/src/routes/v1/resources/organization.js
index 568112ca..3da03c91 100644
--- a/src/routes/v1/resources/organization.js
+++ b/src/routes/v1/resources/organization.js
@@ -1,17 +1,28 @@
 'use strict';
 
 import express from 'express';
+import joiExpress from 'express-joi-validation';
 
 import { OrganizationController } from '../../../controllers';
+import { newOrganizationSchema } from '../../../validations';
 
-const OrganizationRouter = express.Router({ passError: true });
+const validator = joiExpress.createValidator({ passError: true });
+const OrganizationRouter = express.Router();
 
 OrganizationRouter.get('/', (req, res) => {
   return OrganizationController.findAll(req, res);
 });
 
-OrganizationRouter.post('/', (req, res) => {
-  return OrganizationController.create(req, res);
+OrganizationRouter.post(
+  '/',
+  validator.body(newOrganizationSchema),
+  (req, res) => {
+    return OrganizationController.create(req, res);
+  },
+);
+
+OrganizationRouter.put('/', (req, res) => {
+  return OrganizationController.importOrg(req, res);
 });
 
 export { OrganizationRouter };
diff --git a/src/routes/v1/resources/staging.js b/src/routes/v1/resources/staging.js
index 154df971..29d04581 100644
--- a/src/routes/v1/resources/staging.js
+++ b/src/routes/v1/resources/staging.js
@@ -19,7 +19,7 @@ StagingRouter.delete(
   StagingController.destroy,
 );
 
-StagingRouter.post('/commit', StagingController.commitV2);
+StagingRouter.post('/commit', StagingController.commit);
 
 // Empty entire stagin table
 StagingRouter.delete('/clean', StagingController.clean);
diff --git a/src/server.js b/src/server.js
index d098b1f6..4a515352 100644
--- a/src/server.js
+++ b/src/server.js
@@ -5,6 +5,7 @@ import http from 'http';
 import { Server } from 'socket.io';
 import Debug from 'debug';
 import { connection } from './websocket';
+import { startDataLayerUpdatePolling } from './fullnode';
 
 const debug = Debug('climate-warehouse:server');
 
@@ -50,4 +51,6 @@ function onListening() {
   debug('Listening on ' + bind);
 }
 
+startDataLayerUpdatePolling();
+
 export default rootRouter;
diff --git a/src/validations/index.js b/src/validations/index.js
index fbaab0eb..6b4a86ae 100644
--- a/src/validations/index.js
+++ b/src/validations/index.js
@@ -1,3 +1,4 @@
 export * from './units.validations';
 export * from './staging.validations';
 export * from './projects.validations';
+export * from './organizations.validations';
diff --git a/src/validations/organizations.validations.js b/src/validations/organizations.validations.js
new file mode 100644
index 00000000..d1a1a236
--- /dev/null
+++ b/src/validations/organizations.validations.js
@@ -0,0 +1,10 @@
+import Joi from 'joi';
+
+export const newOrganizationSchema = Joi.object({
+  name: Joi.string().required(),
+  icon: Joi.string().required(),
+});
+
+export const importOrganizationSchema = Joi.object({
+  orgUid: Joi.string().required(),
+});