I am trying to create an app and within the app the user can install a theme, however, I can't seem to work out why the theme is not being created. It keeps pulling the themes already installed on my store to the console, my code doesn't seem to create a theme that would show up on my shopify store.
server.js
router.post('/api/theme', async (ctx) => {
try {
const results = await fetch("https://" + ctx.cookies.get('shopOrigin') + "/admin/themes.json", {
headers: {
'X-Shopify-Access-Token': ctx.cookies.get('accessToken')
},
})
.then(response => response.json())
.then(json => {
console.log("https://" + ctx.cookies.get('shopOrigin') + "/admin/api/2020-01/themes.json", json);
});
ctx.body = {
data: results
};
} catch (err) {
console.log(err)
}
});
frontend .js file
async function getUser() {
var url = `/api/theme`;
var method = 'post';
const theme = {
theme: {
name: "Lemongrass",
src: "https://codeload.github.com/Shopify/skeleton-theme/zip/master"
}
};
const data = JSON.stringify(theme);
fetch(url, { method: method, body: data})
}
In order to create a theme you need a zip archive of the theme you like to create.
The end point should be /admin/api/2020-01/themes.json and the body should be something like this:
{
"theme": {
"name": "Theme name",
"src": "http://themes.shopify.com/theme.zip",
"role": "unpublished"
}
}
Please refer to https://shopify.dev/docs/admin-api/rest/reference/online-store/theme#create-2020-01 for more information.
At the moment from your code I don't see neither the correct POST request, neither the archive file.
Related
I hope you can help me, I need to send some parameters in json format like this:
{
"InformationA": {
"str_id": 1,
"str_description": "message",
"str_email": "abcd#abcd.com.co"
},
"AddConfiguration": [
{
"int_code": 1,
"str_valor": "32201"
},
{
"int_code": 104,
"str_valor": "https://www.google.com.co/"
},
{
"int_code": 108,
"str_valor": "1"
}
]
}
I am trying to send the json through the angular service in this way but I don't know if it is correct?:
sendData(InformationA,AddConfiguration){
const params = 'InformationA=' +JSON.stringify(InformationA)+'AddConfiguration=' +
JSON.stringify(AddConfiguration);
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}
also create a function in the nodejs backend to see how it would arrive:
#Post('send-data')
async receibeData(#Req() req, #Res() res) {
try {
const data = req.body;
res.status(HttpStatus.OK).json(data)
} catch (err) {
throw err;
}
}
and by console it is printed in this way:
{,…}
InformationA:"
[{"str_id":"1","str_description":"message","str_email":"abcd#abcd.com.co"}]Addconfiguration=
[{"int_code":1,"str_valor":"32201 "},{"int_code":104,"str_valor":"https://www.google.com.co
"},{"int_code":108,"str_valor":"1 "}]"
I am really very new to this and I would like to know how I adapt my data so that it can be sent as requested.
I think you should try to build the JSON object corresponding to your requirement. You should not use JSON.stringify for this purpose. I hope this will help you out.
sendData(InformationA,AddConfiguration) {
const params = {
InformationA: InformationA,
AddConfiguration: AddConfiguration
};
return this.http.post<any>(`${this.route}/send-data`, params , { headers: this.headers });
}
I'm create a hook file with the following information, which is Hooks.js
Hooks.js is working to authenticate an actions with JWT when need it, I dont need it in all servies calls.
As my understanding the syntax to call a hook was app/use route/hooks and those hooks were only applied to and specific route and not globally.
module.exports = {
errorHandler: (context) => {
if (context.error) {
context.error.stack = null;
return context;
}
},
isValidToken: (context) => {
const token = context.params.headers.authorization;
const payload = Auth.validateToken(token);
console.log(payload);
if(payload !== "Invalid" && payload !== "No Token Provided"){
context.data = payload._id;
}
else {
throw new errors.NotAuthenticated('Authentication Error Token');
}
},
isValidDomain: (context) => {
if (
config.DOMAINS_WHITE_LIST.includes(
context.params.headers.origin || context.params.headers.host
)
) {
return context;
}
throw new errors.NotAuthenticated("Not Authenticated Domain");
},
normalizedId: (context) => {
context.id = context.id || context.params.route.id;
},
normalizedCode: (context) => {
context.id = context.params.route.code;
},
};
Then I create a file for services and routes, like the following:
const Hooks = require("../../Hooks/Hooks");
const userServices = require("./user.services");
module.exports = (app) => {
app
.use("/users", {
find: userServices.find,
create: userServices.createUser,
})
.hooks({
before: {
find: [Hooks.isValidDomain],
create: [Hooks.isValidDomain],
},
});
app
.use("/users/:code/validate", {
update: userServices.validateCode,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.normalizedCode],
},
});
app
.use("/users/personal", {
update: userServices.personalInfo,
})
.hooks({
before: {
update: [Hooks.isValidDomain, Hooks.isValidToken],
},
});
};
Why Hooks.isValidToken applies to all my update methods? Even if I'm not calling it?
Please help.
app.hooks registers an application level hook which runs for all services. If you only want it for a specific service and method it needs to be app.service('users').hooks().
I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.
I'm trying to do something like this in my routes file using hapijs + inert plugin
{
method: 'GET',
path: '/l/{path*}',
handler: {
directory: {
path: (req) => {
const defaultDir = '/public';
return getHostInfo(req.headers.host).then((d) => {
if (!d || !d.directory) return defaultDir;
return d.directory;
}).catch((e) => {
return defaultDir;
});
}
}
}
},
path parameter expects a string, array or function which returns a string or array...in my case, my function returns a promise...so it doesn't work.
I tried adding hapi-as-promised package which modifies reply function to support then method but did't work.
Basically I want to serve static assets from one directory or another depending on the host header value.
Well, the only thing i have in my mind is this hack. First, you have to make an extension point to make your async stuff:
server.ext({
type: `onPreHandler`,
method: (request, reply) => {
const defaultDir = '/public';
return getHostInfo(request.headers.host).then((d) => {
if (!d || !d.directory) {
request.app.dirPath = defaultDir;
return;
}
request.app.dirPath = d.directory;
}).catch((e) => {
request.app.dirPath = defaultDir;
}).then(() => {
return reply.continue();
});
}
});
And then the route:
{
method: `get`,
path: `/l/{path*}`,
handler: {
directory: {
path: (request) => {
return request.app.dirPath;
}
}
}
}
I don't know how right is this way, but i tested and it worked. So i hope this helps. And i gotta notice, using node to server files on production isn't a common way by some reason.
I feel like pulling my hair out; this is either super simple and i'm having brain freeze or it is not that simple.
What I want
I am trying to unshorten a shortened URL using firebase, when a user goes to:
myapp.firebaseappurl.com/url/SHORTENEDLINK
SO wont let me add a shortened URL
I would like the output to be:
{
"url": "https://stackoverflow.com/questions/45420989/sphinx-search-how-to-use-an-empty-before-match-and-after-match"
}
What I have tried
firebase.json file:
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/:item",
"destination": "/url/:item"
} ]
}
}
index.js file:
const functions = require('firebase-functions');
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.url;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
response.send(httpResponse.headers.location || uri);
}
);
});
Result
When I go to myapp.firebaseappurl.com/url/SHORTENEDLINK I get the following:
Error: could not handle the request
You are seeing Error: could not handle the request since there probably was an exception and it timed out.
Check your logs using:
firebase functions:log
Refer docs for more details
Here's how I got URL unshortening to work
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const http = require('http');
const urlP = require('url');
const unshorten = (url, cb) => {
const _r = http.request(
Object.assign(
{},
urlP.parse(url),
{
method: 'HEAD',
}
),
function(response) {
cb(null, response.headers.location || url);
}
);
_r.on('error', cb);
_r.end();
};
const resolveShortUrl = (uri, cb) => {
unshorten(uri, (err, longUrl) => {
if (longUrl === uri) {
cb(null, longUrl);
} else {
resolveShortUrl(longUrl, cb);
}
});
};
exports.url = functions.https.onRequest((requested, response) => {
var uri = requested.query.url;
resolveShortUrl(uri, (err, url) => {
if (err) {
// handle err
} else {
response.send({ url });
}
});
});
You can follow the hello world example straight away and use the above code as your function.
Above code uses HEAD requests to peek into 'Location` field of the headers and decides if the url can be further unshortened.
This is lighter as HEAD requests ask for no body (thereby avoiding body parsing). Also, no third party lib required!
Also note that the url passed as a query param. So the request would be
http://<your_firebase_server>/url?url=<short_url>
Saves you the trouble of URL re-writes. Plus semantically makes a little more sense.
Did you tried using { source: '/url/**' } syntax?
You can use something like this;
{
"hosting": {
"public": "public",
"rewrites": [ {
"source": "/url/**",
"function": "/url"
}]
}
}
and then you can parse the url from the request.
exports.url = functions.https.onRequest((req, res) => {
// parse the url from the req and redirect to the correct link
});
You should try this in the firebase.json, its worked for me:
"source": "/**",
I also tried "source": "/url/**" but its not worked.
I think your code is fine. What you're doing incorrectly is that you're using Express-js notations in your firebase.json's rewrites node. (the :item part). These don't work in the Firebase Realtime Database.
So, instead of doing that, change your firebase.json to the following :-
{
"hosting": {
"public": "public",
"rewrites": {
"source": "YOUR SHORTENED URL",
"destination": "YOUR ORIGINAL URL"
}
}
}
This is also the advocated approach in the Cloud Functions for Firebase's URL Shortener sample.
First make sure you are receiving the request properly with the shortened url.
const functions = require('firebase-functions');
const express = require('express');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
console.log(req.path);
res.send(JSON.stringify(req.path));
});
exports.url = functions.https.onRequest(express_app);
Now when you visit myapp.firebaseappurl.com/url/SHORTENEDLINK you should see the SHORTENEDLINK in plain text. When that's working, try the redirect.
const functions = require('firebase-functions');
const express = require('express');
const request = require('request');
var express_app = express();
express_app.use(body_parser.text({type: ()=>true}));
express_app.all('*', (req, res) => {
var url = req.path;
request({
uri: uri,
followRedirect: true
},
function(err, httpResponse) {
if (err) {
return console.error(err);
}
res.send(httpResponse.headers.location || uri);
}
);
});
exports.url = functions.https.onRequest(express_app);
Also it's good practice to npm install with --save so they end up in the packages.json. While firebase copies your node_modules folder, most other SaaS platforms run npm install.