Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: import organization assertion loop #1232

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions docs/cadt_rpc_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ If using a `CADT_API_KEY` append `--header 'x-api-key: <your-api-key-here>'` to
- [POST Examples](#post-examples)
- [Create an organization](#create-an-organization)
- [PUT Examples](#put-examples)
- [Import a home organization](#import-a-home-organization-that-datalayer-is-subscribed-to)
- [Import an organization that datalayer is subscribed to](#import-an-organization-that-datalayer-is-subscribed-to)
- [DELETE Examples](#delete-examples)
- [Delete a home organization](#reset-home-organization)
- [Delete a home organization](#delete-home-organization)
- [Additional organizations resources](#additional-organizations-resources)
- [`projects`](#projects)
- [GET Examples](#get-examples-1)
Expand Down Expand Up @@ -210,16 +210,18 @@ Response

PUT Options:

| Key | Type | Description |
|:------:|:-------:|:----------------------------------------------------:|
| orgUid | String | (Required) OrgUid of the home organization to import |
| Key | Type | Description |
|:------:|:-------:|:-----------------------------------------------:|
| orgUid | String | (Required) OrgUid of the organization to import |
| isHome | Boolean | Set to true if home organization |

### PUT Examples

#### Import a home organization that datalayer is subscribed to
#### Import an organization that datalayer is subscribed to

- This is typically used when an organization currently using CADT is installing a new instance and wants to use the same
home organization and the current instance(s).
- This can be used to import an organization that is not a home organization as well

Request
```sh
Expand All @@ -232,7 +234,8 @@ curl --location -g --request PUT 'http://localhost:31310/v1/organizations/' \
Response
```json
{
"message":"Importing home organization."
"message":"Successfully imported organization. cadt will begin syncing the organization's data from datalayer",
"success": true
}
```

Expand All @@ -259,8 +262,7 @@ Response
- POST `/organizations/remove-mirror` - given a store ID and coin ID removes the mirror for a given store
- POST `/organizations/sync` - runs the process to sync all subscribed organization metadata with datalayer
- POST `/organizations/create` - create an organization without an icon
- POST `/organizations/edit` - update an organization name and/or icon
- PUT `/organizations/import` - subscribe and import an organization via OrgUid
- POST `/organizations/edit` - update an organization name and/or icon
- DELETE `organizations/import` - delete an organization's record from the CADT instance DB
- PUT `organizations/subscribe` - subscribe to an organization datalayer singleton
- DELETE `organizations/unsubscribe` - unsubscribe from an organization datalayer singleton and keep CADT data
Expand Down
41 changes: 9 additions & 32 deletions src/controllers/organization.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,45 +193,19 @@ export const resetHomeOrg = async (req, res) => {
}
};

export const importOrg = async (req, res) => {
export const importOrganization = async (req, res) => {
try {
await assertIfReadOnlyMode();
await assertWalletIsSynced();

const { orgUid } = req.body;
await Organization.importOrganization(req.body.orgUid, req.body?.orgUid);

res.json({
res.status(200).json({
message:
'Importing and subscribing organization this can take a few mins.',
"Successfully imported organization. cadt will begin syncing the organization's data from datalayer",
success: true,
});

return Organization.importOrganization(orgUid);
} catch (error) {
console.trace(error);
res.status(400).json({
message: 'Error importing organization',
error: error.message,
success: false,
});
}
};

export const importHomeOrg = async (req, res) => {
try {
await assertIfReadOnlyMode();
await assertWalletIsSynced();

const { orgUid } = req.body;

await Organization.importHomeOrg(orgUid);

res.json({
message: 'Importing home organization.',
success: true,
});
} catch (error) {
console.trace(error);
res.status(400).json({
message: 'Error importing organization',
error: error.message,
Expand All @@ -246,10 +220,13 @@ export const subscribeToOrganization = async (req, res) => {
await assertWalletIsSynced();
await assertHomeOrgExists();

await Organization.subscribeToOrganization(req.body.orgUid);
const resultMessage = await Organization.subscribeToOrganization(
req.body.orgUid,
req.body.registryId,
);

return res.json({
message: 'Subscribed to organization',
message: resultMessage,
success: true,
});
} catch (error) {
Expand Down
20 changes: 15 additions & 5 deletions src/datalayer/syncService.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ const subscribeToStoreOnDataLayer = async (storeId) => {
}
};

/** note that although this function will succeed in subscribing to the store, datalayer will not sync data fast enough
* for the data to be available to the calling function when this function returns
* @param storeId
* @returns {Promise<*>}
*/
const getSubscribedStoreData = async (storeId) => {
const { storeIds: subscriptions } = await dataLayer.getSubscriptions(storeId);
const { storeIds: subscriptions } = await dataLayer.getSubscriptions();
const alreadySubscribed = subscriptions.includes(storeId);

if (!alreadySubscribed) {
Expand Down Expand Up @@ -142,11 +147,16 @@ const getCurrentStoreData = async (storeId) => {
}

const encodedData = await dataLayer.getStoreData(storeId);
if (encodedData) {
return decodeDataLayerResponse(encodedData);
} else {
return [];

if (_.isEmpty(encodedData?.keys_values)) {
throw new Error(`No data found for store ${storeId}`);
}

const decodedData = decodeDataLayerResponse(encodedData);
return decodedData.reduce((obj, current) => {
obj[current.key] = current.value;
return obj;
}, {});
};

/**
Expand Down
172 changes: 114 additions & 58 deletions src/models/organizations/organizations.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import { getConfig } from '../../utils/config-loader';
const { USE_SIMULATOR, AUTO_SUBSCRIBE_FILESTORE } = getConfig().APP;

import ModelTypes from './organizations.modeltypes.cjs';
import {
getOwnedStores,
getSubscriptions,
} from '../../datalayer/persistance.js';
import { subscribeToStoreOnDataLayer } from '../../datalayer/simulator.js';

class Organization extends Model {
static async getHomeOrg(includeAddress = true) {
Expand Down Expand Up @@ -225,83 +230,83 @@ class Organization extends Model {
await datalayer.addMirror(storeId, url, force);
}

static async importHomeOrg(orgUid) {
const orgData = await datalayer.getLocalStoreData(orgUid);

if (!orgData) {
throw new Error('Your node does not have write access to this orgUid');
}

const orgDataObj = orgData.reduce((obj, curr) => {
obj[curr.key] = curr.value;
return obj;
}, {});
static async importOrganization(orgUid, isHome) {
try {
const homeOrgExists = Organization.getHomeOrg();
if (isHome && homeOrgExists) {
throw new Error(
'cannot import home organization. home organization already exists on this instance',
);
}

const registryData = await datalayer.getLocalStoreData(
orgDataObj.registryId,
);
logger.info(`Importing ${isHome ? 'home' : ''} organization ${orgUid}`);
logger.debug(
isHome
? `checking that datalayer owns org store ${orgUid}`
: `checking that datalayer is subscribed to org store ${orgUid}`,
);

const registryDataObj = registryData.reduce((obj, curr) => {
obj[curr.key] = curr.value;
return obj;
}, {});
const datalayerStoresResult = isHome
? await getOwnedStores()
: await getSubscriptions();

const dataModelVersion = getDataModelVersion();
if (!datalayerStoresResult.success) {
throw new Error(
isHome
? 'failed to retrieve owned stores from datalayer'
: 'failed to retrieve store subscriptions from datalayer',
);
}

if (!registryDataObj[dataModelVersion]) {
registryDataObj[dataModelVersion] = await Organization.appendNewRegistry(
orgDataObj.registryId,
dataModelVersion,
);
}
if (!datalayerStoresResult?.storeIds.includes(orgUid)) {
throw new Error(
isHome
? `your chia instance does not own store ${orgUid}. cannot import as home organization.`
: `datalayer is not subscribed to store ${orgUid}. please subscribe to this store before importing the organization`,
);
}

await Organization.upsert({
orgUid,
name: orgDataObj.name,
icon: orgDataObj.icon,
registryId: registryDataObj[dataModelVersion],
subscribed: true,
isHome: true,
});
}
logger.info(`found orgUid store. attempting to import registry store`);

static async importOrganization(orgUid) {
try {
logger.info('Importing organization ' + orgUid);
const orgData = await datalayer.getSubscribedStoreData(orgUid);
const orgData = await datalayer.getCurrentStoreData(orgUid);
const registryId = orgData?.registryId;

if (!orgData.registryId) {
logger.error(
'Corrupted organization, no registryId on the datalayer, can not import',
if (!registryId) {
throw new Error(
`store ${orgUid} does not contain a valid registry storeId, can not import`,
);
return;
}

logger.info(`IMPORTING REGISTRY: ${orgData.registryId}`);
if (!datalayerStoresResult?.storeIds.includes(registryId)) {
throw new Error(
isHome
? `your chia instance does not own store ${registryId} belonging to organization store ${orgUid}. cannot import as home organization.`
: `datalayer is NOT subscribed to registry store ${registryId} belonging to organization store ${orgUid}. please subscribe to this registry store before importing the organization`,
);
}

const registryData = await datalayer.getSubscribedStoreData(
orgData.registryId,
);
logger.debug(`getting registry data from registry store ${registryId}`);
const registryData = await datalayer.getCurrentStoreData(orgUid);

const dataModelVersion = getDataModelVersion();

if (!registryData[dataModelVersion]) {
throw new Error(
`Organization has no registry for the ${dataModelVersion} datamodel, can not import`,
`organization ${orgUid} has no registry for the ${dataModelVersion} datamodel, can not import`,
);
}

logger.info(`IMPORTING REGISTRY ${dataModelVersion}: `, registryData.v1);

await datalayer.subscribeToStoreOnDataLayer(registryData.v1);
logger.info(
`importing registry data from store ${registryId} (datamodel version ${dataModelVersion}) for organization ${orgUid}`,
);

logger.info({
logger.debug('upserting the following imported organization data', {
orgUid,
name: orgData.name,
icon: orgData.icon,
registryId: registryData[dataModelVersion],
subscribed: true,
isHome: false,
isHome,
});

await Organization.upsert({
Expand All @@ -310,24 +315,75 @@ class Organization extends Model {
icon: orgData.icon,
registryId: registryData[dataModelVersion],
subscribed: true,
isHome: false,
isHome,
});

if (AUTO_SUBSCRIBE_FILESTORE) {
if (AUTO_SUBSCRIBE_FILESTORE && !isHome) {
await FileStore.subscribeToFileStore(orgUid);
}
} catch (error) {
logger.info(error.message);
// catch for logging purposes. need to re-throw to controller
logger.error(`cannot import organization. Error: ${error.message}`);
throw error;
}
}

static async subscribeToOrganization(orgUid) {
static async subscribeToOrganization(orgUid, registryId) {
const subscribedStores = await getSubscriptions();
if (!subscribedStores.success) {
throw new Error('failed to contact datalayer');
}

const subscribedToOrgStore = subscribedStores.storeIds.includes(orgUid);
const subscribedToRegistryStore =
subscribedStores.storeIds.includes(registryId);

if (!subscribedToOrgStore) {
logger.info(
`datalayer is not subscribed to orgUid store ${orgUid}, subscribing ...`,
);

const result = await subscribeToStoreOnDataLayer(orgUid, true);
if (result) {
logger.info(`subscribed to store ${orgUid}`);
} else {
const error = `failed to subscribe to store ${orgUid}`;
logger.error(error);
throw new Error(error);
}

// wait 5 secs to give RPC a break
await new Promise((resolve) => setTimeout(resolve, 5000));
}

if (!subscribedToRegistryStore) {
logger.info(
`datalayer is not subscribed to registryId store ${registryId}, subscribing ...`,
);

const result = await subscribeToStoreOnDataLayer(registryId, true);
if (result) {
logger.info(`subscribed to store ${registryId}`);
} else {
const error = `failed to subscribe to store ${registryId}`;
logger.error(error);
throw new Error(error);
}

// wait 5 secs to give RPC a break
await new Promise((resolve) => setTimeout(resolve, 5000));
}

const exists = await Organization.findOne({ where: { orgUid } });
if (exists) {
await Organization.update({ subscribed: true }, { where: { orgUid } });
return `successfully subscribed to organization ${orgUid} and its registry store ${registryId}`;
} else {
throw new Error(
'Can not subscribe, please import this organization first',
return (
`successfully subscribed to organization store ${orgUid} the associated registry store ${registryId}. ` +
`this organization does not exist in your cadt instance database. you will need to import the organization for ` +
`cadt to begin syncing the organization's data. please allow a few minutes for datalayer to sync the store data ` +
`before attempting to import.`
);
}
}
Expand Down
Loading