Skip to content

Commit

Permalink
Merge pull request #676 from Shopify/add_more_authentication_examples
Browse files Browse the repository at this point in the history
Add more thorough authentication examples
  • Loading branch information
paulomarg authored Mar 4, 2024
2 parents 7a24add + d7a5bcd commit 06d2b5e
Show file tree
Hide file tree
Showing 15 changed files with 336 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .changeset/bright-students-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
8 changes: 7 additions & 1 deletion packages/shopify-app-remix/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
module.exports = {
ignorePatterns: ['docs/', '**/*.example.tsx?'],
ignorePatterns: [
'docs/',
'*.example.ts',
'*.example.tsx',
'*.example.*.ts',
'*.example.*.tsx',
],
};
70 changes: 70 additions & 0 deletions packages/shopify-app-remix/docs/generated/generated_docs_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -1488,6 +1488,19 @@
}
}
],
"defaultExample": {
"description": "Authenticate, run API mutation, and redirect",
"codeblock": {
"title": "Authenticate, run API mutation, and redirect",
"tabs": [
{
"title": "/app/routes/**.ts",
"language": "typescript",
"code": "import {type ActionFunctionArgs, json} from '@remix-run/node';\nimport {GraphqlQueryError} from '@shopify/shopify-api';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, redirect} = await authenticate.admin(request);\n\n try {\n await admin.graphql(\n `#graphql\n mutation updateProductTitle($input: ProductInput!) {\n productUpdate(input: $input) {\n product {\n id\n }\n }\n }`,\n {\n variables: {\n input: {id: '123', title: 'New title'},\n },\n },\n );\n\n return redirect('/app/product-updated');\n } catch (error) {\n if (error instanceof GraphqlQueryError) {\n return json({errors: error.body?.errors}, {status: 500});\n }\n\n return new Response('Failed to update product title', {status: 500});\n }\n};\n"
}
]
}
},
"jsDocTypeExamples": [
"EmbeddedAdminContext",
"AdminApiContext",
Expand Down Expand Up @@ -3087,6 +3100,19 @@
}
}
],
"defaultExample": {
"description": "Handle a flow action call",
"codeblock": {
"title": "Set a metafield on a customer after a flow call",
"tabs": [
{
"title": "/app/routes/**.ts",
"language": "typescript",
"code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {admin, payload} = await authenticate.flow(request);\n\n const customerId = payload.properties.customer_id;\n\n const response = await admin.graphql(\n `#graphql\n mutation setMetafield($customerId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $customerId\n namespace: \"my-app\",\n key: \"last_flow_update\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n customerId,\n time: new Date().toISOString(),\n },\n },\n );\n const body = await response.json();\n\n console.log('Updated value', body.data!.metafieldsSet!.metafields![0].value);\n\n return new Response();\n};\n"
}
]
}
},
"jsDocTypeExamples": [
"FlowContext"
],
Expand Down Expand Up @@ -4117,6 +4143,19 @@
}
}
],
"defaultExample": {
"description": "Authenticate and fetch product information",
"codeblock": {
"title": "Authenticate and fetch product information",
"tabs": [
{
"title": "/app/routes/**.ts",
"language": "typescript",
"code": "import type {LoaderFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n const {storefront, liquid} = await authenticate.public.appProxy(request);\n\n if (!storefront) {\n return new Response();\n }\n\n const response = await storefront.graphql(\n `#graphql\n query productTitle {\n products(first: 1) {\n nodes {\n title\n }\n }\n }`,\n );\n const body = await response.json();\n\n const title = body.data.products.nodes[0].title;\n\n return liquid(`Found product ${title} from {{shop.name}}`);\n};\n"
}
]
}
},
"jsDocTypeExamples": [
"AppProxyContextWithSession"
],
Expand Down Expand Up @@ -4412,6 +4451,24 @@
}
}
],
"defaultExample": {
"description": "Authenticate and return offers for the shop",
"codeblock": {
"title": "Authenticate and return offers for the shop",
"tabs": [
{
"title": "/app/routes/**.ts",
"language": "typescript",
"code": "import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';\nimport {json} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\nimport {getOffers} from '../offers.server';\n\n// The loader responds to preflight requests from Shopify\nexport const loader = async ({request}: LoaderFunctionArgs) => {\n await authenticate.public.checkout(request);\n};\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {cors, sessionToken} = await authenticate.public.checkout(request);\n\n const offers = getOffers(sessionToken.dest);\n return cors(json({offers}));\n};\n"
},
{
"title": "/app/offers.server.ts",
"language": "typescript",
"code": "// Most apps would load this from their database\nexport function getOffers(shop: string) {\n const offers: Record<any, any[]> = {\n 'shop.com': [\n {\n id: '1',\n title: '10% off',\n price: 10,\n type: 'percentage',\n },\n {\n id: '2',\n title: 'Free shipping',\n price: 0,\n type: 'shipping',\n },\n ],\n };\n\n return offers[shop];\n}\n"
}
]
}
},
"jsDocTypeExamples": [
"CheckoutContext"
],
Expand Down Expand Up @@ -9457,6 +9514,19 @@
}
}
],
"defaultExample": {
"description": "Update a metafield when a product is updated",
"codeblock": {
"title": "Update a metafield when a product is updated",
"tabs": [
{
"title": "/app/routes/**.ts",
"language": "typescript",
"code": "import {type ActionFunctionArgs} from '@remix-run/node';\n\nimport {authenticate} from '../shopify.server';\n\nexport const action = async ({request}: ActionFunctionArgs) => {\n const {topic, admin, payload} = await authenticate.webhook(request);\n\n switch (topic) {\n case 'PRODUCTS_UPDATE':\n await admin.graphql(\n `#graphql\n mutation setMetafield($productId: ID!, $time: String!) {\n metafieldsSet(metafields: {\n ownerId: $productId\n namespace: \"my-app\",\n key: \"webhook_received_at\",\n value: $time,\n type: \"string\",\n }) {\n metafields {\n key\n value\n }\n }\n }\n `,\n {\n variables: {\n productId: payload.admin_graphql_api_id,\n time: new Date().toISOString(),\n },\n },\n );\n\n return new Response();\n }\n\n throw new Response();\n};\n"
}
]
}
},
"jsDocTypeExamples": [
"WebhookContextWithSession"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {type ActionFunctionArgs, json} from '@remix-run/node';
import {GraphqlQueryError} from '@shopify/shopify-api';

import {authenticate} from '../shopify.server';

export const action = async ({request}: ActionFunctionArgs) => {
const {admin, redirect} = await authenticate.admin(request);

try {
await admin.graphql(
`#graphql
mutation updateProductTitle($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
}
}
}`,
{
variables: {
input: {id: '123', title: 'New title'},
},
},
);

return redirect('/app/product-updated');
} catch (error) {
if (error instanceof GraphqlQueryError) {
return json({errors: error.body?.errors}, {status: 500});
}

return new Response('Failed to update product title', {status: 500});
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ const data: ReferenceEntityTemplateSchema = {
type: 'AuthenticateAdmin',
},
],
defaultExample: {
description: 'Authenticate, run API mutation, and redirect',
codeblock: {
title: 'Authenticate, run API mutation, and redirect',
tabs: [
{
title: '/app/routes/**.ts',
language: 'typescript',
code: './authenticate.admin.doc.example.ts',
},
],
},
},
jsDocTypeExamples: [
'EmbeddedAdminContext',
'AdminApiContext',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {type ActionFunctionArgs} from '@remix-run/node';

import {authenticate} from '../shopify.server';

export const action = async ({request}: ActionFunctionArgs) => {
const {admin, payload} = await authenticate.flow(request);

const customerId = payload.properties.customer_id;

const response = await admin.graphql(
`#graphql
mutation setMetafield($customerId: ID!, $time: String!) {
metafieldsSet(metafields: {
ownerId: $customerId
namespace: "my-app",
key: "last_flow_update",
value: $time,
type: "string",
}) {
metafields {
key
value
}
}
}
`,
{
variables: {
customerId,
time: new Date().toISOString(),
},
},
);
const body = await response.json();

console.log('Updated value', body.data!.metafieldsSet!.metafields![0].value);

return new Response();
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ const data: ReferenceEntityTemplateSchema = {
type: 'AuthenticateFlow',
},
],
defaultExample: {
description: 'Handle a flow action call',
codeblock: {
title: 'Set a metafield on a customer after a flow call',
tabs: [
{
title: '/app/routes/**.ts',
language: 'typescript',
code: './authenticate.flow.doc.example.ts',
},
],
},
},
jsDocTypeExamples: ['FlowContext'],
related: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type {LoaderFunctionArgs} from '@remix-run/node';

import {authenticate} from '../shopify.server';

export const loader = async ({request}: LoaderFunctionArgs) => {
const {storefront, liquid} = await authenticate.public.appProxy(request);

if (!storefront) {
return new Response();
}

const response = await storefront.graphql(
`#graphql
query productTitle {
products(first: 1) {
nodes {
title
}
}
}`,
);
const body = await response.json();

const title = body.data.products.nodes[0].title;

return liquid(`Found product ${title} from {{shop.name}}`);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ const data: ReferenceEntityTemplateSchema = {
type: 'AuthenticateAppProxy',
},
],
defaultExample: {
description: 'Authenticate and fetch product information',
codeblock: {
title: 'Authenticate and fetch product information',
tabs: [
{
title: '/app/routes/**.ts',
language: 'typescript',
code: './authenticate.public.app-proxy.doc.example.ts',
},
],
},
},
jsDocTypeExamples: ['AppProxyContextWithSession'],
related: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Most apps would load this from their database
export function getOffers(shop: string) {
const offers: Record<any, any[]> = {
'shop.com': [
{
id: '1',
title: '10% off',
price: 10,
type: 'percentage',
},
{
id: '2',
title: 'Free shipping',
price: 0,
type: 'shipping',
},
],
};

return offers[shop];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {ActionFunctionArgs, LoaderFunctionArgs} from '@remix-run/node';
import {json} from '@remix-run/node';

import {authenticate} from '../shopify.server';
import {getOffers} from '../offers.server';

// The loader responds to preflight requests from Shopify
export const loader = async ({request}: LoaderFunctionArgs) => {
await authenticate.public.checkout(request);
};

export const action = async ({request}: ActionFunctionArgs) => {
const {cors, sessionToken} = await authenticate.public.checkout(request);

const offers = getOffers(sessionToken.dest);
return cors(json({offers}));
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ const data: ReferenceEntityTemplateSchema = {
type: 'AuthenticateCheckout',
},
],
defaultExample: {
description: 'Authenticate and return offers for the shop',
codeblock: {
title: 'Authenticate and return offers for the shop',
tabs: [
{
title: '/app/routes/**.ts',
language: 'typescript',
code: './authenticate.public.checkout.doc.example.ts',
},
{
title: '/app/offers.server.ts',
language: 'typescript',
code: './authenticate.public.checkout.doc.example.offers.ts',
},
],
},
},
jsDocTypeExamples: ['CheckoutContext'],
related: [],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {type ActionFunctionArgs} from '@remix-run/node';

import {authenticate} from '../shopify.server';

export const action = async ({request}: ActionFunctionArgs) => {
const {topic, admin, payload} = await authenticate.webhook(request);

switch (topic) {
case 'PRODUCTS_UPDATE':
await admin.graphql(
`#graphql
mutation setMetafield($productId: ID!, $time: String!) {
metafieldsSet(metafields: {
ownerId: $productId
namespace: "my-app",
key: "webhook_received_at",
value: $time,
type: "string",
}) {
metafields {
key
value
}
}
}
`,
{
variables: {
productId: payload.admin_graphql_api_id,
time: new Date().toISOString(),
},
},
);

return new Response();
}

throw new Response();
};
Loading

0 comments on commit 06d2b5e

Please sign in to comment.