larvitorder
Version:
Generic order system
408 lines • 18.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Order = void 0;
const luxon_1 = require("luxon");
const helpers_1 = require("./helpers");
const larvitutils_1 = require("larvitutils");
const uuidLib = __importStar(require("uuid"));
const topLogPrefix = 'larvitorder: order.ts:';
class Order {
constructor(options) {
if (!options.db)
throw new Error('Required option db is missing');
this.db = options.db;
this.log = options.log ?? new larvitutils_1.Log();
this.lUtils = options.lUtils ?? new larvitutils_1.Utils({ log: this.log });
this.helpers = options.helpers ?? new helpers_1.Helpers({
log: this.log,
db: this.db,
lUtils: this.lUtils,
});
this.init(options);
}
init(options) {
const logPrefix = `${topLogPrefix} Order.prototype.init() -`;
let uuid = options.uuid;
if (!uuid) {
uuid = uuidLib.v1();
this.log.debug(`${logPrefix} New Order - Creating Order with uuid: ${uuid}`);
}
else {
this.log.debug(`${logPrefix} Instanciating order with uuid: ${uuid}`);
}
this.uuid = uuid;
// NOTE: DB table is setup to not store ms, set to 000
this.created = options.created ?? luxon_1.DateTime.utc().startOf('second').toISO();
this.updated = options.updated ?? luxon_1.DateTime.utc().startOf('second').toISO();
this.fields = options.fields ?? {};
this.rows ?? (this.rows = []);
if (options.rows) {
for (const row of options.rows) {
this.rows.push({
...row,
uuid: row.uuid || uuidLib.v1(),
});
}
}
}
async loadFromDb() {
const logPrefix = `${topLogPrefix} Order.loadFromDb() - uuid: "${this.uuid}" -`;
const uuidBuffer = this.lUtils.uuidToBuffer(this.uuid);
if (!uuidBuffer) {
const err = new Error('Invalid order uuid');
this.log.warn(`${logPrefix} ${err.message}`);
throw err;
}
// Get basic order data
this.log.debug(`${logPrefix} Getting basic order data`);
const { rows: dbOrders } = await this.db.query('SELECT * FROM orders WHERE uuid = ?', [uuidBuffer]);
if (!dbOrders.length) {
this.log.verbose(`${logPrefix} Could not find order with uuid: "${this.uuid}"`);
this.fields = {};
this.rows = [];
return false;
}
this.uuid = this.helpers.formatUuid(dbOrders[0].uuid);
const created = dbOrders[0].created;
// Handle DB conf dateStrings being both true and false
if (created instanceof Date) {
this.created = luxon_1.DateTime
.fromJSDate(created)
.toUTC()
.toISO();
}
else {
// We do this extra conversion since mariadb returns non-ISO format
// NOTE: THIS ASSUMES THAT MARIADB IS CONFIGURED FOR UTC TIMEZONE
// for a proper solution we have to look at the timezone in DB conf and
// handle it accordingly.
this.created = `${created.replace(' ', 'T')}.000Z`;
}
const updated = dbOrders[0].updated;
if (updated instanceof Date) {
this.updated = luxon_1.DateTime
.fromJSDate(updated)
.toUTC()
.toISO();
}
else {
// We do this extra conversion since mariadb returns non-ISO format
// NOTE: THIS ASSUMES THAT MARIADB IS CONFIGURED FOR UTC TIMEZONE
// for a proper solution we have to look at the timezone in DB conf and
// handle it accordingly.
this.updated = updated ? `${updated.replace(' ', 'T')}.000Z` : this.created;
}
// Get fields
this.fields = await this.getOrderFields();
// Get rows
const orderRows = await this.getOrderRows();
this.rows = orderRows;
// Sort rows
this.sortRows();
return true;
}
async getOrderFields() {
var _a;
const fields = {};
const uuidBuffer = this.lUtils.uuidToBuffer(this.uuid);
if (!uuidBuffer) {
const err = new Error('Invalid order uuid');
this.log.warn(`${topLogPrefix} getOrderFields() - ${err.message}`);
throw err;
}
let sql = '';
sql += 'SELECT orders_orderFields.name AS name, orders_orders_fields.fieldValue AS value\n';
sql += 'FROM orders_orders_fields\n';
sql += ' INNER JOIN orders_orderFields\n';
sql += ' ON orders_orders_fields.fieldUuid = orders_orderFields.uuid\n';
sql += 'WHERE orders_orders_fields.orderUuid = ?';
const { rows } = await this.db.query(sql, [uuidBuffer]);
for (const row of rows) {
fields[_a = row.name] ?? (fields[_a] = []);
fields[row.name].push(row.value);
}
return fields;
}
async getOrderRows() {
var _a, _b;
const rows = [];
const sorter = {};
const uuidBuffer = this.lUtils.uuidToBuffer(this.uuid);
if (uuidBuffer === false) {
const err = new Error('Invalid order uuid');
this.log.warn(`${topLogPrefix} getOrderRows() - ${err.message}`);
throw err;
}
let sql = '';
sql += 'SELECT orders_rows.rowUuid AS uuid, orders_rows_fields.rowStrValue, orders_rows_fields.rowIntValue, orders_rowFields.name\n';
sql += 'FROM orders_rows\n';
sql += ' INNER JOIN orders_rows_fields\n';
sql += ' ON orders_rows_fields.rowUuid = orders_rows.rowUuid\n';
sql += ' INNER JOIN orders_rowFields\n';
sql += ' ON orders_rowFields.uuid = orders_rows_fields.rowFieldUuid\n';
sql += 'WHERE orders_rows.orderUuid = ?';
const { rows: dbRows } = await this.db.query(sql, [uuidBuffer]);
for (const dbRow of dbRows) {
dbRow.uuid = this.helpers.formatUuid(dbRow.uuid);
if (!sorter[dbRow.uuid]) {
sorter[dbRow.uuid] = {
uuid: dbRow.uuid,
};
}
const value = dbRow.rowStrValue ?? dbRow.rowIntValue;
(_a = sorter[dbRow.uuid])[_b = dbRow.name] ?? (_a[_b] = []);
sorter[dbRow.uuid][dbRow.name].push(value);
}
for (const key in sorter) {
rows.push(sorter[key]);
}
return rows;
}
async rm() {
const orderUuidBuf = this.lUtils.uuidToBuffer(this.uuid);
if (!orderUuidBuf) {
const err = new Error('Invalid order uuid');
this.log.warn(`${topLogPrefix} rm() - ${err.message}`);
throw err;
}
try {
// Delete field data
await this.db.query('DELETE FROM orders_orders_fields WHERE orderUuid = ?', [orderUuidBuf]);
// Delete row field data
const { rows } = await this.db.query('SELECT rowUuid FROM orders_rows WHERE orderUuid = ?', [orderUuidBuf]);
if (rows.length) {
let sql = 'DELETE FROM orders_rows_fields WHERE rowUuid IN (';
sql += rows.map(() => '?').join(',');
sql += ')';
await this.db.query(sql, rows.map((n) => n.rowUuid));
}
// Delete rows
await this.db.query('DELETE FROM orders_rows WHERE orderUuid = ?', [orderUuidBuf]);
// Delete order
await this.db.query('DELETE FROM orders WHERE uuid = ?', [orderUuidBuf]);
this.log.info(`${topLogPrefix} rm() - Removed order with UUID: "${this.uuid}"`);
}
catch (_err) {
const err = _err;
this.log.warn(`${topLogPrefix} rm() - Error removing order with UUID: "${this.uuid}", err: ${err.message}`);
throw err;
}
}
// Saving the order object to the database using a diff.
async save() {
const logPrefix = `${topLogPrefix} save() -`;
const orderFields = this.fields;
const orderRows = this.rows;
const orderUuid = this.uuid;
const created = luxon_1.DateTime.fromISO(this.created);
if (!created.isValid)
throw new Error('created is not an valid ISO-8601 date');
const createdUtc = created.toUTC().toISO(); // Always store in UTC
const updatedUtc = luxon_1.DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss');
const orderUuidBuf = this.lUtils.uuidToBuffer(orderUuid);
const uniqueUpdateRowUuids = [];
if (this.lUtils.formatUuid(orderUuid) === false || typeof orderUuidBuf === 'boolean') {
const err = new Error('Invalid orderUuid: "' + orderUuid + '"');
this.log.error(`${logPrefix} ${err.message}`);
throw err;
}
// Get all field uuids
const fieldUuidsByName = await this.helpers.getOrderFieldUuids(Object.keys(orderFields));
// Get all row field uuids and make sure all rows got an uuid
const rowFieldNames = [];
for (let i = 0; i < orderRows.length; i++) {
const row = orderRows[i];
row.uuid = row.uuid || uuidLib.v4();
// Set sortOrder on rows to maintain order independent of storage engine
row.sortOrder = i;
for (const rowFieldName of Object.keys(row)) {
if (!rowFieldNames.includes(rowFieldName)) {
rowFieldNames.push(rowFieldName);
}
}
}
const rowFieldUuidsByName = await this.helpers.getRowFieldUuids(rowFieldNames);
// Get a database connection
const dbCon = await this.db.getConnection();
try {
// Make sure the base order row exists
await dbCon.query('INSERT IGNORE INTO orders (uuid, created) VALUES(?,?)', [orderUuidBuf, createdUtc]);
// Update base order's updated timestamp
await dbCon.query('UPDATE orders SET updated = ? WHERE uuid = ?', [updatedUtc, orderUuidBuf]);
// Begin transaction
await dbCon.beginTransaction();
// Clean out old field data
await dbCon.query('DELETE FROM orders_orders_fields WHERE orderUuid = ?', [orderUuidBuf]);
// Insert fields
const dbFields = [];
let sql = 'INSERT INTO orders_orders_fields (orderUuid, fieldUuid, fieldValue) VALUES';
for (const fieldName of Object.keys(orderFields)) {
if (!Array.isArray(orderFields[fieldName])) {
orderFields[fieldName] = [orderFields[fieldName]];
}
for (const fieldValue of orderFields[fieldName]) {
if (fieldValue === null || fieldValue === undefined)
continue;
sql += '(?,?,?),';
dbFields.push(orderUuidBuf);
dbFields.push(fieldUuidsByName[fieldName]);
dbFields.push(fieldValue);
}
}
sql = sql.substring(0, sql.length - 1) + ';';
if (dbFields.length) {
await dbCon.query(sql, dbFields);
}
// Get rows to update
const { changedRows: updateRows, removeRows } = await this.helpers.getChangedRows(dbCon, orderUuidBuf, this.rows, rowFieldUuidsByName);
// Get unique rowUuids from updateRows
const seen = {};
let j = 0;
for (const row of updateRows) {
if (!seen[row.rowUuid]) {
seen[row.rowUuid] = true;
uniqueUpdateRowUuids[j++] = { rowUuid: row.rowUuid, rowUuidBuff: row.rowUuidBuff };
}
}
// Clean out changed orders_rows_fields
if (uniqueUpdateRowUuids.length || removeRows.length) {
let sql = 'DELETE FROM orders_rows_fields WHERE rowUuid IN (';
if (uniqueUpdateRowUuids.length) {
sql += uniqueUpdateRowUuids.map(() => '?').join(',');
}
if (removeRows.length) {
if (uniqueUpdateRowUuids.length) {
sql += ',';
}
sql += removeRows.map(() => '?').join(',');
}
sql += ')';
await dbCon.query(sql, [...uniqueUpdateRowUuids.map(x => x.rowUuidBuff), ...removeRows.map(x => x.rowUuidBuff)]);
}
// Clean out changed orders_rows
if (uniqueUpdateRowUuids.length || removeRows.length) {
let sql = 'DELETE FROM orders_rows WHERE orderUuid = ? AND rowUuid IN (';
if (uniqueUpdateRowUuids.length) {
sql += uniqueUpdateRowUuids.map(() => '?').join(',');
}
if (removeRows.length) {
if (uniqueUpdateRowUuids.length) {
sql += ',';
}
sql += removeRows.map(() => '?').join(',');
}
sql += ')';
await dbCon.query(sql, [orderUuidBuf, ...uniqueUpdateRowUuids.map(x => x.rowUuidBuff), ...removeRows.map(x => x.rowUuidBuff)]);
}
// Insert rows
if (uniqueUpdateRowUuids.length) {
const dbFields = [];
let sql = 'INSERT INTO orders_rows (rowUuid, orderUuid) VALUES';
for (const rowUuid of uniqueUpdateRowUuids.map(x => x.rowUuidBuff)) {
sql += '(?,?),';
dbFields.push(rowUuid);
dbFields.push(orderUuidBuf);
}
sql = sql.substring(0, sql.length - 1);
if (dbFields.length) {
await dbCon.query(sql, dbFields);
}
}
// Insert row fields
if (updateRows.length) {
const dbFields = [];
let sql = 'INSERT INTO orders_rows_fields (rowUuid, rowFieldUuid, rowIntValue, rowStrValue) VALUES';
for (const updateRow of updateRows) {
for (const rowFieldName of Object.keys(updateRow.row)) {
if (rowFieldName === 'uuid')
continue;
updateRow.row[rowFieldName] = this.helpers.arrayify(updateRow.row[rowFieldName]) ?? [];
for (const rowFieldValue of updateRow.row[rowFieldName]) {
if (rowFieldValue === undefined || rowFieldValue === null)
continue;
sql += '(?,?,?,?),';
dbFields.push(updateRow.rowUuidBuff);
dbFields.push(rowFieldUuidsByName[rowFieldName]);
if (this.helpers.isNumberIsh(rowFieldValue)) {
dbFields.push(rowFieldValue);
dbFields.push(null);
}
else {
dbFields.push(null);
dbFields.push(rowFieldValue);
}
}
}
}
sql = sql.substring(0, sql.length - 1) + ';';
if (dbFields.length) {
await dbCon.query(sql, dbFields);
}
}
await dbCon.commit();
}
catch (_err) {
const err = _err;
// Rollback transaction
await dbCon?.rollback();
this.log.error(`${logPrefix} failed to save order with UUID "${orderUuid}", err: ${err.message}`);
throw err;
}
finally {
// Always release connection
await dbCon?.release();
}
// Clean out sortOrder row field
for (let i = 0; i < orderRows.length; i++) {
const row = orderRows[i];
delete row.sortOrder;
}
this.log.info(`${logPrefix} Saved order with UUID: "${orderUuid}"`);
return this;
}
// Sorting rows on the row field "sortOrder" if it exists
sortRows() {
if (!this.rows?.length)
return;
this.rows.sort((a, b) => {
const ax = Number(Array.isArray(a.sortOrder) ? a.sortOrder[0] : a.sortOrder);
const bx = Number(Array.isArray(b.sortOrder) ? b.sortOrder[0] : b.sortOrder);
if (ax === bx)
return 0;
if (isNaN(ax) && !isNaN(bx))
return 1;
if (isNaN(bx) && !isNaN(ax))
return -1;
return ax - bx;
});
// Remove all sortOrder fields
for (let i = 0; this.rows[i] !== undefined; i++) {
delete this.rows[i].sortOrder;
}
}
}
exports.Order = Order;
//# sourceMappingURL=order.js.map