← back to Ventura Claw

server/connectors/etsy.js

54 lines

// Etsy — REAL. v3 API, OAuth2 access token + x-api-key header.
const API = "https://api.etsy.com/v3/application";
function key(c) { const k = c?.ETSY_KEYSTRING || process.env.ETSY_KEYSTRING; if (!k) throw new Error("ETSY_KEYSTRING not set"); return k; }
function token(c) { const t = c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN; if (!t) throw new Error("ETSY_ACCESS_TOKEN not set"); return t; }
async function call(method, path, body, c) {
  const r = await fetch(`${API}${path}`, {
    method,
    headers: { "Authorization": `Bearer ${token(c)}`, "x-api-key": key(c), "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined
  });
  const d = await r.json();
  if (!r.ok) throw new Error(`etsy ${path} ${r.status}: ${d.error_description || d.error || JSON.stringify(d).slice(0,200)}`);
  return d;
}
module.exports = {
  meta: { id: "etsy", name: "Etsy", category: "commerce", docsUrl: "https://developers.etsy.com/documentation/", auth: "oauth2", realImpl: true },
  fields: [
    { key: "ETSY_KEYSTRING",     label: "API Keystring (x-api-key)", type: "password", required: true,  hint: "developers.etsy.com app settings" },
    { key: "ETSY_ACCESS_TOKEN",  label: "OAuth Access Token",        type: "password", required: true,  hint: "user-token from OAuth flow (user_id.token)" },
    { key: "ETSY_SHOP_ID",       label: "Default Shop ID",           type: "text",     required: false, hint: "numeric shop_id (optional)" }
  ],
  configured(c) { return !!((c?.ETSY_KEYSTRING || process.env.ETSY_KEYSTRING) && (c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN)); },
  async health(c) {
    if (!this.configured(c)) return { ok: false, reason: "no_token" };
    try {
      // Token format is "<user_id>.<token>" — first part is user_id
      const tokParts = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".");
      const userId = tokParts[0];
      const d = await call("GET", `/users/${userId}`, null, c);
      return { ok: true, user_id: d.user_id, login_name: d.login_name, primary_email: d.primary_email };
    } catch (e) { return { ok: false, reason: e.message }; }
  },
  actions: {
    async "me"(_, c) {
      const userId = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".")[0];
      return call("GET", `/users/${userId}`, null, c);
    },
    async "shops.findByUser"(_, c) {
      const userId = String(c?.ETSY_ACCESS_TOKEN || process.env.ETSY_ACCESS_TOKEN).split(".")[0];
      return call("GET", `/users/${userId}/shops`, null, c);
    },
    async "listings.active"({ shop_id, limit }, c) {
      const id = shop_id || c?.ETSY_SHOP_ID || process.env.ETSY_SHOP_ID;
      if (!id) throw new Error("shop_id required");
      return call("GET", `/shops/${id}/listings/active?limit=${limit||25}`, null, c);
    },
    async "receipts.list"({ shop_id, limit }, c) {
      const id = shop_id || c?.ETSY_SHOP_ID || process.env.ETSY_SHOP_ID;
      if (!id) throw new Error("shop_id required");
      return call("GET", `/shops/${id}/receipts?limit=${limit||25}`, null, c);
    }
  }
};