How can I build nodes and use them to create pages in gatsby? - node.js

I'm able to run my code in dev just fine and works as expected. Not sure what I'm doing wrong. Am I unable to create nodes and then build pages from them in gatsby-node.js? Is there a better way of doing this in gatsby that I'm not aware of?
The problem is when I try to build I receive error.
"gatsby-node.js" threw an error while running the sourceNodes lifecycle:
Request failed with status code 400
Here is my code:
exports.sourceNodes = async ({
actions,
createNodeId,
createContentDigest,
}) => {
const { createNode } = actions
const auth_token = Buffer.from(
`${client_id}:${client_secret}`,
'utf-8'
).toString('base64')
const token_url = 'https://accounts.spotify.com/api/token'
const data = qs.stringify({ grant_type: 'client_credentials' })
const response = await axios.post(token_url, data, {
headers: {
Authorization: `Basic ${auth_token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
})
const access_token = await response.data.access_token
const spotifyData = await axios.get(
`https://api.spotify.com/v1/artists/${artistId}/albums`,
{
headers: {
Authorization: `Bearer ${access_token}`,
'Content-type': 'application/json',
},
params: {
limit: 50,
market: 'US',
groups: 'single,album',
},
}
)
const ALBUM_NODE_TYPE = `Album`
await spotifyData.data.items.forEach(album =>
createNode({
...album,
id: createNodeId(`${ALBUM_NODE_TYPE}-${album.id}`),
parent: null,
children: [],
internal: {
type: ALBUM_NODE_TYPE,
contentDigest: createContentDigest(album),
},
})
)
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const results = await graphql(`
{
collections: allFile {
distinct(field: sourceInstanceName)
edges {
node {
id
sourceInstanceName
childMarkdownRemark {
id
fields {
slug
}
frontmatter {
title
image
}
}
}
}
}
news: allFile(filter: { sourceInstanceName: { eq: "news" } }) {
nodes {
sourceInstanceName
childMarkdownRemark {
frontmatter {
title
image
blurb
url
}
id
fields {
slug
}
}
}
}
music: allAlbum(sort: { fields: release_date, order: DESC }) {
nodes {
id
href
images {
url
}
name
artists {
name
}
release_date
album_type
external_urls {
spotify
}
}
}
}
`)
// Music
await results.data.music.nodes.forEach(node => {
console.log(node)
if (node.errors) {
node.errors.forEach(e => console.error(e.toString()))
return Promise.reject(node.errors)
}
const id = node.id
const name = node.name.replace(/\s+/g, '_').toLowerCase()
createPage({
path: `music/${name}`,
component: path.resolve(`src/templates/music/music.js`),
// additional data can be passed via context
context: {
id,
field: { ...node },
},
})
})
await results.data.news.nodes.forEach(node => {
if (node.errors) {
node.errors.forEach(e => console.error(e.toString()))
return Promise.reject(node.errors)
}
const id = node.childMarkdownRemark.id
createPage({
path: `${node.sourceInstanceName}${node.childMarkdownRemark.fields.slug}`,
component: path.resolve(
`src/templates/${String(node.sourceInstanceName)}/${String(
node.sourceInstanceName
)}.js`
),
// additional data can be passed via context
context: {
id,
field: { ...node.childMarkdownRemark.frontmatter },
},
})
})
//News Collection
await results.data.collections.distinct.forEach(collection => {
if (collection.errors) {
collection.errors.forEach(e => console.error(e.toString()))
return Promise.reject(collection.errors)
} else {
if (collection !== 'news') {
return
} else {
const edges = results.data.collections.edges
const nodes = edges.filter(
e => e.node.sourceInstanceName === 'news'
)
createPage({
path: `/collections/news`,
component: path.resolve(
`src/templates/news/collections/news.js`
),
context: {
nodes: { ...nodes },
},
})
}
}
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
I also tried wrapping the createPage function in sourceNodes but that didn't work either.

I ended up creating a source-plugin and passing in my Spotify credentials as a string in gatbsy-config.js. It's not able to read env variable at build time but it is when running dev and I'm not sure I understand why that is.

Related

Trouble with return statement in Node Async Await

Trying to write a query for DynamoDB and learn promises etc. The console.log(resp.Items) returns the object that I am looking for so I think my query is formatted correctly. I get a status 200 back with an empty object.
I have read up for a few days and tried to implement various changes to the code by nothing is returning the object resp.Items. I am guessing the function is returning before the const is updated with the data but I am not sure why the console.log works.
const AWS = require('aws-sdk')
const dynamodb = new AWS.DynamoDB()
const getTechs = async () => {
try {
const resp = await dynamodb
.query({
ExpressionAttributeValues: {
':tech': { S: 'TECH#' },
},
KeyConditionExpression: 'PK = :tech',
TableName: process.env.TABLE_NAME,
ScanIndexForward: true,
})
.promise()
console.log(resp.Items)
if (!resp.Items) {
return {
error: 'No Techs in the DB',
}
}
return {
tech: resp.Items,
}
} catch (error) {
console.log('Error retrieving Tech List')
console.log(error)
return {
error: 'Could not retrieve Tech List',
}
}
}
handler func
const { makeHandler } = require('./utils')
const { getTechs } = require('../data')
// const { Tech } = require('../entities')
const inputSchema = {
type: 'object',
properties: {
pathParameters: {
type: 'object',
properties: {
tech: { type: 'string' },
},
required: ['tech'],
},
},
required: ['pathParameters'],
}
const handler = async (event) => {
const { techs, error } = await getTechs()
const statusCode = error ? 500 : 200
const body = error ? JSON.stringify({ error }) : JSON.stringify({ techs })
return {
statusCode,
body,
}
}
module.exports.handler = makeHandler({ handler })
executeTransactWrite func
const executeTransactWrite = async ({ tech, params }) => {
const transactionRequest = tech.transactWriteItems(params)
let cancellationReasons
transactionRequest.on('extractError', (response) => {
try {
cancellationReasons = JSON.parse(
response.httpResponse.body.toString()
).CancellationReasons
} catch (err) {
// suppress this just in case some types of errors aren't JSON parseable
console.error('Error extracting cancellation error', err)
}
})
return new Promise((resolve, reject) => {
transactionRequest.send((err, response) => {
if (err) {
err.cancellationReasons = cancellationReasons
return reject(err)
}
return resolve(response)
})
})
}
module.exports = {
executeTransactWrite,
makehandler func
const middy = require('middy')
const {
jsonBodyParser,
validator,
httpErrorHandler,
} = require('middy/middlewares')
const makeHandler = ({ handler, inputSchema }) =>
middy(handler)
.use(jsonBodyParser())
.use(validator({ inputSchema }))
.use(httpErrorHandler())
module.exports = { makeHandler }

How do I test a sequelize database updating function like this?

Hi I can I test a function like this? I cannot test it properly because it says that relation "facilities" does not exist, however I now it exists because I can add a new record.
I should I test this code:
export async function postMemberController(req, res) {
try {
if (req.decodedToken.user_id || req.decodedToken.id) {
const facility = await db.facility.findOne({
where: { id: req.body.facility_id },
raw: true,
});
if (!facility?.id) {
res.send(
"no facilities"
);
} else {
let member = await db.member.findOne({
where: { id_no: Number(req.body.id_no) },
raw: true,
});
const admin = await db.user.findOne({
where: { facility_id: req.body.facility_id },
raw: true,
});
const data = await db.sequelize.transaction(async t => {
if (member?.id_no) {
await db.member
.update(
{
...req.body,
facility_id: [...new Set([...member.facility_id, req.body.facility_id])],
},
{ where: { id_no: member.id_no } },
{ transaction: t }
)
.catch(e => console.log('error : creation MEMBER??? ', e));
const adminToken = await tokenCreator({ ...admin }, 3);
!req.body?.from &&
(await axios
.put(
`${process.env.CENTRAL_URL}/members`,
{
body: {
...req.body,
facility_id: [...new Set([...member.facility_id, req.body.facility_id])],
from: 'web',
},
where: { id: member.id },
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e.response.data, e.config.url)));
} else {
const auth = adminFirebase.auth;
const firebaseToken = await auth.createCustomToken(generateUUID());
member = await db.member
.create(
{
...req.body,
firebase_token: firebaseToken,
facility_id: [req.body.facility_id],
creator: admin.user_id,
updater: admin.user_id,
},
{ transaction: t }
)
.catch(e => console.log('error : creation MEMBER??? ', e));
if (member) {
const card = await db.card.findOne({
where: { card_id: member.dataValues.card_id },
raw: true,
});
if (card) {
await db.card.update(
{ card_number: req.body.card_number },
{
where: {
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
},
},
{ transaction: t }
);
} else {
const newCard = await db.card.create(
{
card_number: req.body?.card_number,
card_id: member?.dataValues?.card_id,
creator: admin.user_id,
updater: admin.user_id,
facility_id: req.body.facility_id,
credits: 0,
},
{ transaction: t }
);
}
}
const adminToken = await tokenCreator({ ...admin }, 3);
!req.body?.from &&
(await axios
.post(
`${process.env.CENTRAL_URL}/members`,
{
...req.body,
id: member.dataValues.id,
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
facility_id: req.body.facility_id,
from: 'web',
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e, e?.response?.data, e.config.url)));
!req.body?.from &&
(await axios
.put(
`${process.env.CENTRAL_URL}/cards`,
{
body: {
card_number: req.body.card_number.toString(),
facility_id: req.body.facility_id,
from: 'web',
},
where: {
card_id: member.dataValues ? member.dataValues.card_id : member.card_id,
},
},
{
headers: { authorization: 'Bearer ' + adminToken },
}
)
.catch(e => console.log('erorr ', e.response.data, e.config.url)));
}
return member;
});
delete data.dataValues?.password;
delete data?.password;
const memberToken = await tokenCreator({ ...data.dataValues }, 3);
return res
.status(200)
.json({ data, session_id: data.id_no || data.dataValues.id_no, token: memberToken });
}
} else {
return res.sendStatus(401);
}
} catch (error) {
res.status(500).send(error.message);
}
}
this is how I try to test it:
it('post member from web terminal', async () => {
try {
jest.mock('../db/models/index.js', () => {
const SequelizeMock = require('sequelize-mock');
const dbMock = new SequelizeMock();
const facility = dbMock.define('facilities', {
id: '6-30189',
})
const member = dbMock.define('members', {});
const card = dbMock.card('cards', {});
});
const req = {
decodedToken: { user_id: makeId() },
body: member,
};
const res = {
sendStatus(num) {},
status(num) {
return { json(payload) {}, send(payload) {} };
},
};
const request = await postMemberController(req, res);
delete member.from;
expect(request).toHaveBeenCalledWith({ ...member });
} catch (error) {
console.log('ERROR post member ', error);
throw new Error(error);
}
});
How do I write a test for this???? when I call postMemberController inside jest it seems that can't find the relations in the table, I tried to console.log db.facility object from jest test output, there is nothing, not even empty object or null or undefined. I don't understand why? How can I solve this??
Here is the object I am sending:
const card_number = 11111111111111;
const card_id = makeId();
const schedule = makeId();
const club = makeId();
const trainer = makeId();
const user = makeId();
const facility_id = '6-30189';
const member = {
from: 'central',
card_number: card_number,
id: makeId(),
first_name: 'Demo',
last_name: 'Demo',
id_no: card_number,
card_id,
home_phone: null,
urgent_phone: null,
email: null,
registered: true,
schedule,
club,
trainer,
birthplace: 'Gölbaşı/Ankara',
birthdate: new Date().toISOString(),
father_name: null,
mother_name: null,
gender: null,
profession: null,
address: null,
phone_number: null,
hes_code: null,
blood_type: null,
nationality: null,
profile_photo: null,
is_employee: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
firebase_token: null,
file: null,
session_ids: null,
facility_id,
following: null,
creator: user,
updater: user,
};

createAysncThunk in Redux Toolkit catching error when making fetch request

I'm working on the user registration functionality of an application using Typescript and Redux Toolkit. When I make the fetch request to the signup endpoint, a new user is saved to the database I've connected, but I keep entering the catch error block.
export const registerUser = createAsyncThunk(
"user/registerUser",
async (form: { username:string, password:string }, thunkAPI) => {
try {
const response = await fetch('/api/auth/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userInfo: {
username: form.username,
password: form.password
}
}),
});
if (response.status === 200) return 200;
else return 400;
} catch (e) {
console.log('Error')
}
}
);
I've tried logging the response to the console a number of ways
const data = await response.json() console.log(data)
But have had no luck. I think this is an error with how I've done my fetch request using createAsyncThunk but haven't been able to figure out what I've missed.
This is the code for the initial state and slice:
interface UserState {
userProfile: {
id: number | null;
},
registration: {
status: 'loaded' | 'loading'
}
}
const initialState : UserState = {
userProfile: {
id: null,
},
registration: {
status: 'loaded'
}
};
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
},
extraReducers: (builder) => {
builder.addCase(registerUser.fulfilled, (state) => { state.registration.status = 'loaded' }),
builder.addCase(registerUser.rejected, (state) => { state.registration.status = 'loaded' }),
builder.addCase(registerUser.pending, (state) => { state.registration.status = 'loading' })
}
})
And here is the code for the function where the action is dispatched on the UI
const handleRegister= async () => {
if (!username) return alert('Username field was left empty');
if (!password) return alert('Password field was left empty');
const {payload} : any = await dispatch(registerUser({ username: username, password: password}));
if (payload === 200) {
alert('Successfully registered. Redirecting to dashboard');
return navigate('/dashboard');
} else { return alert('User creation unsuccessful'); }
}
Appreciate any help as I've looked through many other posts but haven't been able to resolve my issue.

Error with sending an arrays and an object in a GET API and getting them in the reducer

I'm working on a project where I need to send an arrays and an object from the backend (nodejs) through a GET api to the frontend (reactjs) and have both of those be accessible in my reducer. I have never done this, and I'm not sure if I'm going about it the right way. I am currently getting an error saying that totalPages from this line: export const orderMineListReducer = (state = {orders:[], totalPages}, action) => { is not defined. I would really appreciate any help or advice on how to go about sending a GET api with an arrays and an object and receiving an arrays and an object in the reducer. Thank you!
Below, I have included what I have tried to do so far:
Backend:
orderRouter.js
orderRouter.get(
'/mine',
isAuth,
expressAsyncHandler(async (req, res) => {
const page = req.query.page || 1;
const perPage = 20
const orders = await Order.find({ user: req.user._id }).skip(page * perPage).limit(perPage);
const total = await Order.countDocuments();
const totalPages = Math.ceil(total / perPage).toString();
res.status(200).send({
data:
[orders],
totalPages,
});
}),
);
Frontend
orderReducer.js
export const orderMineListReducer = (state = {orders:[], totalPages}, action) => {
switch (action.type) {
case ORDER_MINE_LIST_REQUEST:
return { ...state, loading: true };
case ORDER_MINE_LIST_SUCCESS:
return { ...state, loading: false, orders: action.payload.orders, totalPages: action.payload.totalPages,};
case ORDER_MINE_LIST_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
orderActions.js
export const listOrderMine = (page) => async (dispatch, getState) => {
dispatch({ type: ORDER_MINE_LIST_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.get(`${BASE_URL}/api/orders/mine?page=${page}`, {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
});
dispatch({ type: ORDER_MINE_LIST_SUCCESS, payload: data });
} catch (error) {
const message = error.response && error.response.data.message ? error.response.data.message : error.message;
dispatch({ type: ORDER_MINE_LIST_FAIL, payload: message });
}
};
I've also tried just having
res.status(200).send({
orders,
totalPages,
});
instead of res.status(200).send({data: { orders, totalPages,}});
with my reducer like so:
export const orderMineListReducer = (state = { data: {} }, action) => {
switch (action.type) {
case ORDER_MINE_LIST_REQUEST:
return { ...state, loading: true };
case ORDER_MINE_LIST_SUCCESS:
return { ...state, loading: false, data: action.payload,};
case ORDER_MINE_LIST_FAIL:
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
however in my OrderHistoryScreen.js where I have
const orderMineList = useSelector((state) => state.orderMineList);
const { loading, data, error,} = orderMineList;
const dispatch = useDispatch();
useEffect(() => { dispatch(listOrderMine());
}, [dispatch]);
I am getting undefined for console.log(data.orders) and empty {} for console.log(data).
Your response has this scheme:
{
data: {
orders,
totalPages
}
}
Axios.get will resolve to an object with this schema:
{
data: {
data: {
orders,
totalPages
}
},
status: 200,
statusText: 'OK',
...
}
So you need to change the destructuring or dispatch data.data like this:
dispatch({ type: ORDER_MINE_LIST_SUCCESS, payload: data.data });
Check the Axios documentation on the response schema: https://axios-http.com/docs/res_schema

React-table. How to server-side filter. Query string

I am using react-table and server-side filtering.
The problem is that when filtering filters are not apply together. If I filter by name and the by category for example, the name filter resets.
the url looks like this:
http://localhost:3001/api/v1/products?pages=0&pageSize=20&filtered[]=%7B%22id%22:%22name%22,%22value%22:%22j%22%7D&filtered[]=%7B%22id%22:%22comment%22,%22value%22:%22f%22%7D".
I don't know if it is normal that filter[]= is called each time I try to filter by different column or if there is a problem in server-side.
This is the controller:
exports.index = function (req, res) {
let filtered = {}
let filters = {}
if (req.query.filtered) {
req.query.filtered.map(result => {
filtered = JSON.parse(result)
})
let id = filtered.id
let value = filtered.value
if (filtered['id'] === 'name' || filtered['id'] === 'comment') {
filters[id] = { '$regex': value, '$options': 'i' }
} else {
filters[id] = value
}
}
Product.listWithCategory(filters).then(response => {
res.json({ result: response })
})
}
React onFetchData:
onFetchData = (state, instance) => {
const jwt = getJwt()
if (!jwt) {
this.props.history.push('/login')
}
console.log(state.filtered)
let config = {
headers: { 'Authorization': `Bearer ${jwt}` },
params: {
pages: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
}
}
this.setState({ loading: true })
axios.get('http://localhost:3001/api/v1/products', config)
.then(response => {
this.setState({
data: response.data.result,
pages: response.data.pages,
loading: false
})
})
}

Resources