#!/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); });