194 lines
5.6 KiB
JavaScript
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);
|
|
});
|