const express = require("express"); const axios = require("axios"); const { log } = require("./logger"); const { saveToken, getToken, deleteToken } = require("./tokenStore"); const router = express.Router(); const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID; const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET; router.get("/auth/login", (req, res) => { const { shop } = req.query; if (!shop) { return res.status(400).json({ error: "Missing shop query parameter." }); } const redirectUri = process.env.SHOPIFY_REDIRECT_URI || `${process.env.APP_URL || "http://localhost:3002"}/auth/callback`; const scopes = process.env.SHOPIFY_SCOPES || "write_products,write_files,write_inventory,write_publications"; const installUrl = `https://${shop}/admin/oauth/authorize?client_id=${CLIENT_ID}&scope=${encodeURIComponent(scopes)}&redirect_uri=${encodeURIComponent(redirectUri)}`; return res.redirect(installUrl); }); router.get("/auth/callback", async (req, res) => { const { shop, code } = req.query; if (!shop || !code) { log("general", `Missing shop or code in callback: ${JSON.stringify(req.query)}`); return res.status(400).send("Missing shop or code parameter."); } try { log(shop, "Exchanging OAuth code for access token"); const resp = await axios.post( `https://${shop}/admin/oauth/access_token`, { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code, }, { headers: { "Content-Type": "application/json" }, } ); const { access_token, scope } = resp.data; saveToken(shop, access_token, scope); log(shop, "Token saved to data/tokens.json"); // Fetch the store's primary location ID (requires read_locations scope) try { const locResp = await axios.post( `https://${shop}/admin/api/${process.env.SHOPIFY_API_VERSION || "2025-10"}/graphql.json`, { query: '{ locations(first: 10, includeInactive: false) { nodes { id name fulfillsOnlineOrders } } }' }, { headers: { "X-Shopify-Access-Token": access_token, "Content-Type": "application/json" } } ); const nodes = locResp.data?.data?.locations?.nodes || []; // Prefer the location that fulfills online orders (the real shop location) const node = nodes.find((l) => l.fulfillsOnlineOrders) || nodes[0]; if (node?.id) { saveToken(shop, access_token, scope, null, node.id); log(shop, `Primary location saved: ${node.name} (${node.id})`); } else { log(shop, `No location returned — add read_locations to SHOPIFY_SCOPES and re-auth.`); } } catch (locErr) { log(shop, `Could not fetch primary location: ${locErr.message}`); } const finalToken = getToken(shop); log(shop, `Auth complete. Stored token:\n${JSON.stringify(finalToken, null, 2)}`); const redirectTarget = process.env.SHOPIFY_AFTER_AUTH_REDIRECT || "https://admin.shopify.com"; return res.redirect(redirectTarget); } catch (err) { const errMsg = err.response?.data || err.message; log(shop, `OAuth error: ${JSON.stringify(errMsg)}`); return res.status(500).send("Failed to get access token"); } }); router.post("/auth/logout", (req, res) => { const { shop } = req.body || {}; if (!shop) { return res.status(400).json({ error: "Missing shop." }); } deleteToken(shop); log(shop, "Shop token removed"); return res.json({ ok: true, shop }); }); module.exports = router;