207 lines
5.3 KiB
JavaScript
207 lines
5.3 KiB
JavaScript
|
import {
|
||
|
map, filter, groupBy, split,
|
||
|
some, find,
|
||
|
} from 'lodash/fp';
|
||
|
import {
|
||
|
LOCK_FILENAME, TRANSACTIONS_FOLDER, idSep, isUpdate,
|
||
|
nodeKeyHashFromBuildFolder, isBuildIndexFolder, getTransactionId,
|
||
|
isDelete, isCreate,
|
||
|
} from './transactionsCommon';
|
||
|
import {
|
||
|
joinKey, $, none, isSomething,
|
||
|
} from '../common';
|
||
|
import { getLastPartInKey, getNodeFromNodeKeyHash } from '../templateApi/hierarchy';
|
||
|
import { _load } from '../recordApi/load';
|
||
|
|
||
|
export const retrieve = async (app) => {
|
||
|
const transactionFiles = await app.datastore.getFolderContents(
|
||
|
TRANSACTIONS_FOLDER,
|
||
|
);
|
||
|
|
||
|
let transactions = [];
|
||
|
|
||
|
if (some(isBuildIndexFolder)(transactionFiles)) {
|
||
|
const buildIndexFolder = find(isBuildIndexFolder)(transactionFiles);
|
||
|
|
||
|
transactions = await retrieveBuildIndexTransactions(
|
||
|
app,
|
||
|
joinKey(TRANSACTIONS_FOLDER, buildIndexFolder),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (transactions.length > 0) return transactions;
|
||
|
|
||
|
return await retrieveStandardTransactions(
|
||
|
app, transactionFiles,
|
||
|
);
|
||
|
};
|
||
|
|
||
|
const retrieveBuildIndexTransactions = async (app, buildIndexFolder) => {
|
||
|
const childFolders = await app.datastore.getFolderContents(buildIndexFolder);
|
||
|
if (childFolders.length === 0) {
|
||
|
// cleanup
|
||
|
await app.datastore.deleteFolder(buildIndexFolder);
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
const getTransactionFiles = async (childFolderIndex = 0) => {
|
||
|
if (childFolderIndex >= childFolders.length) return [];
|
||
|
|
||
|
const childFolderKey = joinKey(buildIndexFolder, childFolders[childFolderIndex]);
|
||
|
const files = await app.datastore.getFolderContents(
|
||
|
childFolderKey,
|
||
|
);
|
||
|
|
||
|
if (files.length === 0) {
|
||
|
await app.datastore.deleteFolder(childFolderKey);
|
||
|
return await getTransactionFiles(childFolderIndex + 1);
|
||
|
}
|
||
|
|
||
|
return { childFolderKey, files };
|
||
|
};
|
||
|
|
||
|
const transactionFiles = await getTransactionFiles();
|
||
|
|
||
|
if (transactionFiles.files.length === 0) return [];
|
||
|
|
||
|
const transactions = $(transactionFiles.files, [
|
||
|
map(parseTransactionId),
|
||
|
]);
|
||
|
|
||
|
for (const t of transactions) {
|
||
|
const transactionContent = await app.datastore.loadJson(
|
||
|
joinKey(
|
||
|
transactionFiles.childFolderKey,
|
||
|
t.fullId,
|
||
|
),
|
||
|
);
|
||
|
t.record = await _load(app, transactionContent.recordKey);
|
||
|
}
|
||
|
|
||
|
transactions.indexNode = $(buildIndexFolder, [
|
||
|
getLastPartInKey,
|
||
|
nodeKeyHashFromBuildFolder,
|
||
|
getNodeFromNodeKeyHash(app.hierarchy),
|
||
|
]);
|
||
|
|
||
|
transactions.folderKey = transactionFiles.childFolderKey;
|
||
|
|
||
|
return transactions;
|
||
|
};
|
||
|
|
||
|
const retrieveStandardTransactions = async (app, transactionFiles) => {
|
||
|
const transactionIds = $(transactionFiles, [
|
||
|
filter(f => f !== LOCK_FILENAME
|
||
|
&& !isBuildIndexFolder(f)),
|
||
|
map(parseTransactionId),
|
||
|
]);
|
||
|
|
||
|
const transactionIdsByRecord = $(transactionIds, [
|
||
|
groupBy('recordId'),
|
||
|
]);
|
||
|
|
||
|
const dedupedTransactions = [];
|
||
|
|
||
|
const verify = async (t) => {
|
||
|
if (t.verified === true) return t;
|
||
|
|
||
|
const id = getTransactionId(
|
||
|
t.recordId,
|
||
|
t.transactionType,
|
||
|
t.uniqueId,
|
||
|
);
|
||
|
|
||
|
const transaction = await app.datastore.loadJson(
|
||
|
joinKey(TRANSACTIONS_FOLDER, id),
|
||
|
);
|
||
|
|
||
|
if (isDelete(t)) {
|
||
|
t.record = transaction.record;
|
||
|
t.verified = true;
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
const rec = await _load(
|
||
|
app,
|
||
|
transaction.recordKey,
|
||
|
);
|
||
|
if (rec.transactionId === id) {
|
||
|
t.record = rec;
|
||
|
if (transaction.oldRecord) { t.oldRecord = transaction.oldRecord; }
|
||
|
t.verified = true;
|
||
|
} else {
|
||
|
t.verified = false;
|
||
|
}
|
||
|
|
||
|
return t;
|
||
|
};
|
||
|
|
||
|
const pickOne = async (trans, forType) => {
|
||
|
const transForType = filter(forType)(trans);
|
||
|
if (transForType.length === 1) {
|
||
|
const t = await verify(transForType[0]);
|
||
|
return (t.verified === true ? t : null);
|
||
|
}
|
||
|
for (let t of transForType) {
|
||
|
t = await verify(t);
|
||
|
if (t.verified === true) { return t; }
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
for (const recordId in transactionIdsByRecord) {
|
||
|
const transIdsForRecord = transactionIdsByRecord[recordId];
|
||
|
if (transIdsForRecord.length === 1) {
|
||
|
const t = await verify(transIdsForRecord[0]);
|
||
|
if (t.verified) { dedupedTransactions.push(t); }
|
||
|
continue;
|
||
|
}
|
||
|
if (some(isDelete)(transIdsForRecord)) {
|
||
|
const t = await verify(find(isDelete)(transIdsForRecord));
|
||
|
if (t.verified) { dedupedTransactions.push(t); }
|
||
|
continue;
|
||
|
}
|
||
|
if (some(isUpdate)(transIdsForRecord)) {
|
||
|
const upd = await pickOne(transIdsForRecord, isUpdate);
|
||
|
if (isSomething(upd) && upd.verified) { dedupedTransactions.push(upd); }
|
||
|
continue;
|
||
|
}
|
||
|
if (some(isCreate)(transIdsForRecord)) {
|
||
|
const cre = await pickOne(transIdsForRecord, isCreate);
|
||
|
if (isSomething(cre)) { dedupedTransactions.push(cre); }
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const duplicates = $(transactionIds, [
|
||
|
filter(t => none(ddt => ddt.uniqueId === t.uniqueId)(dedupedTransactions)),
|
||
|
]);
|
||
|
|
||
|
|
||
|
const deletePromises = map(t => app.datastore.deleteFile(
|
||
|
joinKey(
|
||
|
TRANSACTIONS_FOLDER,
|
||
|
getTransactionId(
|
||
|
t.recordId,
|
||
|
t.transactionType,
|
||
|
t.uniqueId,
|
||
|
),
|
||
|
),
|
||
|
))(duplicates);
|
||
|
|
||
|
await Promise.all(deletePromises);
|
||
|
|
||
|
return dedupedTransactions;
|
||
|
};
|
||
|
|
||
|
const parseTransactionId = (id) => {
|
||
|
const splitId = split(idSep)(id);
|
||
|
return ({
|
||
|
recordId: splitId[0],
|
||
|
transactionType: splitId[1],
|
||
|
uniqueId: splitId[2],
|
||
|
fullId: id,
|
||
|
});
|
||
|
};
|