Files
backend/scripts/bootstrap-iranbay-platform.mjs

194 lines
5.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Bootstrap the IranBay platform tenant in Meshkee.
*
* This creates ONE Meshkee business used only for IranBay auth
* (customer accounts via domain iranbay.com).
*
* Producer shops that register on IranBay are SEPARATE Meshkee businesses
* (created later via admin / register-shop) — do not reuse this tenant.
*
* Usage:
* MESHKEE_API_URL=https://api.meshkee.com/api/v1 \
* MESHKEE_API_TOKEN='Bearer …' \ # superadmin access token
* node scripts/bootstrap-iranbay-platform.mjs
*
* Or login instead of token:
* MESHKEE_SUPERADMIN_CELL=+98… MESHKEE_SUPERADMIN_PASSWORD=… node …
*/
const API =
(process.env.MESHKEE_API_URL || "https://api.meshkee.com/api/v1").replace(
/\/$/,
"",
);
const HOST = (
process.env.IRANBAY_PLATFORM_DOMAIN || "iranbay.com"
).toLowerCase();
const SLUG = process.env.IRANBAY_PLATFORM_SLUG || "iranbay";
async function request(path, { method = "GET", token, body } = {}) {
const headers = { "Content-Type": "application/json" };
if (token) headers.Authorization = token.startsWith("Bearer ")
? token
: `Bearer ${token}`;
const res = await fetch(`${API}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const payload = await res.json().catch(() => null);
if (!res.ok) {
const message =
typeof payload?.message === "string"
? payload.message
: Array.isArray(payload?.message)
? payload.message.join(", ")
: `HTTP ${res.status}`;
const err = new Error(message);
err.status = res.status;
err.payload = payload;
throw err;
}
return payload;
}
async function resolveToken() {
const raw = process.env.MESHKEE_API_TOKEN?.trim();
if (raw) return raw.startsWith("Bearer ") ? raw : `Bearer ${raw}`;
const cell = process.env.MESHKEE_SUPERADMIN_CELL?.trim();
const password = process.env.MESHKEE_SUPERADMIN_PASSWORD;
if (!cell || !password) {
throw new Error(
"Set MESHKEE_API_TOKEN or MESHKEE_SUPERADMIN_CELL + MESHKEE_SUPERADMIN_PASSWORD",
);
}
const data = await request("/auth/login", {
method: "POST",
body: { cellNumber: cell, password },
});
if (!data.accessToken) throw new Error("Login did not return accessToken");
return `Bearer ${data.accessToken}`;
}
async function domainExists() {
const res = await fetch(`${API}/tenants/${HOST}/categories`);
return res.status !== 404;
}
async function main() {
console.log(`Meshkee API: ${API}`);
console.log(`Platform domain: ${HOST}`);
if (await domainExists()) {
console.log(`✓ Domain ${HOST} already resolves — nothing to do.`);
return;
}
const token = await resolveToken();
const categories = await request("/business-categories", { token });
const items = categories.items ?? [];
const other =
items.find((c) => String(c.slug) === "other") ??
items[0];
if (!other) {
throw new Error("No Meshkee business categories found (need at least one).");
}
console.log(`Creating Meshkee business slug=${SLUG}…`);
const ownerCell =
process.env.MESHKEE_SUPERADMIN_CELL?.trim() ||
process.env.IRANBAY_PLATFORM_OWNER_CELL?.trim();
const ownerPassword =
process.env.MESHKEE_SUPERADMIN_PASSWORD ||
process.env.IRANBAY_PLATFORM_OWNER_PASSWORD;
if (!ownerCell || !ownerPassword) {
throw new Error(
"Need owner cell/password to create the platform business (use MESHKEE_SUPERADMIN_* or IRANBAY_PLATFORM_OWNER_*).",
);
}
let business;
try {
business = await request("/businesses", {
method: "POST",
token,
body: {
name: "IranBay",
nameFa: "ایران‌بی",
slug: SLUG,
about:
"IranBay marketplace platform identity tenant (auth only — not a Meshkee storefront).",
categoryIds: [Number(other.id)],
ownerFirstName: "IranBay",
ownerLastName: "Platform",
ownerFirstNameEn: "IranBay",
ownerLastNameEn: "Platform",
ownerCellNumber: ownerCell,
ownerPassword,
},
});
} catch (err) {
if (String(err.message).toLowerCase().includes("slug")) {
console.log("Business slug may already exist — searching…");
const search = await request(
`/businesses/search?q=${encodeURIComponent(SLUG)}&limit=5`,
{ token },
);
business = (search.items ?? []).find(
(item) => String(item.slug) === SLUG,
);
if (!business) throw err;
} else {
throw err;
}
}
const businessId = String(business.id);
console.log(`✓ Meshkee business id=${businessId}`);
console.log(`Attaching domain ${HOST} (no storefront / no DNS)…`);
try {
await request(`/businesses/${businessId}/domains`, {
method: "POST",
token,
body: {
host: HOST,
isPrimary: true,
updateDns: false,
},
});
console.log(`✓ Domain ${HOST} linked`);
} catch (err) {
if (String(err.message).toLowerCase().includes("already")) {
console.log(`✓ Domain already attached (${err.message})`);
} else {
throw err;
}
}
if (!(await domainExists())) {
throw new Error(
`Domain attached but tenants/${HOST} still 404 — check Meshkee domain isActive.`,
);
}
console.log("");
console.log("Done. Set in dashboard/website:");
console.log(` NEXT_PUBLIC_MESHKEE_TENANT_DOMAIN=${HOST}`);
console.log("");
console.log(
"Remember: producer shops register as SEPARATE Meshkee businesses + IranBay marketplace listings.",
);
}
main().catch((err) => {
console.error("Bootstrap failed:", err.message);
process.exit(1);
});