I'm trying to create API factory for node.js, Express, Mongoose Tech stack. I also want to provide full type safety using Typescript and the option to add additional validation for creation and updates. For example I have "validator" like that:
type ICheckIfUnique = <M, T extends Model<M>, K extends keyof M>(
attributes: {
model: T;
key: K;
},
body: M
) => void;
export const checkIfUnique: ICheckIfUnique = async (attributes, body) => {
const { model, key } = attributes;
const value = body[key];
const isUnique = !(await model.findOne({ [key]: value }));
if (!isUnique) {
throw `${key} is not unique!`;
}
};
But I can't get the type right as I get:
I can't also find way to get custom model types for the factory methods:
export const factoryCreateEndpoint =
<T, D extends Model<T>>(model: D, additionalLogic?: any) =>
async (req: Request, res: Response): Promise<Response | undefined> => {
const body = req.body;
if (!body) {
return res.status(400).json({
success: false,
error: `You must provide ${capitalize(
model.collection.name.slice(0, -1)
)}.`,
});
}
if (additionalLogic) {
try {
for (const element in additionalLogic) {
const { validator, additionalVariables } = additionalLogic[element];
await validator(additionalVariables, req.body);
}
} catch (error) {
return res.status(400).json({ success: false, error });
}
}
try {
const object = new model(body);
await object.save();
return res.status(201).json({
success: true,
id: object._id,
message: `${capitalize(model.collection.name.slice(0, -1))} created.`,
});
} catch (error) {
return res.status(400).json({ success: false, error });
}
};
But with this generic type I get:
Related
So basically here is the scenario. The user tries to access a protected API link. During the request stage, The Next.js middleware checks if the token is correct, decodes the token and then creates a current user property with response.user and if possible, checks if the exists on the MongoDB database
This is what the middleware.js currently looks like
interface CustomNextResponse extends NextResponse {
request: {
userId: string;
};
}
export async function sign(payload: JwtPayload, secret: string): Promise<string> {
const iat = Math.floor(Date.now() / 1000);
const exp = iat + 60 * 60; // one hour
return new SignJWT({ ...payload })
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
.setExpirationTime(exp)
.setIssuedAt(iat)
.setNotBefore(iat)
.sign(new TextEncoder().encode(secret));
}
export async function verify(token: string, secret: string): Promise<JwtPayload> {
const { payload } = await jwtVerify(token, new TextEncoder().encode(secret));
return payload;
}
export async function middleware(request: NextRequest) {
try {
const refreshToken = request.cookies.get('X-Refresh-Token')?.value;
const requestHeaders = new Headers(request.headers);
const bearerToken = requestHeaders.get('Authorization');
if (!refreshToken) {
throw new Error('Unauthorized');
}
if (!bearerToken) {
throw new Error('Unauthorized');
}
const accessToken = bearerToken.split(' ')[1];
if (!accessToken) {
throw new Error('Unauthorized');
}
const decode: JwtPayload = (await verify(accessToken, secretKey)) as JwtPayload;
const response = NextResponse.next() as CustomNextResponse;
response.request = {
userId: decode.id
}
return response;
} catch (error) {
const errorObject = error as Error;
return new NextResponse(JSON.stringify({ success: false, message: errorObject.message }), {
status: 401,
headers: { 'content-type': 'application/json' },
});
}
}
But in the Nextapi at pages/api/me I can neither access the property with req nor res
async function handler(req: CustomNextApiRequest, res: NextApiResponse) {
//req.userId and res.userId give me undefined
Is it case the case then that middleware cant does not store custom properties to be accessed when making a request the same that Express.js does it? plus can you access MongoDB within the middleware?
Could someone help me please. Do I need to convert the request body into raw json type?
I'm using remix.run to create an endpoint for the stripe webhook. I'm getting an error:
No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing - but I don't know how to convert the req.body into raw?
import type { ActionFunction } from "#remix-run/node";
import stripe from "stripe";
export const action: ActionFunction = async ({ request }) => {
switch (request.method) {
case "POST": {
const endpointSecret =
"whsec_abxxxxaf67fxxxa955";
console.log("Header", request.headers.get("stripe-signature"));
const sig: any = request.headers.get("stripe-signature");
console.log("--sig", sig);
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
endpointSecret
);
console.log("event", event);
return null;
} catch (err) {
console.log("err", err);
return null;
}
}
}
return null;
};
export default () => {
return <p>FAILED</p>;
};
And the error that I'm getting.
{
type: 'StripeSignatureVerificationError',
raw: {
message: 'No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing'
},
rawType: undefined,
code: undefined,
doc_url: undefined,
param: undefined,
detail: undefined,
headers: undefined,
requestId: undefined,
statusCode: undefined,
charge: undefined,
decline_code: undefined,
payment_intent: undefined,
payment_method: undefined,
payment_method_type: undefined,
setup_intent: undefined,
source: undefined,
header: 't=1669990993,v1=026c8a0xxxxxxxfc1048d1abc07,v0=85099acc2420c06bxxx22dd8553e60681befd59d238b4514cbdd',
payload: <ref *1> ReadableStream3 [ReadableStream]
My printed header is show the correct key, i think.
t=1669990993,v1=026c8a00b366cd657a2xxxxxxf5b003fc1048d1abc07,v0=85099acc2420c06bc0d2dxxx3e60681befd59d238b4514cbdd
It looks like you can get the raw request body with request.text() then use that in constructEvent.
const payload = await request.text()
...
event = stripe.webhooks.constructEvent(payload, sig, secret)
You can see this here, an example shared by a Stripe Developer Advocate here.
My working codes
import Stripe from "stripe";
import { insertIntoStripe } from "~/models/stripe.server";
import cuid from "cuid";
import { updatePaymentStatus } from "~/models/order.server";
import Plunk from "#plunk/node";
const stripe = new Stripe(process.env.STRIPE_API, {});
export const action = async ({ request }) => {
const sig = request.headers.get("stripe-signature");
let event;
const payload = await request.text();
try {
event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_KEY
);
} catch (err) {
return new Response(err.message, {
status: 400,
});
}
if (event.type == "payment_intent.succeeded") {
const paymentIntentJson: any = event.data.object;
const paymentIntentID: any = paymentIntentJson.id;
const email: any = paymentIntentJson.charges.data[0].billing_details.email;
const name: any = paymentIntentJson.charges.data[0].billing_details.name;
const amount: any = paymentIntentJson.amount;
const sessionList = await stripe.checkout.sessions.list();
sessionList.data.map(async (session) => {
if (session.payment_intent == paymentIntentID) {
const client_reference_id: any = session.client_reference_id;
await insertIntoStripe(
cuid(),
paymentIntentID,
name,
email,
amount,
client_reference_id,
paymentIntentJson
);
await updatePaymentStatus(client_reference_id, "SUCCESS");
const plunk = new Plunk(process.env.USEPLUNK_EMAIL_API);
//send email
await plunk.events.publish({
email: email,
event: "send-completed-order-email",
data: {
orderId: client_reference_id,
},
});
}
return null;
});
console.log("💰 payment success!");
return new Response("Success", {
status: 200,
});
}
return new Response("Success", {
status: 200,
});
};
i'm junior fronted developer, and i want to your help...
below my pages code
const sendAnotherIp = async () => {
const domain_format = /^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?/;
if (domain_format.test(anotherIp)) {
await axios
.get(`${process.env.NEXT_PUBLIC_CLIENT_URL}/api/dnslookup`, {
params: {
search: anotherIp,
date: nowTime,
},
})
.then((res) => {
alert(res.data);
});
} else {
alert("it's wrong domain format");
}
};
console.log(JSON.stringify(data)); -> "[Object object]"
below my api code
import { NextApiRequest, NextApiResponse } from "next";
const sqlite3 = require("sqlite3").verbose();
const dns = require("dns");
interface Iquerys {
search: string;
date: string;
}
interface IUrl {
ip: string;
}
const dnslookup = (request: NextApiRequest, response) => {
const req = response.req;
const querys: Iquerys = req.query;
const searchData = querys.search;
const dateData = querys.date;
// below my logic
response.status(200).end(
`${dns.resolveAny(searchData, function (err: any, addresses: any) {
return addresses;
})}`
);
//
// db connect
let db = new sqlite3.Database("./db/my_database.db", (err) => {
if (err) {
return console.log(err.message);
}
console.log("Connected to database!");
});
// db peristalsis
db.run("INSERT INTO DnsLookup(search, date) VALUES (?, ?)", [searchData, dateData], (err) => {
if (err) {
return console.log(err.message);
}
console.log("Row was added to the table: ${this.lastID}");
});
};
export default dnslookup;
my project build in react, next js.
I used to JSON.parse() and JSON.stringify() and toString() ... etc
but I can't find a way to real data.
what's wrong with my code? help me please guys
I think the server code is the problem. You should wait for the call back to complete then you should send the response.
response.status(200).end(
`${dns.resolveAny(searchData, function (err: any, addresses: any) {
return addresses;
})}`
);
On the front end code, you should remove await in front of Axios that is not needed.
First declare the headers to be Application/Json
axios.get('m', {
headers: {
'Content-Type': 'application/json'
}
})
I have a form where i enter an email and it gets ''subscribed'' in a user.json file using a fetch api on node server.My task is to :
upon clicking on the "Unsubscribe" button, implement the functionality for unsubscribing from the community list. For that, make POST Ajax request using http://localhost:3000/unsubscribe endpoint.
I tried to make the function but it wasnt succeseful so i deleted it. Also,i need to do the following :
While the requests to http://localhost:3000/subscribe and
http://localhost:3000/unsubscribe endpoints are in progress, prevent
additional requests upon clicking on "Subscribe" and "Unsubscribe".
Also, disable them (use the disabled attribute) and style them using
opacity: 0.5.
For me ajax requests,fetch and javascript is something new,so i dont know really well how to do this task,if you could help me i'll be happy,thanks in advance.
fetch code for subscribing:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput);
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data ? {
'Content-Type': 'application/json'
} : {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!');
error.data = errResData;
throw error;
});
}
return response.json();
});
};
const sendData = (emailInput) => {
sendHttpRequest('POST', 'http://localhost:8080/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data);
window.alert(err.data.error)
});
}
index.js from route node server:
const express = require('express');
const router = express.Router();
const FileStorage = require('../services/FileStorage');
/* POST /subscribe */
router.post('/subscribe', async function (req, res) {
try {
if (!req.body || !req.body.email) {
return res.status(400).json({ error: "Wrong payload" });
}
if (req.body.email === 'forbidden#gmail.com') {
return res.status(422).json({ error: "Email is already in use" });
}
const data = {email: req.body.email};
await FileStorage.writeFile('user.json', data);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
/* GET /unsubscribe */
router.post('/unsubscribe ', async function (req, res) {
try {
await FileStorage.deleteFile('user.json');
await FileStorage.writeFile('user-analytics.json', []);
await FileStorage.writeFile('performance-analytics.json', []);
await res.json({success: true})
} catch (e) {
console.log(e);
res.status(500).send('Internal error');
}
});
module.exports = router;
And user.json file looks like this :
{"email":"Email#gmail.com"}
This is my attempt for unsubscribing :
export const unsubscribeUser = () => {
try {
const response = fetch('http://localhost:8080/unsubscribe', {
method: "POST"
});
if (!response.ok) {
const message = 'Error with Status Code: ' + response.status;
throw new Error(message);
}
const data = response.json();
console.log(data);
} catch (error) {
console.log('Error: ' + error);
}
}
It gives the following errors:
Error: Error: Error with Status Code: undefined
main.js:2
main.js:2 POST http://localhost:8080/unsubscribe 404 (Not Found)
FileStorage.js:
const fs = require('fs');
const fsp = fs.promises;
class FileStorage {
static getRealPath(path) {
return `${global.appRoot}/storage/${path}`
}
static async checkFileExist(path, mode = fs.constants.F_OK) {
try {
await fsp.access(FileStorage.getRealPath(path), mode);
return true
} catch (e) {
return false
}
}
static async readFile(path) {
if (await FileStorage.checkFileExist(path)) {
return await fsp.readFile(FileStorage.getRealPath(path), 'utf-8');
} else {
throw new Error('File read error');
}
}
static async readJsonFile(path) {
const rawJson = await FileStorage.readFile(path);
try {
return JSON.parse(rawJson);
} catch (e) {
return {error: 'Non valid JSON in file content'};
}
}
static async writeFile(path, content) {
const preparedContent = typeof content !== 'string' && typeof content === 'object' ? JSON.stringify(content) : content;
return await fsp.writeFile(FileStorage.getRealPath(path), preparedContent);
}
static async deleteFile(path) {
if (!await FileStorage.checkFileExist(path, fs.constants.F_OK | fs.constants.W_OK)) {
return await fsp.unlink(FileStorage.getRealPath(path));
}
return true;
}
}
module.exports = FileStorage;
You should consider using a database for handling CRUD operations on your persisted data. If you must use filestorage, theres a flat file DB library called lowdb that can make working the files easier.
As for preventing duplicate requests, you can track if user has already made a request.
let fetchBtn = document.getElementById('fetch')
let isFetching = false
fetchBtn.addEventListener('click', handleClick)
async function handleClick(){
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock()
isFetching = false
enableBtn()
}
function fetchMock(){
// const response = await fetch("https://example.com");
return new Promise(resolve => setTimeout (() => resolve('hello'), 2000))
}
function disableBtn(){
fetchBtn.setAttribute('disabled', 'disabled');
fetchBtn.style.opacity = "0.5"
}
function enableBtn(){
fetchBtn.removeAttribute('disabled');
fetchBtn.style.opacity = "1"
}
<button type="button" id="fetch">Fetch</button>
In my bot I have a section where the user can search for knowledge articles using a keyword. The knowledge articles are stored in a service now table. The bot is supposed to return all of the articles that match the keyword in a carousel format but whenever I click the search button nothing happens. Code below:
const search = async (turnContext) => {
const knowledgeBaseTopic = turnContext.activity.value.knowledgeBaseTopic;
const message = await new Promise(resolve => {
axios.request({
url: `url + topic`,
method: 'get',
baseURL: 'url',
auth: {
username: 'username',
password: 'password'
}
},
(error, response, body) => {
console.log(error);
var stuff = [];
let messageWithCarouselOfCards = MessageFactory.carousel(stuff);
for (var i = 0, len = body.result.length; i < len; i++) {
stuff.push(
CardFactory.heroCard(body.result[i].short_description, ['imageUrl1'], [`${ process.env.SN_KB_Resp_URl }${ body.result[i].number }`])
);
}
resolve(messageWithCarouselOfCards);
});
});
return turnContext.sendActivity(message);
};
I took my axios request and put it in a .js file completely seperate to the bot to ensure that the request was finding results. Code below:
const axios = require('axios')
const getArticles = async () => {
try {
axios.request({
url: 'url',
method: 'get',
baseURL: 'url',
auth: {
username: 'username',
password: 'password'
},
}
)
}
catch (error) {
console.error(error)
}
}
const countArticles = async () => {
try{
let articles = await getArticles()
console.log(`${Object.entries(articles.data.message).length}`)
} catch (error) {
console.error(error)
}
}
countArticles()
When running this .js file it fails with the error:
TypeError: Cannot read property 'data' of undefined
Any idea what I'm doing wrong with my request?