product-review/app/routes/api.reviews.jsx

180 lines
6.4 KiB
JavaScript

import { authenticate, unauthenticated } from "../shopify.server";
const jsonResponse = (data, status = 200) => {
return new Response(JSON.stringify(data), {
status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-store",
},
});
};
async function ensureDefinition(admin) {
console.log(">>> [DEBUG] Triggering Metaobject Definition Creation...");
const mutation = `#graphql
mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
metaobjectDefinitionCreate(definition: $definition) {
metaobjectDefinition { type }
userErrors { field message }
}
}
`;
const variables = {
definition: {
name: "Product Review",
type: "custom_product_review",
access: { storefront: "PUBLIC_READ" },
capabilities: { publishable: { enabled: true } },
fieldDefinitions: [
{ name: "Product ID", key: "product_id", type: "single_line_text_field", required: true },
{ name: "Customer Name", key: "customer_name", type: "single_line_text_field" },
{ name: "Review Content", key: "content", type: "multi_line_text_field" },
{ name: "Rating", key: "rating", type: "single_line_text_field" }
]
}
};
const response = await admin.graphql(mutation, { variables });
const result = await response.json();
console.log(">>> [DEBUG] Creation Result:", JSON.stringify(result));
return result;
}
export const loader = async ({ request }) => {
try {
const { admin, session } = await authenticate.public.appProxy(request);
const url = new URL(request.url);
const productId = url.searchParams.get("productId");
if (!productId) return jsonResponse([]);
const query = `#graphql
query getReviews($query: String!) {
metaobjects(first: 50, type: "custom_product_review", query: $query) {
edges { node { fields { key value } } }
}
}
`;
const response = await admin.graphql(query, {
variables: { query: `product_id:${productId}` },
});
const data = await response.json();
// Check for "No definition" error
const isMissing = data.errors?.some(e => e.message.toLowerCase().includes("metaobject definition")) || !data.data?.metaobjects;
if (isMissing) {
console.log(">>> [DEBUG] Loader: Definition missing. Attempting to create...");
await ensureDefinition(admin);
return jsonResponse([]);
}
const reviews = (data.data?.metaobjects?.edges || []).map((edge) => {
const fields = {};
edge.node.fields.forEach((f) => { fields[f.key] = f.value; });
return {
name: fields.customer_name || "Anonymous",
review: fields.content || "",
rating: fields.rating || "5",
};
});
return jsonResponse(reviews);
} catch (err) {
console.error(">>> [ERROR] Loader:", err.message);
return jsonResponse([]);
}
};
export const action = async ({ request }) => {
try {
console.log(">>> [DEBUG] Action: Review Submission Started");
const { admin, session } = await authenticate.public.appProxy(request);
const url = new URL(request.url);
const customerId = url.searchParams.get("logged_in_customer_id");
if (!customerId) return jsonResponse({ error: "Please log in to leave a review." });
const formData = await request.formData();
const productId = formData.get("productId");
const name = formData.get("name");
const review = formData.get("review");
// 1. Purchase Check
const orderQuery = `#graphql
query getOrders($query: String!) {
orders(first: 10, query: $query) {
edges { node { lineItems(first: 20) { edges { node { product { id } } } } } }
}
}
`;
const orderResponse = await admin.graphql(orderQuery, {
variables: { query: `customer_id:${customerId}` }
});
const orderData = await orderResponse.json();
const hasPurchased = (orderData.data?.orders?.edges || []).some(o =>
o.node.lineItems.edges.some(i => i.node.product?.id?.includes(productId))
);
// if (!hasPurchased) {
// console.log(">>> [DEBUG] Action: No purchase history found for Customer ID:", customerId);
// return jsonResponse({ error: "Only verified buyers can leave a review. (Note: It may take a few minutes for new orders to sync)" });
// }
// 2. Save Review
const saveMutation = `#graphql
mutation create($m: MetaobjectCreateInput!) { metaobjectCreate(metaobject: $m) { metaobject { id } userErrors { field message } } }
`;
const saveResponse = await admin.graphql(saveMutation, {
variables: {
m: {
type: "custom_product_review",
fields: [
{ key: "product_id", value: String(productId) },
{ key: "customer_name", value: String(name) },
{ key: "content", value: String(review) },
{ key: "rating", value: "5" }
]
}
}
});
const result = await saveResponse.json();
console.log(">>> [DEBUG] Save Result:", JSON.stringify(result));
// Check for "No definition" error
const userErrors = result.data?.metaobjectCreate?.userErrors || [];
const isMissing = result.errors?.some(e => e.message.toLowerCase().includes("metaobject definition")) ||
userErrors.some(e => e.message.toLowerCase().includes("metaobject definition")) ||
result.data?.metaobjectCreate === null;
if (isMissing) {
console.log(">>> [DEBUG] Action: Definition missing. Creating structure...");
const createResult = await ensureDefinition(admin);
const creationError = createResult.data?.metaobjectDefinitionCreate?.userErrors?.[0]?.message;
if (creationError && !creationError.includes("already exists")) {
return jsonResponse({ error: `Database Creation Error: ${creationError}` });
}
return jsonResponse({ error: "SUCCESS: Database structure created! Please click Submit Review one last time to save your review." });
}
const errors = result.data?.metaobjectCreate?.userErrors;
if (errors && errors.length > 0) {
return jsonResponse({ error: errors[0].message });
}
console.log(">>> [DEBUG] Action: Review Saved Successfully!");
return jsonResponse({ success: true });
} catch (err) {
console.error(">>> [ERROR] Action Exception:", err.message);
return jsonResponse({ error: "Connection issue. Please refresh the page and try again." });
}
};