What permission ffmpeg-static need in AWS Lambda? - aws-lambda

I have this code. It download a image, made a video from it and upload it to S3. It runs on Lambda. Added packages, intalled, zipped, uploaded.
npm install --production
zip -r my-lambda-function.zip ./
But get an error code 126
2023-02-17T09:27:55.236Z 5c845bb6-02c1-41b0-8759-4459591b57b0 INFO Error: ffmpeg exited with code 126
at ChildProcess.<anonymous> (/var/task/node_modules/fluent-ffmpeg/lib/processor.js:182:22)
at ChildProcess.emit (node:events:513:28)
at ChildProcess._handle.onexit (node:internal/child_process:291:12)
2023-02-17T09:27:55.236Z 5c845bb6-02c1-41b0-8759-4459591b57b0 INFO Error: ffmpeg exited with code 126 at ChildProcess.<anonymous> (/var/task/node_modules/fluent-ffmpeg/lib/processor.js:182:22) at ChildProcess.emit (node:events:513:28) at ChildProcess._handle.onexit (node:internal/child_process:291:12)
Do I need to set a specific premission for ffmpeg?
import { PutObjectCommand, S3Client } from '#aws-sdk/client-s3'
import { fromNodeProviderChain } from '#aws-sdk/credential-providers'
import axios from 'axios'
import pathToFfmpeg from 'ffmpeg-static'
import ffmpeg from 'fluent-ffmpeg'
import fs from 'fs'
ffmpeg.setFfmpegPath(pathToFfmpeg)
const credentials = fromNodeProviderChain({
clientConfig: {
region: 'eu-central-1',
},
})
const client = new S3Client({ credentials })
export const handler = async (event, context) => {
try {
let body
let statusCode = 200
const query = event?.queryStringParameters
if (!query?.imgId && !query?.video1Id && !query?.video2Id) {
return
}
const imgId = query?.imgId
const video1Id = query?.video1Id
const video2Id = query?.video2Id
console.log(
`Parameters received, imgId: ${imgId}, video1Id: ${video1Id}, video2Id: ${video2Id}`
)
const imgURL = getFileURL(imgId)
const video1URL = getFileURL(`${video1Id}.mp4`)
const video2URL = getFileURL(`${video2Id}.mp4`)
const imagePath = `/tmp/${imgId}`
const video1Path = `/tmp/${video1Id}.mp4`
const video2Path = `/tmp/${video2Id}.mp4`
const outputPath = `/tmp/${imgId}.mp4`
await Promise.all([
downloadFile(imgURL, imagePath),
downloadFile(video1URL, video1Path),
downloadFile(video2URL, video2Path),
])
await new Promise((resolve, reject) => {
console.log('Input files downloaded')
ffmpeg()
.input(imagePath)
.inputFormat('image2')
.inputFPS(30)
.loop(1)
.size('1080x1080')
.videoCodec('libx264')
.format('mp4')
.outputOptions([
'-tune animation',
'-pix_fmt yuv420p',
'-profile:v baseline',
'-level 3.0',
'-preset medium',
'-crf 23',
'-movflags +faststart',
'-y',
])
.output(outputPath)
.on('end', () => {
console.log('Output file generated')
resolve()
})
.on('error', (e) => {
console.log(e)
reject()
})
.run()
})
await uploadFile(outputPath, imgId + '.mp4')
.then((url) => {
body = JSON.stringify({
url,
})
})
.catch((error) => {
console.error(error)
statusCode = 400
body = error?.message ?? error
})
console.log(`File uploaded to S3`)
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': 'https://tikex.com, https://borespiac.hu',
'Access-Control-Allow-Methods': 'GET',
}
return {
statusCode,
body,
headers,
}
} catch (error) {
console.error(error)
return {
statusCode: 500,
body: JSON.stringify('Error fetching data'),
}
}
}
const downloadFile = async (url, path) => {
try {
console.log(`Download will start: ${url}`)
const response = await axios(url, {
responseType: 'stream',
})
if (response.status !== 200) {
throw new Error(
`Failed to download file, status code: ${response.status}`
)
}
response.data
.pipe(fs.createWriteStream(path))
.on('finish', () => console.log(`File downloaded to ${path}`))
.on('error', (e) => {
throw new Error(`Failed to save file: ${e}`)
})
} catch (e) {
console.error(`Error downloading file: ${e}`)
}
}
const uploadFile = async (path, id) => {
const buffer = fs.readFileSync(path)
const params = {
Bucket: 't44-post-cover',
ACL: 'public-read',
Key: id,
ContentType: 'video/mp4',
Body: buffer,
}
await client.send(new PutObjectCommand(params))
return getFileURL(id)
}
const getFileURL = (id) => {
const bucket = 't44-post-cover'
const url = `https://${bucket}.s3.eu-central-1.amazonaws.com/${id}`
return url
}
Added AWSLambdaBasicExecutionRole-16e770c8-05fa-4c42-9819-12c468cb5b49 permission, with policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:eu-central-1:634617701827:*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:eu-central-1:634617701827:log-group:/aws/lambda/promo-video-composer-2:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeNetworkInterfaces"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"sns:*"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:*"
],
"Resource": [
"*"
]
},
{
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"*"
]
}
]
}
What do I miss?
janoskukoda#Janoss-MacBook-Pro promo-video-composer-2 % ls -l $(which ffmpeg)
lrwxr-xr-x 1 janoskukoda admin 35 Feb 10 12:50 /opt/homebrew/bin/ffmpeg -> ../Cellar/ffmpeg/5.1.2_4/bin/ffmpeg

Related

Gatsby build ignores few images to copy into static folder

I have uploads folder with all my assets. When I run gatsby clean / gatsby build, it copies most of the images except 4 of them. It doesn't throw any error or warning as to what it ignores them. The same command on another docker container copies all of the images. The node/gatsby/npm and package.json are precisely the same on both containers. Am I missing something?
Package.json:
{
"name": "gatsby-starter-hello-world",
"private": true,
"description": "A simplified bare-bones starter for Gatsby",
"version": "0.1.0",
"license": "MIT",
"scripts": {
"build": "gatsby build",
"clean": "gatsby clean",
"develop": "gatsby develop -H 0.0.0.0",
"format": "prettier --write \"**/*.{js,jsx,json,md}\"",
"start": "npm run develop",
"refresh": "curl -X POST http://localhost:8000/__refresh",
"docker": "docker-compose -f docker-dev.yml up",
"serve": "gatsby serve -H 0.0.0.0 --port 8000",
"db:ql": "node graphql-server.js",
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
},
"dependencies": {
"#apollo/react-hooks": "^3.1.2",
"#babel/runtime": "^7.8.4",
"#hot-loader/react-dom": "^16.11.0",
"#use-it/interval": "^0.1.3",
"apollo-boost": "^0.4.7",
"axios": "^0.19.0",
"body-scroll-lock": "^2.6.4",
"bootstrap": "^4.4.1",
"clsx": "^1.0.4",
"cross-env": "^6.0.3",
"final-form": "^4.18.5",
"final-form-calculate": "^1.3.1",
"final-form-set-field-data": "^1.0.2",
"gatsby": "^2.19.10",
"gatsby-image": "^2.2.39",
"gatsby-plugin-alias-imports": "^1.0.5",
"gatsby-plugin-client-side-redirect": "0.0.2",
"gatsby-plugin-lodash": "^3.1.19",
"gatsby-plugin-manifest": "^2.2.40",
"gatsby-plugin-minify-classnames": "^0.1.2",
"gatsby-plugin-offline": "^3.0.33",
"gatsby-plugin-polyfill-io": "^1.1.0",
"gatsby-plugin-react-axe": "^0.3.0",
"gatsby-plugin-react-helmet": "^3.1.21",
"gatsby-plugin-robots-txt": "^1.5.0",
"gatsby-plugin-s3": "^0.3.2",
"gatsby-plugin-sass": "^2.1.27",
"gatsby-plugin-sharp": "^2.4.4",
"gatsby-plugin-sitemap": "^2.2.27",
"gatsby-plugin-webpack-bundle-analyzer": "^1.0.5",
"gatsby-source-filesystem": "^2.1.47",
"gatsby-source-graphql": "^2.5.1",
"gatsby-source-wordpress": "^3.3.1",
"gatsby-transformer-sharp": "^2.3.13",
"gatsby-wordpress-inline-images": "^1.2.1",
"gatsby-wpgraphql-inline-images": "^0.2.5",
"graphql": "^14.6.0",
"graphql-tag": "^2.10.1",
"html-react-parser": "^0.10.0",
"install": "^0.13.0",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.15",
"lodash-es": "^4.17.15",
"moment": "^2.24.0",
"node-sass": "^4.13.0",
"prop-types": "^15.7.2",
"query-string": "^6.8.3",
"react": "^16.12.0",
"react-apollo": "^3.1.2",
"react-autosuggest": "^9.4.3",
"react-bootstrap": "^1.0.0-beta.16",
"react-calendar": "^2.19.2",
"react-datepicker": "^2.11.0",
"react-dates": "^21.8.0",
"react-dom": "^16.12.0",
"react-final-form": "^6.3.5",
"react-helmet": "^5.2.1",
"react-hotjar": "^2.2.0",
"react-icons": "^3.8.0",
"react-image-lightbox": "^5.1.1",
"react-payment-inputs": "^1.0.7",
"react-select": "^3.0.8",
"react-star-ratings": "^2.3.0",
"react-sticky": "^6.0.3",
"styled-components": "^4.4.1"
},
"devDependencies": {
"express": "^4.17.1",
"json-graphql-server": "^2.1.3",
"prettier": "^1.18.2"
},
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby-starter-hello-world"
},
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
},
"browserslist": [
"> 2%",
"last 2 versions",
"IE 11"
],
"prettier": {
"tabWidth": 4,
"semi": false
},
"deploy": "gatsby-plugin-s3 deploy --yes"
}
gatsby-config:
/**
* Configure your Gatsby site with this file.
*
* See: https://www.gatsbyjs.org/docs/gatsby-config/
*/
const fs = require('fs')
const path = require("path")
const _ = require("lodash")
const axios = require("axios")
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
})
const rootPath = path.resolve(__dirname)
// https://gist.github.com/flipace/bed6b89aed5cb0e19cde
function replaceLocalhostBaseUrl(object) {
const newObject = _.clone(object);
_.each(object, (val, key) => {
if (typeof val === 'string' && /(http)(.*)(localhost)(:[0-9]{4})?/.test(val)) {
newObject[key] = val.replace(/(http)(.*)(localhost)(:[0-9]{4})?/, `https://${process.env.WEBSITE_DOMAIN}`)
} else if (typeof val === 'object') {
newObject[key] = replaceLocalhostBaseUrl(val)
} else {
newObject[key] = val
}
});
return newObject;
}
function replaceAbsoluteUrls(contentString){
const regex = /href\s*=\s*(['"])(https?:\/\/.+?)\1/ig
let link
let l
const getLocation = function(href){
let l = document.createElement("a")
l.href = href
return l
}
while((link = regex.exec(contentString)) !== null) {
//only replace urls where localhost or awesomenz.com is mentioned, like uat.awesomenz, dev.awesomenz
if (/localhost|dummy.co.nz/.test(link[2]))
{
l = getLocation(link[2])
contentString = contentString.replace(link[2],
// process.env.SITE_URL +
l.pathname +
"?" +l.href.substring( l.href.indexOf("?")+1 )
);
}
}
return contentString
}
function replaceImagesToLocalEnv(tg){
let fromUrl = "https://uat.dummy.co.nz"
if(process.env.ENV_TYPE == "build")
{
tg.url = tg.url.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.link = tg.link.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.icon = tg.icon.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes.thumbnail = tg.sizes.thumbnail.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes.medium = tg.sizes.medium.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes.medium_large = tg.sizes.medium_large.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes.large = tg.sizes.large.replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes['1536x1536'] = tg.sizes['1536x1536'].replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
tg.sizes['2048x2048'] = tg.sizes['2048x2048'].replace(fromUrl, process.env.MEDIA_BASE_SOURCE_URL)
}
return tg
}
const download_image = async (url, image_path) => {
const imagePathDir = path.dirname(image_path)
await fs.promises.mkdir(imagePathDir, { recursive: true }).catch(console.error);
return axios({
url,
responseType: 'stream',
}).then(
response =>
new Promise((resolve, reject) => {
response.data
.pipe(fs.createWriteStream(image_path))
.on('finish', () => resolve())
.on('error', e => reject(e));
}),
);
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
let wasOgDefaultImageDownloaded = false
let wasOgFrontpageImageDownloaded = false
async function downloadSocialImages(meta) {
if(meta.yoast_wpseo_facebook_image) {
const image = meta.yoast_wpseo_facebook_image
const imageUrl = new URL(image)
await download_image(image, path.join(rootPath, 'public', imageUrl.pathname))
}
if(meta.yoast_wpseo_twitter_image) {
const image = meta.yoast_wpseo_twitter_image
const imageUrl = new URL(image)
await download_image(image, path.join(rootPath, 'public', imageUrl.pathname))
}
if(!wasOgDefaultImageDownloaded && meta.yoast_wpseo_social_defaults.og_default_image) {
wasOgDefaultImageDownloaded = true
const image = meta.yoast_wpseo_social_defaults.og_default_image
const imageUrl = new URL(image)
await download_image(image, path.join(rootPath, 'public', imageUrl.pathname))
}
if(!wasOgFrontpageImageDownloaded && meta.yoast_wpseo_social_defaults.og_frontpage_image) {
wasOgFrontpageImageDownloaded = true
const image = meta.yoast_wpseo_social_defaults.og_frontpage_image
const imageUrl = new URL(image)
await download_image(image, path.join(rootPath, 'public', imageUrl.pathname))
}
}
module.exports = {
siteMetadata: {
title: `dummy`,
// siteUrl is hard coded for use in sitemap.xml (`gatsby-plugin-sitemap`)
siteUrl: process.env.SITE_URL,
},
plugins: [
{
resolve: 'gatsby-plugin-robots-txt',
options: {
host: process.env.SITE_URL,
sitemap: `${process.env.SITE_URL}/sitemap.xml`,
policy: [
{
userAgent: '*',
allow: '/',
disallow: [
'/cart',
'/checkout',
'/filter',
'/filter-tours',
'/manage-booking',
'/mydetails',
'/dev',
'/admin',
'/?flush',
]
},
]
}
},
{
resolve: `gatsby-plugin-polyfill-io`,
options: {
features: [`Array.prototype.find`, 'String.prototype.padStart'],
},
},
`gatsby-plugin-lodash`,
{
resolve: `gatsby-plugin-alias-imports`,
options: {
alias: {
// 'react-dom': '#hot-loader/react-dom',
"entrada-ui": path.join(rootPath, "src/entrada-ui"),
components: path.join(rootPath, "src/components"),
constants: path.join(rootPath, "src/constants"),
queries: path.join(rootPath, "src/queries"),
utils: path.join(rootPath, "src/utils"),
images: path.join(rootPath, "src/images"),
css: path.join(rootPath, "src/css"),
theme: path.join(rootPath, "src/theme"),
},
extensions: [],
},
},
{
resolve: "gatsby-plugin-sass",
options: {
cssLoaderOptions: {
camelCase: true,
},
},
},
{
resolve: `gatsby-plugin-minify-classnames`,
options: {
develop: false, // Enable/Disable on `gatsby develop`
},
},
// {
// resolve: "gatsby-source-graphql",
// options: {
// typeName: "PIH",
// fieldName: "pih",
// url: "https://api.dev.tourcatalogue.co.nz/supplier-graphql-api/query",
// // // HTTP headers
// // headers: {
// // // Learn about environment variables: https://gatsby.dev/env-vars
// // Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
// // },
// // // Additional options to pass to node-fetch
// // fetchOptions: {},
// },
// },
// {
// resolve: "gatsby-source-graphql",
// options: {
// typeName: "WP",
// fieldName: "wp",
// url: "http://localhost:4100/graphql",
// },
// },
// Wordpress
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
// {
// resolve: `gatsby-plugin-manifest`,
// options: {
// name: `gatsby-starter-default`,
// short_name: `starter`,
// start_url: `/`,
// background_color: `#663399`,
// theme_color: `#663399`,
// display: `minimal-ui`,
// icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
// crossOrigin: `use-credentials`,
// },
// },
{
resolve: "gatsby-source-wordpress",
options: {
baseUrl: `${process.env.WORDPRESS_URL}`,
protocol: `${process.env.WP_PROTOCOL}`,
plugins: [
{
resolve: `gatsby-wordpress-inline-images`,
options: {
baseUrl: `${process.env.WORDPRESS_URL}`,
protocol: `${process.env.WP_PROTOCOL}`
// baseUrl: `uat.dummy.co.nz`,
// protocol: `https`
}
}
],
// TODO: Need to have further testing
// searchAndReplaceContentUrls: {
// sourceUrl: `${process.env.SITE_URL}`,
// replacementUrl: "",
// },
hostingWPCOM: false,
useACF: true,
acfOptionPageIds: [],
verboseOutput: false,
perPage: 100,
// Set how many simultaneous requests are sent at once.
concurrentRequests: 10,
includedRoutes: [
"**/categories",
"**/posts",
"**/pages",
"**/media",
"**/tags",
"**/tours",
"**/destinations",
"**/themes",
"**/taxonomies",
"**/announcements",
"**/*/*/menus", // <== Menu api endpoint
"**/*/*/menu-locations", // <== Menu api endpoint
"**/exposeredirections", // <== Exposeredirections api endpoint
],
excludedRoutes: [],
normalizers: normalizers => {
return _.compact([
process.env.MEDIA_BASE_SOURCE_URL ?
{
name: `changeMediaSourceUrls`,
normalizer: async ({
entities,
}) => (
entities.map((entity) => {
if(entity.content && entity.content !== ""){
entity.content = replaceAbsoluteUrls(entity.content)
}
///////////////////////////////////////////////////////////
if(entity.acf)
{
if(entity.acf.gallery)
{
let nLoop = entity.acf.gallery.length
if( nLoop > 0)
{
let tempGallery = []
for(let c=0; c<nLoop; c++){
tempGallery.push( replaceImagesToLocalEnv(entity.acf.gallery[c]) )
}
entity.acf.gallery = tempGallery
}
}
if(entity.acf.card_image)
{
entity.acf.card_image = replaceImagesToLocalEnv(entity.acf.card_image)
}
if(entity.acf.card_map)
{
entity.acf.card_map = replaceImagesToLocalEnv(entity.acf.card_map)
}
if(entity.acf.image)
{
entity.acf.image = replaceImagesToLocalEnv(entity.acf.image)
}
if(entity.acf.info_cards)
{
let nLoop = entity.acf.info_cards.length
if( nLoop > 0)
{
let tempCardsInfo = []
let tmp
for(let c=0; c<nLoop; c++){
tmp = entity.acf.info_cards[c]
tmp.card_image = replaceImagesToLocalEnv(tmp.card_image)
tempCardsInfo.push( tmp )
}
entity.acf.info_cards = tempCardsInfo
}
}
}
///////////////////////////////////////////////////////////
if(entity.__type === 'wordpress__wp_media') {
// Transforms:
// https://uat.dummy.co.nz/wp-content/uploads/2020/01/Placeholder-Image.png
// into:
// http://localhost/wp-content/uploads/2020/01/Placeholder-Image.png
entity.source_url = `${process.env.MEDIA_BASE_SOURCE_URL}/${entity.source_url.split("/").slice(3).join('/')}`
} else if (entity.yoast_meta) {
downloadSocialImages(entity.yoast_meta)
entity.yoast_meta.yoast_schema = typeof entity.yoast_meta.yoast_schema === 'string' ? JSON.parse(entity.yoast_meta.yoast_schema) : entity.yoast_meta.yoast_schema
entity.yoast_meta = replaceLocalhostBaseUrl(entity.yoast_meta)
entity.yoast_meta.yoast_schema = JSON.stringify(entity.yoast_meta.yoast_schema)
}
return entity
})
)
} : undefined,
...normalizers
])
},
},
},
// {
// resolve: 'gatsby-wpgraphql-inline-images',
// options: {
// wordPressUrl: process.env.MEDIA_BASE_SOURCE_URL+'/',
// uploadsUrl: process.env.MEDIA_BASE_SOURCE_URL+'/wp-content/uploads/',
// processPostTypes: ['Page', 'Post', 'CustomPost'],
// graphqlTypeName: 'WPGraphQL',
// httpHeaders: {
// Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
// }
// },
// },
{
resolve: `gatsby-plugin-s3`,
options: {
bucketName: 'static-dummy'
},
},
{
resolve: `gatsby-plugin-sitemap`,
options: {
// See: https://github.com/isaacs/minimatch
// The example below will exclude the single `path/to/page` and all routes beginning with `category`
query: `
{
site {
siteMetadata {
siteUrl
}
}
allSitePage {
edges {
node {
path
}
}
}
}`,
serialize: ({ site, allSitePage }) =>
allSitePage.edges.map(edge => {
return {
url: site.siteMetadata.siteUrl + edge.node.path,
changefreq: `daily`,
priority: 0.7,
}
})
}
},
`gatsby-plugin-client-side-redirect`, // keep it in last in list,
// {
// resolve: 'gatsby-plugin-react-axe',
// options: {
// // Integrate react-axe in production. This defaults to false.
// showInProduction: false,
// },
// },
// {
// resolve: 'gatsby-plugin-webpack-bundle-analyzer',
// options: {
// analyzerPort: 8888,
// production: true,
// },
// }
],
}
env file:
# If you are running `npm run build` in your local, you need to add the port :8080
# to WORDPRESS_URL and MEDIA_BASE_SOURCE_URL
WORDPRESS_URL=localhost
MEDIA_BASE_SOURCE_URL=http://localhost
WP_POD_PUBLIC_URL=https://uat.dummy.co.nz
WP_PROTOCOL=http
ENV_TYPE=build
WEBSITE_DOMAIN=uat.dummy.co.nz
SITE_URL=https://www.uat.dummy.co.nz
GATSBY_CPU_COUNT=logical_cores

Graphql-Shield hijacks errors

In the resolver throw new createError.BadRequest("bad input") error is hijacked by Graphql-shield and shown as
{
"errors": [
{
"message": "Not Authorised!",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"myMutation"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Not Authorised!",
This is the Apollo Server setup
const schema = buildSubgraphSchema([
{ typeDefs: await typeDefs(), resolvers },
]);
const apolloServer = new ApolloServer({
schema: applyMiddleware(schema, permissions),
context: async ({ req, res }) => new AuthenticatedContext(req, res)
});
How do I return the actual error occuring?
I found the solution on shield documentation:
const permissions = shield({
Query: {
...
},
Mutation: {
...
},
}, {allowExternalErrors: true});
The allowExternalErrors option is defaulted to be false according to the documentation.

How do I build a custom graphql query with support for Mongodb aggregations in Strapi?

I tried building a custom graphql query with Strapi as per below:
module.exports = {
definition: `
type flatOnts {
site_name: String
unit_no: String
firstname: String
lastName: String
description: String
isp_name: String
serial_number: String
status: Boolean
}
`,
query: `
flattenOntObj: [flatOnts]
`,
type: {},
resolver: {
Query: {
flattenOntObj: {
description: "Return a flat ont object",
resolverOf: "application::onts.onts.aggregate",
resolver: async (obj, options, ctx) => {
const res = await strapi.api.onts.services.onts.aggregate([
{
$lookup: {
from: "onts",
localField: "ont",
foreignField: "_id",
as: "ont_details",
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
{
$arrayElemAt: ["$ont_details", 0],
},
"$$ROOT",
],
},
},
},
]);
console.log(res);
},
},
},
},
};
However, upon running this in the Graphql playground, I am presented with the "forbidden" error.
Any ideas or pointers?
Appreciate any assistance.
Nevermind, I got it right. I missed a section on the Strapi documentation that explains it: https://strapi.io/documentation/3.0.0-beta.x/concepts/queries.html#custom-queries
In the folder: api/model/services/model.js (my case: api/onts/services/ont.js)
module.exports = {
aggregate: async (aggArray) => {
const res = await strapi.query("ont").model.aggregate(aggArray);
return res;
},
};
and then in api/onts/config/schema.graphql.js:
module.exports = {
definition: `
type flatOnts {
site_name: String
unit_no: String
firstname: String
lastName: String
description: String
isp_name: String
serial_number: String
status: Boolean
}
`,
query: `
flattenOntObj: [flatOnts]
`,
type: {},
resolver: {
Query: {
flattenOntObj: {
description: "Return a flat ont object",
// policies: ["plugins::users-permissions.isAuthenticated"],
resolverOf: "application::onts.onts.find",
resolver: async (obj, options, ctx) => {
const aggregationArray = [
{
$lookup: {
from: "onts",
localField: "ont",
foreignField: "_id",
as: "ont_details",
},
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
{
$arrayElemAt: ["$ont_details", 0],
},
"$$ROOT",
],
},
},
];
const res = await strapi.api.onts.services.onts.aggregate(
aggregationArray
);
return res;
},
},
},
},
};

Graphql Cannot create property 'clientMutationId' error on mutation?

this is the mutation I want to perform:
const GraphQLAddPlayerResponseMutation = mutationWithClientMutationId({
name: 'AddPlayerResponse',
inputFields: {
cdx: { type: new GraphQLNonNull(GraphQLInt) },
},
mutateAndGetPayload: ({cdx}) => {
var cdxAdded = addplayerResponse(cdx);
console.log("cdxAdded = ",cdxAdded)
return cdxAdded;
}, // what u return on mutateAndGetPayload is available on outputFields
outputFields: {
playerResponse: {
type: GraphQLInt,
resolve: ({cdxAdded}) => {
console.log("outputFields cdxAdded = ",cdxAdded)
return cdxAdded
},
},
viewer: {
type: GraphQLUser,
resolve: () => getViewer(),
},
},
});
Can't figure out what's wrong with the code, it logs on the mutateAndPayload:
mutateAndGetPayload: ({cdx}) => {
var cdxAdded = addplayerResponse(cdx);
console.log("cdxAdded = ",cdxAdded)
return cdxAdded;
},
but I think the outputFields is not evaluated since it's not logging in the console and I get this error:
{
"data": {
"addPlayerResponse": null
},
"errors": [
{
"message": "Cannot create property 'clientMutationId' on number '3'",
"locations": [
{
"line": 4,
"column": 3
}
],
"path": [
"addPlayerResponse"
]
}
]
}
Help?
Replace return cdxAdded; by return { cdxAdded }; (wild guess)

Upload file to s3 on client side with rails, carrierwave-direct, and jquery file upload

I keep getting a 403 when trying to upload from the client side. Is this due to not having conditions on the bucket? If I just specify the key - with no accesskey, signature, or policy - it will upload fine.
Bucket policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::example/*"
}
]
}
CORS (open due to being local development)
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Signature generation
///whats returned - in controller
string_to_sign
set_s3_direct_post(photo)
render :json => {
:policy => #policy,
:signature => sig,
:key => Rails.application.secrets.aws_access_key_id,
:success=>true,
:store=> photo.photo.store_dir,
:time => #time_policy,
:time_date => #date_stamp,
:form_data => #s3_direct_post
}
------------------------------------------------------------------
private
def string_to_sign
#time = Time.now.utc
#time_policy = #time.strftime('%Y%m%dT000000Z')
#date_stamp = #time.strftime('%Y%m%d')
ret = {"expiration" => 1.day.from_now.utc.xmlschema,
"conditions" => [
{"bucket" => Rails.application.secrets.aws_bucket},
{"x-amz-credential": "#{Rails.application.secrets.aws_access_key_id}/#{#date_stamp}/us-west-2/s3/aws4_request"},
{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
{"x-amz-date": #time_policy },
]
}
#policy = Base64.encode64(ret.to_json).gsub(/\n/,'').gsub(/\r/,'')
end
def getSignatureKey
kDate = OpenSSL::HMAC.digest('sha256', ("AWS4" + Rails.application.secrets.aws_secret_access_key), #date_stamp)
kRegion = OpenSSL::HMAC.digest('sha256', kDate, 'us-west-2')
kService = OpenSSL::HMAC.digest('sha256', kRegion, 's3')
kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")
end
def sig
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), getSignatureKey, #policy).gsub(/\n|\r/, '')
end
Client:
var self=this;
$(`#song-upload`).fileupload({
url: `https://${self._backend.BUCKET}.s3.amazonaws.com`,
dataType: 'json',
add: function (e, data) {
var data_add = data;
$.ajax({
url: `${self._backend.SERVER_URL}/api/photo/new`,
data: {'authorization': `Bearer ${self._auth.isLoggedIn.getCookie('_auth')}`, post_type: 1, file_name:this.file_name},
type: 'POST',
success: function(data) {
if(data.success){
console.log(data);
self.key = data.key;
self.policy = data.policy;
self.signature = data.signature;
self.store_dir = data.store;
self.upload_time = data.time;
self.upload_date = data.time_date;
data_add.submit();
}
}
});
},
submit: function (e, data) {
data.formData = {key:`${self.store_dir}/${self.file_name}`,AWSAccessKeyId: self.key, "Policy":self.policy, "x-amz-algorithm":"AWS4-HMAC-SHA256","Signature":self.signature,"x-amz-credential":`${self.key}/${self.upload_date}/us-west-2/s3/aws4_request`, "x-amz-date":self.upload_time};
},
progress: function (e, data) {
var progress = Math.floor(((parseInt(data.loaded)*0.9) / (parseInt(data.total))) * 100);
$('#inner-progress').css({'transform':`translateX(${progress}%)`});
$('#progress-text').text(progress);
},
done: function (e, data) {
$('#inner-progress').css({'transform':`translateX(100%)`});
$('#progress-text').text(100);
if(e) console.log(e);
}
});
If someone has this, and is trying to do a javascript upload, try plugging in the values into the html file found here. Amazon will tell you the actual errors, instead of just a 403 response.
I was missing the ["starts-with", "$key", "uploads"] in my base64'd config.
Here's my end configurations:
Bucket Config:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Allow Get",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-development/*"
},
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:user/example"
},
"Action": "s3:*",
"Resource": ["arn:aws:s3:::example-development/*","arn:aws:s3:::example-development"]
}
]
}
Bucket
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>DELETE</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Backend:
string_to_sign
set_s3_direct_post(song)
render :json => {
:policy => #policy,
:signature => sig,
:key => Rails.application.secrets.aws_access_key_id,
:success=>true,
:store=> song.song.store_dir,
:time => #time_policy,
:time_date => #date_stamp,
:form_data => #s3_direct_post
}
def string_to_sign
#time = Time.now.utc
#time_policy = #time.strftime('%Y%m%dT000000Z')
#date_stamp = #time.strftime('%Y%m%d')
ret = {"expiration" => 10.hours.from_now.utc.iso8601,
"conditions" => [
{"bucket" => 'waydope-development'},
{"x-amz-credential": "#{Rails.application.secrets.aws_access_key_id}/#{#date_stamp}/us-west-2/s3/aws4_request"},
{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
{"x-amz-date": #time_policy },
["starts-with", "$key", "uploads"]
]
}
#policy = Base64.encode64(ret.to_json).gsub(/\n|\r/, '')
end
def getSignatureKey
kDate = OpenSSL::HMAC.digest('sha256', ("AWS4" + Rails.application.secrets.aws_secret_access_key), #date_stamp)
kRegion = OpenSSL::HMAC.digest('sha256', kDate, 'us-west-2')
kService = OpenSSL::HMAC.digest('sha256', kRegion, 's3')
kSigning = OpenSSL::HMAC.digest('sha256', kService, "aws4_request")
end
def sig
# sig = Base64.encode64(OpenSSL::HMAC.digest('sha256', getSignatureKey, #policy)).gsub(/\n|\r/, '')
sig = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), getSignatureKey, #policy).gsub(/\n|\r/, '')
end
Client:
var self=this;
$(`#song-upload`).fileupload({
url: `https://${self._backend.BUCKET}.s3.amazonaws.com`,
dataType: 'multipart/form-data',
add: function (e, data) {
var data_add = data;
$.ajax({
url: `${self._backend.SERVER_URL}/api/music/new`,
data: {'authorization': `Bearer ${self._auth.isLoggedIn.getCookie('_waydope')}`, post_type: 1, file_name:this.file_name},
type: 'POST',
success: function(data) {
if(data.success){
console.log(data);
self.key = data.key;
self.policy = data.policy;
self.signature = data.signature;
self.store_dir = data.store;
self.upload_time = data.time;
self.upload_date = data.time_date;
data_add.submit();
}
}
});
},
submit: function (e, data) {
data.formData = {key:`${self.store_dir}/${self.file_name}`, "Policy":self.policy,"X-Amz-Signature":self.signature,"X-Amz-Credential":`${self.key}/${self.upload_date}/us-west-2/s3/aws4_request`,"X-Amz-Algorithm":"AWS4-HMAC-SHA256", "X-Amz-Date":self.upload_time, "acl": "public-read"};
},
progress: function (e, data) {
var progress = Math.floor(((parseInt(data.loaded)*0.9) / (parseInt(data.total))) * 100);
$('#inner-progress').css({'transform':`translateX(${progress}%)`});
$('#progress-text').text(progress);
},
done: function (e, data) {
$('#inner-progress').css({'transform':`translateX(100%)`});
$('#progress-text').text(100);
if(e) console.log(e);
}
});

Resources