How to render HTML to the user of a step function endpoint? - serverless-framework

I'm using serverless and https://github.com/horike37/serverless-step-functions to try and implement a system that is hit by a user, returns HTML based on a database entry for the params provided and then moves to a second function that writes to the database (without forcing the user to wait).
I think a step function in the right approach but I can't seem to get it to return HTML - it always returns a JSON body with the executionArn and startDate. e.g.
{
"executionArn": "arn:aws:states:us-west-2:.......etc...",
"startDate": 1513831673.779
}
Is it possible to have my html body return? At the moment my lambda function returns a simple h1 tag:
'use strict';
module.exports.requestHandler = (event, context, callback) => {
const response = {
statusCode: 200,
headers: {
'Content-Type': 'text/html'
},
body: `<h1>Success!</h1>`,
};
callback(null, response);
};
This is the state machine I'm aiming to create.

I would suggest going for a react/angular/vue frontend hosted e.g. on S3/CDN that uses serverless for backend queries only, instead of rendering dynamic HTML through Lambdas. The 'standard' approach allows you to build apps that are much more responsive and can benefit from e.g. CDNs.
See e.g. https://www.slideshare.net/mitocgroup/serverless-microservices-real-life-story-of-a-web-app-that-uses-angularjs-aws-lambda-and-more or https://serverless-stack.com/

Related

How to use a private API key with Nuxt (on the client)?

Problem Solved
If you're struggling with the same issue, look at the accepted answer which is one way to achieve it by using serverMiddleware
I'm using an API which required a private key. I've stored the key inside a .env file, and called it in the nuxt configuration file, like this :
privateRuntimeConfig: {
secretKey: process.env.MY_SECRET_KEY
},
My API call is done inside the asyncData() hook on my index page. It works fine when i load this page, or reload it, but everytime i use the navigation to come back to this page, i end up with an error (I use a buffer to convert my API key to base64)
First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.
After some research and debugging, i found out that my private key wasn't available at the time, and the "secret" value used in my api call was "undefined".
The thing I don't get is why is this working on initial load / reload but not on page navigation ? And is there a way to fix it without using a backend ? (SSR for SEO and the ability to use private keys without exposing them are the main reasons why i used Nuxt for my project)
Here is my code :
async asyncData({ $content, store, $config }) {
const secret = Buffer.from($config.secretKey).toString('base64')
const request = await fetch('https://app.snipcart.com/api/products', {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
})
const result = await request.json()
store.commit('products/addProducts', result)
const stocks = store.getters['products/getProducts']
return { stocks }
},
Update
Looking at the #nuxtjs/snipcart module's key key and since it's a buildModules, you can totally put it there since it will be available only during the build (on Node.js only)!
For more info, Snipcart do have a lot of blog posts, this one based on Nuxt may help clearing things up: https://www.storyblok.com/tp/how-to-build-a-shop-with-nuxt-storyblok-and-snipcart
You do have your key initially because you're reaching the server when you enter the page or hard refresh it.
If you navigate after the hydration, it will be a client side navigation so you will not be able to have access to the private key. At the end, if your key is really private (nowadays, some API provide keys that can be exposed), you'll need to work around it in some ways.
Looking at Snipcart: https://docs.snipcart.com/v3/api-reference/authentication, it clearly states that the key should be available in
Appear in your compiled front-end assets (HTML, JavaScript)
Meanwhile, if you need to make another call to your backend (trying to access something else than products), you'll need to make a second call.
With Nuxt2, you cannot reach for the backend each time as of right now since you will stay in an SPA context (Nuxt is a server then client Vue app basically). But you could write down the token into a cookie or even better, use a backend as a proxy to hide this specific key (or even a serverless function).
Some more info can be found on my other answer here: https://stackoverflow.com/a/69575243/8816585
Thanks #kissu for your (very) quick answer :)
So, based on what you said and your other answer on the subject, i've made a server Middleware in Nuxt in my server folder.
server/snipcart.js
const bodyParser = require('body-parser')
const axios = require('axios')
const app = require('express')()
app.use(bodyParser.json())
app.all('/getProducts', (request, response) => {
const url = 'https://app.snipcart.com/api/products'
const secret = Buffer.from(process.env.SNIPCART_SECRET).toString('base64')
const config = {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
}
axios
.get(url, config)
.then(res => {
const products = {}
res.data.items.forEach(
item => {
const productId = item.userDefinedId.replace(/-/g, '')
const stocks = {}
item.variants.forEach(
variant => {
const size = variant.variation[0].option
const stock = variant.stock
stocks[size] = stock
}
)
products[productId] = stocks
}
)
response.json(products)
})
.catch( err => response.json(err) )
})
module.exports = app
Correct me if i'm wrong, but I think that's basically the same as using a server as a proxy right ? Based on Nuxt lifecycle hooks, the serverMiddleware one is only run on the server, so my API key shouldn't be exposed to the client ? (I still need to do some refactoring to clean the code, but at least it's working) (https://nuxtjs.org/docs/concepts/nuxt-lifecycle/#server & https://nuxtjs.org/docs/configuration-glossary/configuration-servermiddleware/)
nuxt.config.js
serverMiddleware: [
{ path: "/server", handler: "~/server/snipcart.js" }
]
index.vue (where my snipcart API call was previously made, i guess now I should move this call directly from the product card component where the data is needed) :
async asyncData({ $content, store, $axios }) {
await $axios
.get('/server/getProducts')
.then(res => store.commit('products/addProducts', res.data))
.catch(err => console.log(err))
const stocks = store.getters['products/getProducts']
return {stocks, masterplanProducts }
},
PS : Snipcart does provide a public API key, but the use is very limited. In order to access the remaining stock for each product, i have to use the private key (which allows for some other operations, like removing products / accessing orders and such)
UPDATE :
It's not working when the website is fists accessed from any other page than the one one where the API call is, since the store won't have any data from the API call)
Okay, now I feel dumb. I found a way to make it work. I guess taking the time to explain my problem helped me understand how to solve it.
For those who encounter a similar issue, i fixed it by wrapping my API call with a If statement.
if ($config.secretKey) {
const secret = Buffer.from($config.secretKey).toString('base64')
const request = await fetch('https://app.snipcart.com/api/products', {
headers: {
'Authorization': `Basic ${secret}`,
'Accept': 'application/json'
}
})
const result = await request.json()
store.commit('products/addProducts', result)
}
const stocks = store.getters['products/getProducts']
This way, i can just skip the API call and access values from my vuex store.

Fetch Product variant by SKU works in storefront but not in independent JavaScript program

If I put below script in theme.liquid(Shopify storefront), I get expected result:
<script>
const GRAPHQL_URL = 'https://<my-store>/admin/variants/search.json?query=sku:big-mug-black';
const GRAPHQL_BODY = {
'method': 'GET',
'headers': {
'Content-Type': 'application/json',
},
};
fetch(GRAPHQL_URL, GRAPHQL_BODY)
.then(res => res.json())
.then(console.log)
.catch(console.error);
</script>
But If I try to execute same piece of code from JavaScript program, I get 404({errors: "Not Found"})
const GRAPHQL_URL = `https://<my-proxy>.herokuapp.com/https://<my-store>/admin/variants/search.json?query=sku:big-mug-black`;
const STOREFRONT_ACCESS_TOKEN = '<my-token>';
const GRAPHQL_BODY = {
'method': 'GET',
'headers': {
'Authorization': `Basic ${btoa('<my-api-key>' + ':' + '<my-password>')}`,
'X-Shopify-Storefront-Access-Token': STOREFRONT_ACCESS_TOKEN,
'Content-Type': 'application/json',
},
};
fetch(GRAPHQL_URL, GRAPHQL_BODY)
.then(res => res.json())
.then(console.log)
.catch(console.error);
Note: I can fetch all products using same program, so its not an permission issue. Is there something I need to add/remove to achieve same result in my local JavaScript program? Thank you.
I might have solution just for your need.
You gonna need following accesses:
read_products - for finding product
read_script_tags - for read existing script tags
write_script_tags - for writing new script tag
First on your application create console action that will be run automatically every X hours. That action shall download all existing products. You gonna need save them to local database.
Things to remember during downloading all products.
Api limits watch for them in headers (X_SHOPIFY_SHOP_API_CALL_LIMIT)
Pagination, there is variable link in headers (Api pagination)
When you have stored all products locally, you can create search method for it using SKU.
When you have working method you can create find method in your controller. You will use this method to create request from shopify page to your server.
After creating this you can add JS by yourself or even better automate it with script tags.
ScriptTag on Shopify are basically javascript code that you host on your side and they are automatically loaded TO EACH page in shopify (except basket).
POST /admin/api/2021-01/script_tags.json
{
"script_tag": {
"event": "onload",
"src": "https://djavaskripped.org/fancy.js"
}
}
With that you can add javascript that can be used to create find request on your website. And you can return result back.
I created simplified graph for that.
.

Next.js & Ant Design Dragger: File upload fails on deployed instance

I'm trying to build a file upload with Next.js and Ant Design using React.
On localhost, everything works fine. When I deployed the instance and try to upload a file, I get the following error:
Request URL: https://my-app.my-team.now.sh/url/for/test/
Request Method: POST
Status Code: 405
Remote Address: 34.65.228.161:443
Referrer Policy: no-referrer-when-downgrade
The UI that I use looks like the following:
<Dragger {...fileUploadProps}>{renderImageUploadText()}</Dragger>
where fileUploadProps are:
const fileUploadProps = {
name: 'file',
multiple: false,
showUploadList: false,
accept: 'image/png,image/gif,image/jpeg',
onChange(info) {
const { status } = info.file;
if (status === 'done') {
if (info.file.size > 2000000) {
setUploadSizeError('File size is too large');
} else {
handleFieldValue(API_FORM_FIELDS.PICTURE, info);
}
} else if (status === 'error') {
setUploadSizeError(`${info.file.name} file upload failed.`);
}
},
};
I assume, it has to do with the server side rendering of Next.js? On the other hand, it might not, because by the time I navigated to url/for/test it should render on the client.
How do you implemented file uploads with Ant Design and Next.js?
Got this to work by passing the prop action="https://www.mocky.io/v2/5cc8019d300000980a055e76" to the <Upload/> component.
The ANTD Upload component must make a POST request to upload the file. It either makes that POST request to the current url, which results in the 405, or to the url specified by the action prop. https://www.mocky.io/v2/5cc8019d300000980a055e76 works as this url.
Inspired by Trey's answer (and because I didn't want to send data anywhere outside my domain) I made a no-op api route that simply returns a success, and then pointed Ant Design <Upload>'s action to /api/noop
/* pages/api/noop.tsx */
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
res.status(200).end('noop')
}
/* Wherever I'm using <Upload /> */
<Upload
...otherProps
action={'/api/noop'}
>
Note: this is specific to Next.js, which makes it easy to create API routes.
This works for me.
By default, the ANTD upload component makes a POST request to upload the file. So, to avoid this, add a customRequest props on Upload as below.
customRequest={({ onSuccess }) => setTimeout(() => { onSuccess("ok", null); }, 0) }

datatables.net ajax outside angular scope does not call the interceptor

Angular CLI: 7.3.3 | Node: 10.7.0 | OS: linux x64 | Angular: 7.2.6
I need server side pagination, sorting and filtering, my table will get 100.000 records easily.
Project example -> https://github.com/sibelly/angular-with-datatablesnet
This example consumes themoviedb API, it's just for testing, that is not my real scenario. Here in this project, I don't activate the server side option as I said is just for testing the calling to my interceptor, just to prove that with ajax directly the interceptor isn't called.
On my real scenario, I have a Laravel API, which has the methods that expect the pagination, sorting and filtering params, because I need it to be done server side to reach a better performance and usability.
When I call the API endpoint using HttpClient, it calls my error.interceptor.ts normally, but the pagination, sorting and filtering information isn't passing to the URL.
So, I concluded that I need to call it via Ajax when I call using the ajax the params are sent as expected.
But what happens is that the user token expire and I need to redirect to the login page again. And this is done using the interceptors of angular, that is not activated when calling using ajax directly because it is outside angular's scope.
To resume, how can I intercept my ajax calling from datatables.net framework in my angular project?
Or is there another way to make the server side pagination, sorting and filtering?
movie.component.ts
ngOnInit() {
//When calling this method the error.interceptor is activated normally
//this.getMostPopularMovies();
//But when I call direct using the AJAX, the interceptor doesn't work
this.dtOptions = {
ajax: {
url: 'https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=2ed54a614803785fce2d7fe401cc3b21',
params: {
api_key: this.moviedbService.apiKey
},
dataSrc: 'results'
//type: 'GET',
//headers: {"Authorization": 'Bearer ' + JSON.parse(localStorage.getItem('authUser'))["token"]}
},
columns: [
{ data: 'title' },
{ data: 'release_date' }
]
};
}
getMostPopularMovies(){
this.moviedbService.getMostPopularMovies().
subscribe((movies: any) => {
this.moviesList = movies.results;
console.log("====", movies);
}, (error: any) => {
console.error("Erro-> ", error);
});
}
Thanks, guys o/

Logging in HapiJS v16 Models

I've created a HapiJS project, using my own MVC pattern.
When I want to log from inside my controllers in some cases. Currently when I want to log from my controllers I simply invoke request.log. I'm using Good as a logging plugin.
For example:
const user = function(req, res){
// do stuff
req.log(['info'], 'some log info here');
};
module.exports = {
user,
};
How can I log from inside my models where I have no request object? I don't want to have to pass in my request object into the methods of the model.
If you plan to register models as plug in, you will have access to the server object and so, you will be able to use server.methods
EDIT
In my company we declare routes as plug in (see code below)
exports.register = function (server, options, next) {
server.route({
method: 'POST',
path: '/FOO/BAR'
handler(request, reply) {}
});
return next();
};
exports.register.attributes = {
name: 'routes-foobar'
};
And we register as such :
server.register([
require('./route-foo-bar'),
...,
]);
This way we have the server objects in our route
What I would do in your case is register my models as server methods and use them in my routes.
The same goes for logging.
I would register my log function as a server method and call them from inside my models
I don't know if it's the good way to do that but that's a least a working one