This is the template project that's checked out and configured when you run the bando-up command from ljsthw-bandolier. This is where the code really lives.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bandolier-template/commands/examples/dumpup.js

174 lines
5.5 KiB

// you may not need all of these but they come up a lot
import fs from "fs";
import assert from "assert";
import logging from '../lib/logging.js';
import { mkdir, write } from "../lib/builderator.js";
import glob from "fast-glob";
import { Database } from "duckdb-async";
import { UserPaymentProduct, User, Product, Payment } from "../lib/models.js";
import slugify from "slugify";
const log = logging.create(import.meta.url);
export const description = "Is used to load the data found in the django dump to the database."
// your command uses the npm package commander's options format
export const options = [
["--mode-split", "Split a django dump into multiple files per table with pk=id"],
["--mode-analyze", "Run in analysis mode, which loads the tables into duckdb."],
["--mode-import", "Use the split files in --split-dir to import"],
["--input <string>", "A string option with a default."],
]
// put required options in the required variable
export const required = [
["--split-dir <string>", "Directory where the split .json files go", "data_dump"],
["--duckdb <string>", "The duckdb to create.", "dump.duckdb"],
]
// handy function for checking things are good and aborting
const check = (test, fail_message) => {
if(!test) {
log.error(fail_message);
process.exit(1);
}
}
export const mode_split = async (opts) => {
// it's easier to debug options with console
const raw_data = JSON.parse(fs.readFileSync(opts.input));
const tables = {};
for(let row of raw_data) {
assert(row.fields.id === undefined, `Bad ID ${JSON.stringify(row)}`);
row.fields.id = row.pk; // why did django do this? so weird
if(tables[row.model] === undefined) {
// first one
tables[row.model] = [row.fields];
} else {
tables[row.model].push(row.fields);
}
}
console.log(opts);
mkdir(opts.splitDir);
for(let [name, data] of Object.entries(tables)) {
const out_file = `${opts.splitDir}/${name}.json`;
console.log("WRITE", out_file);
write(out_file, JSON.stringify(data, null, 4));
}
// due to how async/await works it's just easier to manually exit with exit codes
process.exit(0);
}
export const mode_analyze = async (opts) => {
console.log("ANALYZE", opts);
const db = new Database(opts.duckdb);
// glob the files in the splitDir
const split_dir = await glob(`${opts.splitDir}/*.json`);
for(let file_name of split_dir) {
const [junk, table_name, ext] = file_name.split('.');
console.log(">> TABLE", table_name, "CREATE FROM", file_name);
await db.exec(`CREATE TABLE "${table_name}" AS SELECT * FROM "${file_name}"`);
// syntax doesn't follow SQL for replace ? with variable
}
await db.close();
process.exit(0);
}
export const mode_import = async (opts) => {
console.log("IMPORT", opts);
const db = new Database(opts.duckdb);
// DDB go through each product
const products = await db.all("SELECT * FROM PRODUCT");
for(let product of products) {
const my_product = await Product.insert({
created_at: product.created_on,
title: product.title,
description: product.description,
price: product.base_price,
currency: "USD",
currency_symbol: "$",
active: product.active,
slug: slugify(product.title, {lower: true, strict: true}),
short_blurb: product.description,
docs_url: product.private_location,
poster: product.poster,
category: "Python",
created_by: "Zed A. Shaw",
preorder: 1
});
const purchases = await db.all(`SELECT * FROM PURCHASE WHERE product=${product.id} AND state='PAID'`);
console.log("PRODUCT", my_product.title, "PURCHASES", purchases.length);
for(let purchase of purchases) {
const cust_q = await db.all(`SELECT * FROM CUSTOMER WHERE id=${purchase.customer}`);
const customer = cust_q[0];
const fake_password = User.random_hex(10);
let user = await User.register({
created_at: customer.created_on,
initials: "",
full_name: customer.full_name,
password: fake_password,
password_repeat: fake_password,
email: customer.email,
unsubscribe: !customer.promotable,
unsubscribed_on: null,
});
if(!user) {
console.log("USER EXISTS LOADING", customer.email);
user = await User.first({email: customer.email});
} else {
console.log("ADDED", user.email, user.id, "PASS", fake_password);
}
// create payment and then connect with upp
const payment = await Payment.insert({
created_at: purchase.created_on,
system: purchase.purchase_system.toLowerCase(),
status: "complete",
internal_id: Payment.gen_internal_id(),
sys_created_on: purchase.ended_on ? purchase.ended_on : purchase.created_on,
sys_primary_id: purchase.service_data || "",
sys_secondary_id: purchase.fsm_state,
user_id: purchase.customer,
status_reason: "imported",
});
const upp = await UserPaymentProduct.finish_purchase(user, payment, my_product);
}
}
await db.close();
process.exit(0);
}
export const main = async (opts) => {
if(opts.modeSplit) {
check(opts.input !== undefined, "--input required for --mode-split");
await mode_split(opts);
} else if(opts.modeAnalyze) {
await mode_analyze(opts);
} else if(opts.modeImport) {
await mode_import(opts);
} else {
console.error("USAGE: need one of --mode-analyze, --mode-import, or --mode-sync. BACKUP THE DB FIRST.");
process.exit(1);
}
}