From a6d2f10149860b9e6c4fcdbb44e1792330556d2d Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 9 Jun 2026 22:34:52 -0500 Subject: [PATCH] chore: Mark Adams rescue (3rd real customer stuck on the login bug) Paid Jun 1 for MCS-150 (card), no customers row -> couldn't log in -> no intake -> filing stuck 'NEEDS MANUAL FILING'. Created his customers row + sent login + intake email (cc justin). All 3 real paying customers now rescued; the underlying card/PayPal login bug is fixed so new orders won't hit this. --- scripts/rescue-mark.mjs | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 scripts/rescue-mark.mjs diff --git a/scripts/rescue-mark.mjs b/scripts/rescue-mark.mjs new file mode 100644 index 0000000..f497bc8 --- /dev/null +++ b/scripts/rescue-mark.mjs @@ -0,0 +1,77 @@ +/** + * Rescue Mark Adams (mark@adamslumber.com) - paid Jun 1 for an MCS-150 update via + * card, but the card/webhook path never created his portal `customers` row (the + * login bug), so he could not log in or complete intake -> his MCS-150 is stuck + * "NEEDS MANUAL FILING". Now that the login bug is fixed: + * 1. create his customers row, + * 2. send a password-set link + the intake link (CC justin), so he can log in + * and complete intake to unblock the filing. + * + * Run: docker exec performancewest-api-1 node /app/scripts/rescue-mark.mjs + */ +import pg from "pg"; +import crypto from "crypto"; +import nodemailer from "nodemailer"; + +const EMAIL = "mark@adamslumber.com"; +const CC = "justin@performancewest.net"; +const SITE = process.env.DOMAIN ? `https://${process.env.DOMAIN}` : "https://performancewest.net"; +const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL }); +const mailer = nodemailer.createTransport({ + host: process.env.SMTP_HOST || "co.carrierone.com", + port: parseInt(process.env.SMTP_PORT || "587", 10), + secure: false, + auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }, +}); +const FROM = process.env.SMTP_FROM || "Performance West "; + +const { rows: orders } = await pool.query( + `SELECT order_number, service_name, service_slug, customer_name, intake_data + FROM compliance_orders WHERE lower(customer_email)=lower($1) ORDER BY created_at`, + [EMAIL], +); +if (!orders.length) { console.log("no orders for", EMAIL); process.exit(1); } +const name = orders[0].customer_name || "there"; +const firstName = name.split(" ")[0]; +let company = null; +try { const i = typeof orders[0].intake_data === "string" ? JSON.parse(orders[0].intake_data) : orders[0].intake_data; company = i?.company || i?.legal_name || i?.entity_name || null; } catch {} + +// 1) customers row +const c = await pool.query( + `INSERT INTO customers (email, name, company) VALUES ($1,$2,$3) + ON CONFLICT (email) DO UPDATE SET name=COALESCE(customers.name,EXCLUDED.name), company=COALESCE(customers.company,EXCLUDED.company) + RETURNING id, (password_hash IS NOT NULL) AS has_pw`, [EMAIL, name, company]); +console.log(`[rescue] customers row id=${c.rows[0].id} email=${EMAIL} has_password=${c.rows[0].has_pw}`); + +// 2) password-set link +const token = crypto.randomBytes(32).toString("hex"); +await pool.query(`INSERT INTO password_reset_tokens (customer_id, token, expires_at) VALUES ($1,$2,$3)`, + [c.rows[0].id, token, new Date(Date.now() + 60 * 60 * 1000)]); +const resetLink = `${SITE}/account/reset-password?token=${token}`; + +const items = orders.map(o => { + const url = `${SITE}/order/${o.service_slug}?order=${o.order_number}`; + const note = o.service_slug === "mcs150-update" + ? " (we will prepare the update from your intake, then send it to you to sign before we submit to FMCSA)" + : ""; + return `
  • ${o.service_name} (${o.order_number})${note}
  • `; +}).join(""); + +await mailer.sendMail({ + from: FROM, to: EMAIL, cc: CC, + subject: "Your Performance West order - log in and complete your intake", + html: `
    +

    You're all set, ${firstName}

    +

    Thanks for your order. We had a delivery issue that kept our earlier emails from reaching you - that's fixed now, so here is everything you need to get your filing moving.

    +

    1. Set your password to log in (expires in 60 minutes):

    +

    Set my password →

    +

    Or paste this link: ${resetLink}

    +

    2. Complete the short intake form so we have what we need to prepare your filing (about 2-5 minutes):

    +
      ${items}
    +

    Once your intake is in, we prepare the MCS-150 update and send it to you to review and sign - we never submit to FMCSA without your signature. Questions? Reply here or call 1-888-411-0383.

    +

    Performance West Inc. · performancewest.net · 1-888-411-0383

    +
    `, + text: `Hi ${firstName}, set your password to log in: ${resetLink} (expires 60 min). Then complete your intake: ${orders.map(o => o.service_name + " (" + o.order_number + "): " + SITE + "/order/" + o.service_slug + "?order=" + o.order_number).join("; ")}. The MCS-150 will be sent to you to sign before we submit to FMCSA. Questions? 1-888-411-0383.`, +}); +console.log(`[rescue] login + intake email sent to ${EMAIL} (cc ${CC}) for ${orders.length} order(s)`); +await pool.end();