diff --git a/api/graphql.js b/api/graphql.js index 5c68821..b2b82f6 100644 --- a/api/graphql.js +++ b/api/graphql.js @@ -1,10 +1,5 @@ -const express = require("express"); -const { ApolloServer } = require("apollo-server-express"); - +const { ApolloServer } = require("../src/server/apollo-server-vercel"); const { config } = require("../src/server"); const server = new ApolloServer(config); -const app = express(); -server.applyMiddleware({ app }); - -module.exports = app; +module.exports = server.createHandler(); diff --git a/package.json b/package.json index 4b7405b..8ac4050 100644 --- a/package.json +++ b/package.json @@ -3,17 +3,20 @@ "version": "0.1.0", "private": true, "dependencies": { + "@apollo/react-hooks": "^3.1.5", + "@apollographql/graphql-playground-html": "^1.6.24", "@chakra-ui/core": "^0.7.0", "@emotion/core": "^10.0.28", "@emotion/styled": "^10.0.27", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "apollo-boost": "^0.4.7", "apollo-server": "^2.12.0", - "apollo-server-express": "^2.12.0", + "apollo-server-core": "^2.12.0", + "apollo-server-env": "^2.4.3", "dataloader": "^2.0.0", "emotion-theming": "^10.0.27", - "express": "^4.17.1", "graphql": "^15.0.0", "mysql2": "^2.1.0", "react": "^16.13.1", diff --git a/src/App.js b/src/App.js index 3f841dc..aa4e4ca 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,22 @@ import React from "react"; +import { ApolloProvider } from "@apollo/react-hooks"; import { CSSReset, ThemeProvider, theme } from "@chakra-ui/core"; import WardrobePage from "./WardrobePage"; +import ApolloClient from "apollo-boost"; + +const client = new ApolloClient({ + uri: "/api/graphql", +}); + function App() { return ( - - - - + + + + + + ); } diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 4db7ebc..0000000 --- a/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/WardrobePage.js b/src/WardrobePage.js index f0cc9f0..7af1483 100644 --- a/src/WardrobePage.js +++ b/src/WardrobePage.js @@ -1,4 +1,6 @@ import React from "react"; +import gql from "graphql-tag"; +import { useQuery } from "@apollo/react-hooks"; import { Box, Editable, @@ -25,6 +27,16 @@ import useOutfitState from "./useOutfitState.js"; import { ITEMS } from "./data"; function WardrobePage() { + const { loading, error, data: datax } = useQuery(gql` + query { + items(ids: [38913, 38911]) { + id + name + } + } + `); + console.log(loading, error, datax); + const [data, wearItemRaw] = useOutfitState(); const [searchQuery, setSearchQuery] = React.useState(""); diff --git a/src/server/apollo-server-vercel.js b/src/server/apollo-server-vercel.js new file mode 100644 index 0000000..9e9bb42 --- /dev/null +++ b/src/server/apollo-server-vercel.js @@ -0,0 +1,209 @@ +// Adapted from https://github.com/apollographql/apollo-server/blob/201630ad284754248fc9ab6ebedc7506fcc3d951/packages/apollo-server-lambda/src/ApolloServer.ts + +const { ApolloServerBase, runHttpQuery } = require("apollo-server-core"); +const { Headers } = require("apollo-server-env"); +const { + renderPlaygroundPage, +} = require("@apollographql/graphql-playground-html"); + +function graphqlVercel(options) { + if (!options) { + throw new Error("Apollo Server requires options."); + } + + if (arguments.length > 1) { + throw new Error( + `Apollo Server expects exactly one argument, got ${arguments.length}` + ); + } + + const graphqlHandler = (req, res) => { + if (req.httpMethod === "POST" && !req.body) { + return res.status(500).send("POST body missing."); + } + runHttpQuery([req, res], { + method: req.method, + options: options, + query: req.method === "POST" && req.body ? req.body : req.query, + request: { + url: req.path, + method: req.method, + headers: new Headers(req.headers), + }, + }).then( + ({ graphqlResponse, responseInit }) => { + setHeaders(res, new Headers(responseInit.headers)).send( + graphqlResponse + ); + }, + (error) => { + if ("HttpQueryError" !== error.name) { + console.error(error); + return; + } + setHeaders(res, new Headers(error.headers)) + .status(error.statusCode) + .send(error.message); + } + ); + }; + + return graphqlHandler; +} + +class ApolloServer extends ApolloServerBase { + // If you feel tempted to add an option to this constructor. Please consider + // another place, since the documentation becomes much more complicated when + // the constructor is not longer shared between all integration + constructor(options) { + if (process.env.ENGINE_API_KEY || options.engine) { + options.engine = { + sendReportsImmediately: true, + ...(typeof options.engine !== "boolean" ? options.engine : {}), + }; + } + super(options); + } + + // This translates the arguments from the middleware into graphQL options It + // provides typings for the integration specific behavior, ideally this would + // be propagated with a generic to the super class + createGraphQLServerOptions(req, res) { + return super.graphQLServerOptions({ req, res }); + } + + createHandler({ cors } = { cors: undefined }) { + // We will kick off the `willStart` event once for the server, and then + // await it before processing any requests by incorporating its `await` into + // the GraphQLServerOptions function which is called before each request. + const promiseWillStart = this.willStart(); + + const corsHeaders = new Headers(); + + if (cors) { + if (cors.methods) { + if (typeof cors.methods === "string") { + corsHeaders.set("access-control-allow-methods", cors.methods); + } else if (Array.isArray(cors.methods)) { + corsHeaders.set( + "access-control-allow-methods", + cors.methods.join(",") + ); + } + } + + if (cors.allowedHeaders) { + if (typeof cors.allowedHeaders === "string") { + corsHeaders.set("access-control-allow-headers", cors.allowedHeaders); + } else if (Array.isArray(cors.allowedHeaders)) { + corsHeaders.set( + "access-control-allow-headers", + cors.allowedHeaders.join(",") + ); + } + } + + if (cors.exposedHeaders) { + if (typeof cors.exposedHeaders === "string") { + corsHeaders.set("access-control-expose-headers", cors.exposedHeaders); + } else if (Array.isArray(cors.exposedHeaders)) { + corsHeaders.set( + "access-control-expose-headers", + cors.exposedHeaders.join(",") + ); + } + } + + if (cors.credentials) { + corsHeaders.set("access-control-allow-credentials", "true"); + } + if (typeof cors.maxAge === "number") { + corsHeaders.set("access-control-max-age", cors.maxAge.toString()); + } + } + + return (req, res) => { + // Make a request-specific copy of the CORS headers, based on the server + // global CORS headers we've set above. + const requestCorsHeaders = new Headers(corsHeaders); + + if (cors && cors.origin) { + const requestOrigin = req.headers["origin"]; + if (typeof cors.origin === "string") { + requestCorsHeaders.set("access-control-allow-origin", cors.origin); + } else if ( + requestOrigin && + (typeof cors.origin === "boolean" || + (Array.isArray(cors.origin) && + requestOrigin && + cors.origin.includes(requestOrigin))) + ) { + requestCorsHeaders.set("access-control-allow-origin", requestOrigin); + } + + const requestAccessControlRequestHeaders = + req.headers["access-control-request-headers"]; + if (!cors.allowedHeaders && requestAccessControlRequestHeaders) { + requestCorsHeaders.set( + "access-control-allow-headers", + requestAccessControlRequestHeaders + ); + } + } + + // Convert the `Headers` into an object which can be spread into the + // various headers objects below. + // Note: while Object.fromEntries simplifies this code, it's only currently + // supported in Node 12 (we support >=6) + const requestCorsHeadersObject = Array.from(requestCorsHeaders).reduce( + (headersObject, [key, value]) => { + headersObject[key] = value; + return headersObject; + }, + {} + ); + + if (res.method === "OPTIONS") { + setHeaders(res, requestCorsHeadersObject).status(204).send(""); + } + + if (this.playgroundOptions && req.method === "GET") { + const acceptHeader = req.headers["accept"]; + if (acceptHeader && acceptHeader.includes("text/html")) { + const path = req.path || "/"; + + const playgroundRenderPageOptions = { + endpoint: path, + ...this.playgroundOptions, + }; + + return setHeaders(res, { + "Content-Type": "text/html", + ...requestCorsHeadersObject, + }).send(renderPlaygroundPage(playgroundRenderPageOptions)); + } + } + + graphqlVercel(async () => { + // In a world where this `createHandler` was async, we might avoid this + // but since we don't want to introduce a breaking change to this API + // (by switching it to `async`), we'll leverage the + // `GraphQLServerOptions`, which are dynamically built on each request, + // to `await` the `promiseWillStart` which we kicked off at the top of + // this method to ensure that it runs to completion (which is part of + // its contract) prior to processing the request. + await promiseWillStart; + return this.createGraphQLServerOptions(req, res); + })(req, res); + }; + } +} + +function setHeaders(res, headers) { + for (const [name, value] of headers.entries()) { + res.setHeader(name, value); + } + return res; +} + +module.exports = { ApolloServer }; diff --git a/src/server/db.js b/src/server/db.js index 967476c..785ede2 100644 --- a/src/server/db.js +++ b/src/server/db.js @@ -1,10 +1,5 @@ const mysql = require("mysql2/promise"); -console.log( - process.env["IMPRESS_MYSQL_USER"], - process.env["IMPRESS_MYSQL_PASSWORD"] -); - async function connectToDb() { const db = await mysql.createConnection({ host: "impress.openneo.net", diff --git a/yarn.lock b/yarn.lock index 0651a2b..12a0a8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,6 +21,24 @@ "@types/node" "^10.1.0" long "^4.0.0" +"@apollo/react-common@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@apollo/react-common/-/react-common-3.1.4.tgz#ec13c985be23ea8e799c9ea18e696eccc97be345" + integrity sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA== + dependencies: + ts-invariant "^0.4.4" + tslib "^1.10.0" + +"@apollo/react-hooks@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@apollo/react-hooks/-/react-hooks-3.1.5.tgz#7e710be52461255ae7fc0b3b9c2ece64299c10e6" + integrity sha512-y0CJ393DLxIIkksRup4nt+vSjxalbZBXnnXxYbviq/woj+zKa431zy0yT4LqyRKpFy9ahMIwxBnBwfwIoupqLQ== + dependencies: + "@apollo/react-common" "^3.1.4" + "@wry/equality" "^0.1.9" + ts-invariant "^0.4.4" + tslib "^1.10.0" + "@apollographql/apollo-tools@^0.4.3": version "0.4.7" resolved "https://registry.yarnpkg.com/@apollographql/apollo-tools/-/apollo-tools-0.4.7.tgz#6ba9e0fa872128fbfc82a5ded1447fde932a0169" @@ -28,7 +46,7 @@ dependencies: apollo-env "^0.6.4" -"@apollographql/graphql-playground-html@1.6.24": +"@apollographql/graphql-playground-html@1.6.24", "@apollographql/graphql-playground-html@^1.6.24": version "1.6.24" resolved "https://registry.yarnpkg.com/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.24.tgz#3ce939cb127fb8aaa3ffc1e90dff9b8af9f2e3dc" integrity sha512-8GqG48m1XqyXh4mIZrtB5xOhUwSsh1WsrrsaZQOEYYql3YN9DEu9OOSg0ILzXHZo/h2Q74777YE4YzlArQzQEQ== @@ -1885,6 +1903,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== +"@types/node@>=6": + version "13.13.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54" + integrity sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A== + "@types/node@^10.1.0": version "10.17.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.21.tgz#c00e9603399126925806bed2d9a1e37da506965e" @@ -1999,6 +2022,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/zen-observable@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" + integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== + "@typescript-eslint/eslint-plugin@^2.10.0": version "2.24.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz#a86cf618c965a462cddf3601f594544b134d6d68" @@ -2193,7 +2221,15 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" -"@wry/equality@^0.1.2": +"@wry/context@^0.4.0": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.4.4.tgz#e50f5fa1d6cfaabf2977d1fda5ae91717f8815f8" + integrity sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag== + dependencies: + "@types/node" ">=6" + tslib "^1.9.3" + +"@wry/equality@^0.1.2", "@wry/equality@^0.1.9": version "0.1.11" resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA== @@ -2388,6 +2424,21 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apollo-boost@^0.4.7: + version "0.4.7" + resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.4.7.tgz#b0680ab0893e3f8b1ab1058dcfa2b00cb6440d79" + integrity sha512-jfc3aqO0vpCV+W662EOG5gq4AH94yIsvSgAUuDvS3o/Z+8Joqn4zGC9CgLCDHusK30mFgtsEgwEe0pZoedohsQ== + dependencies: + apollo-cache "^1.3.4" + apollo-cache-inmemory "^1.6.5" + apollo-client "^2.6.7" + apollo-link "^1.0.6" + apollo-link-error "^1.0.3" + apollo-link-http "^1.3.1" + graphql-tag "^2.4.2" + ts-invariant "^0.4.0" + tslib "^1.10.0" + apollo-cache-control@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/apollo-cache-control/-/apollo-cache-control-0.9.1.tgz#2af4c53ef697a87808285a25f2b93ca0f183880c" @@ -2396,6 +2447,39 @@ apollo-cache-control@^0.9.1: apollo-server-env "^2.4.3" graphql-extensions "^0.11.1" +apollo-cache-inmemory@^1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a" + integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA== + dependencies: + apollo-cache "^1.3.4" + apollo-utilities "^1.3.3" + optimism "^0.10.0" + ts-invariant "^0.4.0" + tslib "^1.10.0" + +apollo-cache@1.3.4, apollo-cache@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42" + integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA== + dependencies: + apollo-utilities "^1.3.3" + tslib "^1.10.0" + +apollo-client@^2.6.7: + version "2.6.8" + resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537" + integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw== + dependencies: + "@types/zen-observable" "^0.8.0" + apollo-cache "1.3.4" + apollo-link "^1.0.0" + apollo-utilities "1.3.3" + symbol-observable "^1.0.2" + ts-invariant "^0.4.0" + tslib "^1.10.0" + zen-observable "^0.8.0" + apollo-datasource@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/apollo-datasource/-/apollo-datasource-0.7.0.tgz#2a6d82edb2eba21b4ddf21877009ba39ff821945" @@ -2443,7 +2527,34 @@ apollo-graphql@^0.4.0: apollo-env "^0.6.4" lodash.sortby "^4.7.0" -apollo-link@^1.2.14: +apollo-link-error@^1.0.3: + version "1.1.13" + resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.13.tgz#c1a1bb876ffe380802c8df0506a32c33aad284cd" + integrity sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg== + dependencies: + apollo-link "^1.2.14" + apollo-link-http-common "^0.2.16" + tslib "^1.9.3" + +apollo-link-http-common@^0.2.16: + version "0.2.16" + resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc" + integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg== + dependencies: + apollo-link "^1.2.14" + ts-invariant "^0.4.0" + tslib "^1.9.3" + +apollo-link-http@^1.3.1: + version "1.5.17" + resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.17.tgz#499e9f1711bf694497f02c51af12d82de5d8d8ba" + integrity sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg== + dependencies: + apollo-link "^1.2.14" + apollo-link-http-common "^0.2.16" + tslib "^1.9.3" + +apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.14: version "1.2.14" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== @@ -2565,7 +2676,7 @@ apollo-tracing@^0.9.1: apollo-server-env "^2.4.3" graphql-extensions "^0.11.1" -apollo-utilities@^1.0.1, apollo-utilities@^1.3.0: +apollo-utilities@1.3.3, apollo-utilities@^1.0.1, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c" integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw== @@ -5602,7 +5713,7 @@ graphql-subscriptions@^1.0.0: dependencies: iterall "^1.2.1" -graphql-tag@^2.9.2: +graphql-tag@^2.4.2, graphql-tag@^2.9.2: version "2.10.3" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03" integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA== @@ -8071,6 +8182,13 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" +optimism@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.3.tgz#163268fdc741dea2fb50f300bedda80356445fd7" + integrity sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw== + dependencies: + "@wry/context" "^0.4.0" + optimize-css-assets-webpack-plugin@5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" @@ -10824,7 +10942,7 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.4: +symbol-observable@^1.0.2, symbol-observable@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== @@ -11026,7 +11144,7 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -ts-invariant@^0.4.0: +ts-invariant@^0.4.0, ts-invariant@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" integrity sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==