parent
a09a9a5766
commit
057c286cd3
@ -0,0 +1,173 @@ |
||||
// 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); |
||||
} |
||||
} |
Loading…
Reference in new issue