Skip to content

Commit

Permalink
fix(EMS-3524): no pdf - data migration - account, buyer relationships (
Browse files Browse the repository at this point in the history
…#2650)

* fix(EMS-3524): no pdf - data migration - account status relationships

* fix(EMS-3524): no pdf - data migration - buyer relationships

* fix(EMS-3524): update README.md

* fix(EMS-3524): minor code improvements
  • Loading branch information
ttbarnes authored Jun 28, 2024
1 parent 13aab3e commit 6022563
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 165 deletions.
2 changes: 2 additions & 0 deletions src/api/data-migration/version-1-to-version-2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ To set up and run the API locally, you'll need the following prerequisites:
- The `DATABASE_URL` environment variable should be configured to point to your local MySQL database, for example: `mysql://root:@localhost:1234/db-name`.
- The local `NODE_ENV` environment variable set to `migration`.
- The local `DATABASE_USER` environment variable set to the database's user.
- The local `DATABASE_PASSWORD` environment variable set to the database's password.
- `mysql2` NPM package installed as an API dependency.
- `ts-node` NPM package installed locally.

## Running Locally :computer:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const connectToDatabase = async () => {
const connection = (await mysql.createConnection({
host: '127.0.0.1',
user: process.env.DATABASE_USER,
database: 'exip-migration',
password: process.env.DATABASE_PASSWORD,
database: 'exip',
port: Number(process.env.DATABASE_PORT),
})) as Connection;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Context } from '.keystone/types'; // eslint-disable-line
import { format } from 'date-fns';
import { Connection } from 'mysql2/promise';
import getAllAccounts from '../get-all-accounts';
import { AccountMvp, AccountStatus } from '../../../types';
import executeSqlQuery from '../execute-sql-query';
import { AccountMvp } from '../../../types';

/**
* createNewAccountStatusRelationships
Expand All @@ -10,45 +11,27 @@ import { AccountMvp, AccountStatus } from '../../../types';
* 2) Create an array of "account status" data - using isVerified and isBlocked from the original accounts data.
* 3) Create new "account status" entries.
* @param {Connection} connection: SQL database connection
* @param {Context} context: KeystoneJS context API
* @returns {Promise<Array<AccountStatus>>} Account status entires
* @returns {Promise<Boolean>}
*/
const createNewAccountStatusRelationships = async (connection: Connection, context: Context): Promise<Array<AccountStatus>> => {
const createNewAccountStatusRelationships = async (connection: Connection): Promise<boolean> => {
const loggingMessage = 'Creating new status relationships for all accounts';

console.info(`✅ ${loggingMessage}`);

try {
const accounts = await getAllAccounts(connection);

const mappedAccountStatusData = accounts.map((account: AccountMvp) => {
const mapped = {
account: {
connect: {
id: account.id,
},
},
/**
* NOTE: The accounts data we receive is raw database data.
* In the database, boolean fields are TINYINT/integer values.
* The KeystoneJS context/GraphQL API expects these fields to be booleans.
* Therefore, since the TINYINT values will be 0 or 1,
* we can safely transform these fields to have a boolean value.
* KeystoneJS will then automatically handle saving in the database as a TINYINT
*/
isVerified: Boolean(account.isVerified),
isBlocked: Boolean(account.isBlocked),
updatedAt: account.updatedAt,
};

return mapped;
});

const created = (await context.db.AccountStatus.createMany({
data: mappedAccountStatusData,
})) as Array<AccountStatus>;

return created;
const accountStatusData = accounts.map(
(account: AccountMvp) => `('${account.id}', ${account.isBlocked}, ${account.isVerified}, '${format(account.updatedAt, 'yyyy-MM-dd HH:mm')}')`,
);

const query = `
INSERT INTO AccountStatus (id, isBlocked, isVerified, updatedAt) VALUES ${accountStatusData};
`;

await executeSqlQuery({ connection, query, loggingMessage });

return true;
} catch (err) {
console.error(`🚨 error ${loggingMessage} %O`, err);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Connection } from 'mysql2/promise';
import executeSqlQuery from './execute-sql-query';

/**
* getAllBuyerContacts
* Get all buyer contacts in the "BuyerContact" table.
* @param {Connection} connection: SQL database connection
* @returns {Promise<Array<object>>} executeSqlQuery response
*/
const getAllBuyerContacts = async (connection: Connection) => {
const loggingMessage = 'Getting all buyer contacts';

const query = 'SELECT * FROM BuyerContact';

const [contacts] = await executeSqlQuery({ connection, query, loggingMessage });

return contacts;
};

export default getAllBuyerContacts;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Connection } from 'mysql2/promise';
import executeSqlQuery from './execute-sql-query';

/**
* getAllBuyerRelationships
* Get all buyer relationships in the "BuyerRelationship" table.
* @param {Connection} connection: SQL database connection
* @returns {Promise<Array<object>>} executeSqlQuery response
*/
const getAllBuyerRelationships = async (connection: Connection) => {
const loggingMessage = 'Getting all buyer relationships';

const query = 'SELECT * FROM BuyerRelationship';

const [relationships] = await executeSqlQuery({ connection, query, loggingMessage });

return relationships;
};

export default getAllBuyerRelationships;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Connection } from 'mysql2/promise';
import executeSqlQuery from './execute-sql-query';

/**
* getAllBuyerTradingHistories
* Get all buyer trading history in the "BuyerTradingHistory" table.
* @param {Connection} connection: SQL database connection
* @returns {Promise<Array<object>>} executeSqlQuery response
*/
const getAllBuyerTradingHistories = async (connection: Connection) => {
const loggingMessage = 'Getting all buyer trading histories';

const query = 'SELECT * FROM BuyerTradingHistory';

const [tradingHistories] = await executeSqlQuery({ connection, query, loggingMessage });

return tradingHistories;
};

export default getAllBuyerTradingHistories;
9 changes: 6 additions & 3 deletions src/api/data-migration/version-1-to-version-2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import updateApplications from './update-applications';
import createNewAccountStatusRelationships from './create-new-account-status-relationships';
import removeAccountStatusFields from './update-accounts/remove-account-status-fields';
import createNewApplicationRelationships from './create-new-application-relationships';
import getAllBuyers from './get-all-buyers';
import updateBuyers from './update-buyers';
import getKeystoneContext from '../../test-helpers/get-keystone-context';

Expand Down Expand Up @@ -36,14 +37,16 @@ const dataMigration = async () => {

console.info('✅ Obtained keystone context. Executing additional queries');

await createNewAccountStatusRelationships(connection, context);
await createNewAccountStatusRelationships(connection);

await removeAccountStatusFields(connection);
const buyers = await getAllBuyers(connection);

await updateBuyers(connection, context);
await updateBuyers(connection, buyers);

await createNewApplicationRelationships(context);

await removeAccountStatusFields(connection);

console.info('🎉 Migration complete. Exiting script');

process.exit();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,33 @@
import { Context } from '.keystone/types'; // eslint-disable-line
import crypto from 'crypto';
import { Connection } from 'mysql2/promise';
import executeSqlQuery from '../execute-sql-query';
import { ApplicationBuyerMvp } from '../../../types';

/**
* moveBuyerContactFields
* Move MVP "buyers" fields into the new "No PDF" data model/structure.
* NOTE: The buyer data we receive is raw database data.
* In the database, boolean fields are TINYINT/integer values.
* The KeystoneJS context/GraphQL API expects these fields to be booleans.
* Therefore, since the TINYINT values will be 0 or 1,
* we can safely transform any TINYINT fields to have a boolean value.
* KeystoneJS will then automatically handle saving in the database as a TINYINT
* @param {Array<ApplicationBuyerMvp>} context: KeystoneJS context API
* @param {Context} context: KeystoneJS context API
* @returns {Promise<Array<ApplicationBuyer>>} Updated buyers
* @param {Array<ApplicationBuyerMvp>} buyers: Buyers
* @param {Connection} connection: SQL database connection
* @returns {Promise<Boolean>}
*/
const moveBuyerContactFields = async (buyers: Array<ApplicationBuyerMvp>, context: Context) => {
const mappedBuyerContactData = buyers.map((buyer: ApplicationBuyerMvp) => {
const mapped = {
application: {
connect: {
id: buyer.application,
},
},
canContactBuyer: Boolean(buyer.canContactBuyer),
contactEmail: buyer.contactEmail,
contactFirstName: buyer.contactFirstName,
contactLastName: buyer.contactLastName,
contactPosition: buyer.contactPosition,
};

return mapped;
});
const moveBuyerContactFields = async (buyers: Array<ApplicationBuyerMvp>, connection: Connection): Promise<Array<object>> => {
console.info('✅ Moving buyer contact relationships for all buyers');

const buyerContactValues = buyers.map((buyer: ApplicationBuyerMvp) => {
const { application, canContactBuyer, contactEmail, contactFirstName, contactLastName, contactPosition } = buyer;

const created = await context.db.BuyerContact.createMany({
data: mappedBuyerContactData,
return `('${crypto.randomUUID()}', '${application}', ${canContactBuyer}, '${contactEmail}', '${contactFirstName}', '${contactLastName}', '${contactPosition}')`;
});

return created;
const loggingMessage = 'Creating new buyer contact relationships for all buyers';

const query = `
INSERT INTO BuyerContact (id, application, canContactBuyer, contactEmail, contactFirstName, contactLastName, contactPosition) VALUES ${buyerContactValues};
`;

const updated = executeSqlQuery({ connection, query, loggingMessage });

return updated;
};

export default moveBuyerContactFields;
Original file line number Diff line number Diff line change
@@ -1,38 +1,33 @@
import { Context } from '.keystone/types'; // eslint-disable-line
import crypto from 'crypto';
import { Connection } from 'mysql2/promise';
import executeSqlQuery from '../execute-sql-query';
import { ApplicationBuyerMvp } from '../../../types';

/**
* moveBuyerRelationshipFields
* Move MVP "buyer relationships" fields into the new "No PDF" data model/structure.
* NOTE: The buyer data we receive is raw database data.
* In the database, boolean fields are TINYINT/integer values.
* The KeystoneJS context/GraphQL API expects these fields to be booleans.
* Therefore, since the TINYINT values will be 0 or 1,
* we can safely transform any TINYINT fields to have a boolean value.
* KeystoneJS will then automatically handle saving in the database as a TINYINT
* @param {Array<ApplicationBuyerMvp>} context: KeystoneJS context API
* @param {Context} context: KeystoneJS context API
* @param {Array<ApplicationBuyerMvp>} buyers: Buyers
* @param {Connection} connection: SQL database connection
* @returns {Promise<Array<ApplicationBuyer>>} Updated buyers
*/
const moveBuyerRelationshipFields = async (buyers: Array<ApplicationBuyerMvp>, context: Context) => {
const mappedBuyerRelationshipData = buyers.map((buyer: ApplicationBuyerMvp) => {
const mapped = {
application: {
connect: {
id: buyer.application,
},
},
exporterIsConnectedWithBuyer: Boolean(buyer.exporterIsConnectedWithBuyer),
};

return mapped;
});
const moveBuyerRelationshipFields = async (buyers: Array<ApplicationBuyerMvp>, connection: Connection): Promise<Array<object>> => {
console.info('✅ Moving buyer relationship for all buyers');

const buyerRelationshipValues = buyers.map((buyer: ApplicationBuyerMvp) => {
const { application, exporterIsConnectedWithBuyer } = buyer;

const created = await context.db.BuyerRelationship.createMany({
data: mappedBuyerRelationshipData,
return `('${crypto.randomUUID()}', '${application}', ${exporterIsConnectedWithBuyer})`;
});

return created;
const loggingMessage = 'Creating new buyer relationship for all buyers';

const query = `
INSERT INTO BuyerRelationship (id, application, exporterIsConnectedWithBuyer) VALUES ${buyerRelationshipValues};
`;

const updated = await executeSqlQuery({ connection, query, loggingMessage });

return updated;
};

export default moveBuyerRelationshipFields;
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Context } from '.keystone/types'; // eslint-disable-line
import crypto from 'crypto';
import { Connection } from 'mysql2/promise';
import executeSqlQuery from '../execute-sql-query';
import { ApplicationBuyerMvp } from '../../../types';

/**
Expand All @@ -10,29 +12,28 @@ import { ApplicationBuyerMvp } from '../../../types';
* Therefore, since the TINYINT values will be 0 or 1,
* we can safely transform any TINYINT fields to have a boolean value.
* KeystoneJS will then automatically handle saving in the database as a TINYINT
* @param {Array<ApplicationBuyerMvp>} context: KeystoneJS context API
* @param {Context} context: KeystoneJS context API
* @param {Array<ApplicationBuyerMvp>} buyers: Buyers
* @param {Connection} connection: SQL database connection
* @returns {Promise<Array<ApplicationBuyer>>} Updated buyers
*/
const moveBuyerTradingHistoryFields = async (buyers: Array<ApplicationBuyerMvp>, context: Context) => {
const mappedBuyerTradingHistoryData = buyers.map((buyer: ApplicationBuyerMvp) => {
const mapped = {
application: {
connect: {
id: buyer.application,
},
},
exporterHasTradedWithBuyer: Boolean(buyer.exporterHasTradedWithBuyer),
};

return mapped;
});
const moveBuyerTradingHistoryFields = async (buyers: Array<ApplicationBuyerMvp>, connection: Connection): Promise<Array<object>> => {
console.info('✅ Moving buyer trading history relationships for all buyers');

const buyerTradingHistoryValues = buyers.map((buyer: ApplicationBuyerMvp) => {
const { application, exporterHasTradedWithBuyer } = buyer;

const created = await context.db.BuyerTradingHistory.createMany({
data: mappedBuyerTradingHistoryData,
return `('${crypto.randomUUID()}', '${application}', ${exporterHasTradedWithBuyer})`;
});

return created;
const loggingMessage = 'Creating new buyer trading history relationships for all buyers';

const query = `
INSERT INTO BuyerTradingHistory (id, application, exporterHasTradedWithBuyer) VALUES ${buyerTradingHistoryValues};
`;

const updated = await executeSqlQuery({ connection, query, loggingMessage });

return updated;
};

export default moveBuyerTradingHistoryFields;
Loading

0 comments on commit 6022563

Please sign in to comment.