const { Controller } = require('libflitter') const FakeRequest = require('../../../FakeRequest') class OfflineController extends Controller { static get services() { return [...super.services, 'models', 'controllers', 'app'] } async do_sync(req, res, next) { // TODO account for modify date to not overwrite more recent data!! const OfflineDataSync = this.models.get('api:OfflineDataSync') const record = await OfflineDataSync.from_request(req) console.log('sync data', record) const return_maps = {} // pages if ( Array.isArray(record.pages) ) { return_maps.pages = await this.do_sync_pages(req, record.pages) } // pageNodes if ( Array.isArray(record.pageNodes) ) { return_maps.pageNodes = await this.do_sync_page_nodes(req, record.pageNodes, record.pages) } // codiums if ( Array.isArray(record.codiums) ) { return_maps.codiums = await this.do_sync_codiums(req, record.codiums) } // databases // databaseColumns // databaseEntries // fileGroups return res.api(return_maps) } async do_sync_codiums(req, codium_recs) { const FormCodeController = this.controllers.get('api:v1:FormCode') const Codium = this.models.get('api:Codium') const uuid_mapping = {} for ( const rec of codium_recs ) { const existing_code = await Codium.findOne({ UUID: rec.UUID }) const fake_req = this.app.di().make(FakeRequest) await fake_req.inflate(req) fake_req.params = { PageId: rec.PageId, NodeId: rec.NodeId, CodiumId: rec.UUID } fake_req.body = rec if ( !existing_code && !rec.deleted ) { // This was created on the client side await FormCodeController.create_new(fake_req, fake_req.response) uuid_mapping[rec.UUID] = fake_req.response?._api_data?.UUID } else { if ( rec.deleted ) { // The code was deleted await FormCodeController.drop_code(fake_req, fake_req.response) uuid_mapping[rec.UUID] = false } else { // The code was updated await FormCodeController.set_values(fake_req, fake_req.response) uuid_mapping[rec.UUID] = fake_req.response?._api_data?.UUID } } } return uuid_mapping } async do_sync_page_nodes(req, page_node_recs, page_recs) { const PageController = this.controllers.get('api:v1:Page') const PageModel = this.models.get('api:Page') const NodeModel = this.models.get('api:Node') const page_id_x_page = {} for ( const page of page_recs ) { page_id_x_page[page.UUID] = page } const uuid_mapping = {} for ( const rec of page_node_recs ) { rec.Value = rec.ValueJSON ? JSON.parse(rec.ValueJSON) : {} const existing_node = await NodeModel.findOne({ UUID: rec.UUID }) const offline_page = page_id_x_page[rec.PageId] let online_page = await PageModel.findOne({ UUID: rec.PageId }) if ( existing_node && rec.deleted ) { // node that exists on the server was deleted if ( online_page ) { // if it existed in the online page, delete it online_page.NodeIds = online_page.NodeIds.filter(x => x !== rec.UUID) await online_page.save() } await existing_node.delete() uuid_mapping[rec.UUID] = false } else if ( existing_node ) { // if the node exists, we assume it's already in a page structure // update the server-side record if the user can access it if ( await online_page.is_accessible_by(req.user, 'edit') ) { existing_node.Type = rec.Type existing_node.Value = rec.Value existing_node.UpdatedAt = new Date(rec.UpdatedAt) existing_node.UpdateUserId = req.user.id await existing_node.save() uuid_mapping[rec.UUID] = existing_node.UUID } } else if ( !existing_node && online_page && !rec.deleted ) { // the node was created offline // first, save the node to the page const fake_req = this.app.di().make(FakeRequest) await fake_req.inflate(req) fake_req.params = { PageId: online_page.UUID } fake_req.body = { nodeData: {...rec} } await PageController.save_node_to_page(fake_req, fake_req.response) const new_uuid = uuid_mapping[rec.UUID] = fake_req.response?._api_data?.UUID online_page = await PageModel.findOne({ UUID: rec.PageId }) // to refresh the model's data // now, try to place the node in the correct spot // if first in offline page, make first in online page if ( Array.isArray(offline_page?.NodeIds) && offline_page.NodeIds[0] === rec.UUID ) { online_page.NodeIds = [new_uuid, ...online_page.NodeIds.filter(x => x !== new_uuid && x !== rec.UUID)] } else if ( Array.isArray(offline_page?.NodeIds) && offline_page.NodeIds.includes(rec.UUID) ) { if ( offline_page.NodeIds.slice(-1)[0] !== rec.UUID ) { // We're not first, and we're not last, so try to place in the correct spot const index = offline_page.NodeIds.findIndex(x => x.UUID === rec.UUID) let predecessor = undefined let index_diff = 1 while ( !predecessor && (index - index_diff) >= 0 ) { const maybe_predecessor = offline_page.NodeIds[index - index_diff] if ( online_page.NodeIds.includes(maybe_predecessor) ) { predecessor = maybe_predecessor } else { index_diff += 1 } } if ( predecessor ) { // We found the predecessor to insert the child in the existing node const newNodeIds = [] online_page.NodeIds.forEach(uuid => { newNodeIds.push(uuid) if ( uuid === predecessor ) { newNodeIds.push(new_uuid) } }) online_page.NodeIds = newNodeIds } } } await online_page.save() } // assuming the pages were created first, we should never have a case // where we have !existing_node && !online_page } return uuid_mapping } async do_sync_pages(req, page_recs) { // TODO order pages by create date to prevent child-before-parent errors const PageController = this.controllers.get('api:v1:Page') const PageModel = this.models.get('api:Page') const uuid_mapping = {} for ( const rec of page_recs ) { const UUID = rec.UUID if ( !UUID ) continue; const existing_page = await PageModel.findOne({ UUID }) const fake_req = this.app.di().make(FakeRequest) await fake_req.inflate(req) fake_req.body = {...rec} if ( !existing_page && !rec.deleted ) { // create the new page if ( parseInt(rec.ParentId) === 0 ) { // Create a new top-level page fake_req.body.omit_starter = true fake_req.body.name = rec.Name await PageController.create_top_level(fake_req, fake_req.response) } else { // Create a new child page fake_req.body.omit_starter = true fake_req.body.name = rec.Name fake_req.body.parentId = rec.ParentId await PageController.create_child(fake_req, fake_req.response) } } else if ( existing_page ) { fake_req.params = { PageId: rec.UUID } if ( rec.deleted ) { // The page was deleted on the client await PageController.delete_page(fake_req, fake_req.response) } else { // update an existing page await PageController.save_page(fake_req, fake_req.response) } } uuid_mapping[rec.UUID] = fake_req.response?._api_data?.UUID || false } return uuid_mapping } } module.exports = exports = OfflineController