Dynamically build external API URL and parameters - javascript

Today we have an object to keep track an external API usage like this:
const setParams =
(params) => Object.keys(params).map(
(element) => element + '=' + encodeURIComponent(params[element])).join('&')
const API = {
Common: {
Search: (order) => `Common/Search?${setParams({order})}`,
}}
console.log(API.Common.Search(1)); // "Common/Search?order=1"
However, we still get some typos what could be avoided.
Is it possible to dynamically set that URL even what we would need to change that API structure a bit?
const API = {
Common: {
Search: (order) => `${magicParent}/${magicCurrent}?${params}`,
Another: (login, argA, argB) => `${magicParent}/${magicCurrent}?${magic(params)}`,
}}
console.log(API.Common.Search(1)); // "Common/Search?order=1"
console.log(API.Common.Another(1,2,3)); // "Common/Another?login=1&argA=2&argB=3"
Thanks,

Just an idea | Not tested | Ugly (mutates):
const isObject = // Implement yourself
const injectPath = (obj, path = '') => {
Object.entries(obj).forEach( ([key, value]) => {
if (isObject(value) {
injectPath(value, path + '/' + key;
} else {
// It is a function
obj[key] = value(path);
}
})
}
const API = injectPath({
Common: {
Search: (path) => (order) => `${path}?${setParams({order})}`,
}}
});

Related

How to filter two arrays of splitting?

I'm a bit confused
I am sending emails with nodemailer, and every time I send one I perform certain validations in order to manage the upload limit of the attachments. If the upload limit exceeds what is established, the service divides that email and sends it in different emails with the same subject and body as well as its attachment.
Every time this happens, it does a _.chunk that takes care of splitting the pdfs array into smaller elements. But, it should be noted that before that, he made a method to prepare the attachments and this is in charge of obtaining certain information from the api to paint the pdf buffer and thus put it in the content of the emails.
But now what I want to do is search within the matrix that performs the step before dividing the files those that are equal to the array that obtains the information and if they are equal, carry out the instruction that it sends
I will explain with a graph:
If getAmount.pdfBuffer === attachmentMap
// doAction console.log('Equals)
But even though I tried to do it, I couldn't, I don't know if it's because for each attachment that the array has divided, it generates a getAmount array. What do you think I'm doing wrong?
async sendEmail(
{
para: to,
asunto: subject,
plantilla: template,
contexto: context,
}: CorreoInfoDto,
attachments: EmailAttachment[],
driveConfig: OAuthGoogleConfig
) {
const totalSize: number = this.getSizeFromAttachments(attachments);
const chunkSplit = Math.floor(isNaN(totalSize) ? 1 : totalSize / this.LIMIT_ATTACHMENTS) + 1;
const attachmentsChunk: any[][] = _.chunk(attachments, chunkSplit);
if ((totalSize > this.LIMIT_ATTACHMENTS) && attachmentsChunk?.length >= 1) {
await Promise.all(
attachmentsChunk?.map(async (attachment: EmailAttachment[], index) => {
console.log('attachment', attachment)
if (this.getSizeFromAttachments(attachment) > this.LIMIT_ATTACHMENTS) {
const result: GenerateDriveLinkResponse[] = await Promise.all(
attachment?.map(item => {
const file = new GoogleDriveUploadFile({
name: item?.filename,
mimeType: MimeTypesEnum.PDF,
body: item?.content
});
return this.uploadFilesService.uploadToDrive(driveConfig, file) as any;
})
)
const texto = result?.map((item, index) => {
console.log('item', item?.webViewLink);
console.log('index', index);
return new SolicitudXLinkDrive({
texto: attachment[index].filename,
link: item?.webViewLink
})
});
context.links = texto;
const link = `(${index + 1}/${attachmentsChunk?.length - 1})`;
const newContext = {
getCurrent: link,
...context
}
const prepareEmail = this.prepareEmail({
para: to,
asunto: ` ${subject} (${index + 1}/${attachmentsChunk?.length})`,
plantilla: template,
contexto: newContext,
}, []);
return prepareEmail
} else {
// this.getCantidad = `(${index + 1}/${attachmentsChunk?.length - 1})`;
console.log('getCantidad', this.getAmount );
const attachmentMap = attachment.map(element => element.content);
this.getAmount .forEach(element => {
if (element.pdfBuffer === attachmentMap) {
console.log('do action');
}
})
const link = ` (${index + 1}/${attachmentsChunk?.length - 1})`;
const newContext = {
getCurrent: link,
...context
}
return this.prepareEmail({
para: to,
asunto: ` ${subject} (Correo ${index + 1}/${attachmentsChunk?.length - 1})`,
plantilla: template,
contexto: newContext,
}, attachment);
}
})
);
} else {
await this.prepareEmail(
{
para: to,
asunto: ` ${subject}`,
plantilla: template,
contexto: context,
},
attachments,
);
}
}
async prepareEmail(
{
para: to,
asunto: subject,
plantilla: template,
contexto: context,
}: CorreoInfoDto,
attachments: EmailAttachment[],
) {
return await this.mailerService.sendMail({
to,
from: `${process.env.SENDER_NAME} <${process.env.EMAIL_USER}>`,
subject,
template,
attachments: attachments,
context: context,
});
}
async sendEmails(correos: EnvioMultiplesCorreosDto) {
let pdf = null;
let info: ConfiguracionDocument = null;
let GDriveConfig: ConfiguracionDocument = null;
let logo: ConfiguracionDocument = null;
let forContext = {};
const documents = Array.isArray(correos.documento_id) ? correos.documento_id : [correos.documento_id];
const solicitudes = await this.solicitudesService.findByIds(documents);
const nombresPacientes = solicitudes.reduce((acc, cv) => {
acc[cv.correlativo_solicitud] = cv['info_paciente']?.nombre_paciente;
return acc;
}, {});
await Promise.all([
await this.getPdf(correos.tipo_reporte, correos.documento_id, correos?.dividir_archivos).then(data => { pdf = data; }),
await this.configuracionesService.findByCodes([
ConfigKeys.TEXTO_CORREO_MUESTRA,
ConfigKeys[process.env.DRIVE_CONFIG_API],
ConfigKeys.LOGO_FIRMA_PATMED
]).then(data => {
info = data[0];
GDriveConfig = data[1];
logo = data[2];
})
]);
forContext = this.configuracionesService.castValorObjectToObject(info?.valor_object)
const attachmentPrepare = this.prepareAttachments(pdf as any, nombresPacientes);
await this.sendEmail(
{
para: correos.para,
asunto: correos.asunto,
plantilla: 'muestras',
contexto: {
cuerpo: correos.cuerpo,
titulo: forContext[EnvioCorreoMuestraEnum.titulo],
direccion: forContext[EnvioCorreoMuestraEnum.direccion],
movil: forContext[EnvioCorreoMuestraEnum.movil],
pbx: forContext[EnvioCorreoMuestraEnum.pbx],
email: forContext[EnvioCorreoMuestraEnum.email],
logo: logo?.valor,
},
},
attachmentPrepare,
this.configuracionesService.castValorObjectToObject(GDriveConfig?.valor_object) as any,
);
const usuario = new UsuarioBitacoraSolicitudTDTO();
usuario.createFromUserRequest(this.sharedService.getUserFromRequest());
solicitudes.forEach((solicitud) => {
const actual = new BitacoraSolicitudDTO();
actual.createFromSolicitudDocument(solicitud);
const newBitacora = new CrearBitacoraSolicitudDTO();
newBitacora.createNewItem(null, actual, actual, usuario, AccionesBitacora.EmailEnviado);
this.bitacoraSolicitudesService.create(newBitacora);
});
}
prepareAttachments(item: BufferCorrelativosDTO | BufferXSolicitudDTO[], nombresPacientes: { [key: string]: string }) {
if (this.sharedService.isAnArray(item)) {
const castItem: BufferXSolicitudDTO[] = item as any;
this.getCantidad = castItem;
return castItem?.map((s) => {
const namePatient = nombresPacientes[s.correlativo_solicitud];
return new EmailAttachment().setFromBufferXSolicitudDTO(s, namePatient, 'pdf');
});
} else {
return [new EmailAttachment().setFromBufferCorrelativosDTO(item as any, 'pdf')];
}
}
Thank you very much for your attention, I appreciate it. Cheers
You could try using lodash as this has _.intersectionBy and _.intersectionWith functions that should allow you to compare 2 arrays and filter the common values.
There are some good examples here:
How to get intersection with lodash?

How to delete multiple url params

There is a problem with deleting several string parameters. Only the last parameter is being deleted now.
upd: I did not specify that I wanted to achieve the ability to remove specific parameter values
this code does not work correctly:
const updateFiltersSearchParams = (paramKey, newValue) => {
const isParamExist = searchParams.getAll(paramKey).includes(newValue);
if (!isParamExist) {
searchParams.append(paramKey, newValue);
setSearchParams(searchParams);
} else {
const updatedSearchParams = new URLSearchParams(
[...searchParams].filter(
([key, value]) => key !== paramKey || value !== newValue
)
);
setSearchParams(updatedSearchParams);
}
};
const handleDeleteParams = () => {
[...checkboxParams].forEach((param) => {
updateFiltersSearchParams("selected", param);
});
};
Sandbox
change your handleDeleteParams function with this
const handleDeleteParams = () => {
setSearchParams([]);
};
If you want to delete *only the selected (or any specific queryString key) queryString parameters you can use the delete method of the URLSearchParams object, then enqueue the params URL update.
const handleDeleteParams = (key) => {
searchParams.delete(key);
setSearchParams(searchParams);
};
...
<button type="button" onClick={() => handleDeleteParams("selected")}>
Clear all "selected" params
</button>
Solved the problem by modifying the function like this
const toggleSearchParams = (params) => {
const newSearchParams = [...searchParams];
for (const prevParam of params) {
const index = newSearchParams.findIndex(
(newParam) =>
prevParam[0] === newParam[0] && prevParam[1] === newParam[1]
);
if (index === -1) {
newSearchParams.push(prevParam);
} else {
newSearchParams.splice(index, 1);
}
}
setSearchParams(new URLSearchParams(newSearchParams));
};
const handleChangeCheckBoxValue = (e) => {
toggleSearchParams([["selected", e.target.value]]);
};
const handleDeleteParams = () => {
toggleSearchParams(checkboxParams.map((param) => ["selected", param]));
};

Trying to access state in oncompleted method

I have API query and getting the result and setting those in a state variable in Oncompleted method of API query, Now i am updating the same state variable in another api query "onCompleted method.
I am not able to access the result from state what i have set before in first api query and below is my code
Query 1:
const designHubQueryOnCompleted = designHubProject => {
if (designHubProject) {
const {
name,
spaceTypes
} = designHubProject;
updateState(draft => { // setting state here
draft.projectName = name;
draft.spaceTypes = (spaceTypes || []).map(po => {
const obj = getTargetObject(po);
return {
id: po.id,
name: obj.name,
category: obj.librarySpaceTypeCategory?.name,
description: obj.description,
warning: null // trying to modify this variable result in another query
};
});
});
}
};
const { projectDataLoading, projectDataError } = useProjectDataQuery(
projectNumber,
DESIGNHUB_PROJECT_SPACE_TYPES_MIN,
({ designHubProjects }) => designHubQueryOnCompleted(designHubProjects[0])
);
Query 2:
const {
// data: designhubProjectSpaceTypeWarnings,
loading: designhubProjectSpaceTypeWarningsLoading,
error: designhubProjectSpaceTypeWarningsError
} = useQuery(DESIGNHUB_PROJECT_LINKED_SPACETYPE_WARNINGS, {
variables: {
where: {
projectNumber: { eq: projectNumber }
}
},
onCompleted: data => {
const projectSpaceTypeWarnings = data.designHubProjectLinkedSpaceTypeWarnings[0];
const warnings = projectSpaceTypeWarnings.spaceTypeWarnings.reduce((acc, item) => {
const spaceTypeIdWithWarningState = {
spaceTypeId: item.spaceTypeProjectObjectId,
isInWarningState: item.isInWarningState
};
acc.push(spaceTypeIdWithWarningState);
return acc;
}, []);
console.log(state.spaceTypes); // trying to access the state here but getting empty array
if (state.spaceTypes.length > 0) {
const updatedSpaceTypes = state.spaceTypes;
updatedSpaceTypes.forEach(item => {
const spaceTypeWarning = { ...item };
spaceTypeWarning.warning = warnings?.filter(
w => w.spaceTypeId === spaceTypeWarning.id
).isInWarningState;
return spaceTypeWarning;
});
updateState(draft => {
draft.spaceTypes = updatedSpaceTypes;
});
}
}
});
Could any one please let me know where I am doing wrong with above code Or any other approach to modify the state, Many thanks in advance!!

Destructuring the API https://randomuser.me/api/ to grab title, last name, and first name also large photo of the user profile returned by the API

I'm trying to grab some data(title, last name, first name and also large photo of the user profile returned by the API.) from the API https://randomuser.me/api/, which seem to not be working.
const displayUserPhotoAndName = (data) => {
if(!data) return;
// add your code here
let {results} = data;
let [profile] = results;
document.querySelector('h2').textContent = profile.name.title +' '+ profile.name.last +' '+ profile.name.first;
document.querySelector('img').src = profile.picture.large;
displayExtraUserInfo(profile);
clearNotice();
};
const getAUserProfile = () => {
const api = 'https://randomuser.me/api/';
// make API call here
fetch(api)
.then((resp) => resp.json())
.then(data => {displayUserPhotoAndName()});
notify(`requesting profile data ...`);
};
const displayBirthdate = ({dob = 'dob'}) => {
document.querySelector('.details').textContent = dob.age;
}
const displayPhone = ({phone = 'phone', cell = 'cell'}) => {
document.querySelector('.details').textContent = phone + ', ' + cell;
}
const displayAddress = ({location = 'location'}) => {
document.querySelector('.details').textContent = location.street + ', ' + location.city + ', ' + location.state;
}
You are passing the data to the function.Do like below
const getAUserProfile = () => {
const api = 'https://randomuser.me/api/';
// make API call here
fetch(api)
.then((resp) => resp.json())
.then(data => {displayUserPhotoAndName(data)}); //this line is changed
notify(`requesting profile data ...`);
};
Here is the line that will get all the required properties
let {results:[{ name: { title , first , last } , picture:{ large } }]} = data;
Here is how you can do it and also handle bad data with default values. Also, do not forget to pass data to your function in the then callback:
const display = data => {
const { results: [{ name: { title, first, last } = {}, picture: { large } = {} }] = [] } = data || {};
console.log(title, first, last, large);
};
fetch('https://randomuser.me/api/').then(r => display(r.json()));

Cleaning Unwanted Fields From GraphQL Responses

I have an object that my GraphQL client requests.
It's a reasonably simple object:
type Element {
content: [ElementContent]
elementId: String
name: String
notes: String
type: String
createdAt: String
updatedAt: String
}
With the special type ElementContent, which is tiny and looks like this:
type ElementContent {
content: String
locale: String
}
Now, when I query this on the clientside, both the top level object and the lower level object has additional properties (which interfere with updating the object if I attempt to clone the body exactly-as-is);
Notably, GraphQL seems to supply a __typename property in the parent object, and in the child objects, they have typename and a Symbol(id) property as well.
I'd love to copy this object to state, update in state, then clone the state and ship it to my update mutation. However, I get roadblocked because of unknown properties that GraphQL itself supplies.
I've tried doing:
delete element.__typename to good effect, but then I also need to loop through the children (a dynamic array of objects), and likely have to remove those properties as well.
I'm not sure if I'm missing something during this equation, or I should just struggle through the code and loop + delete (I received errors attempting to do a forEach loop initially). Is there a better strategy for what I'm attempting to do? Or am I on the right path and just need some good loop code to clean unwanted properties?
There are three ways of doing this
First way
Update the client parameter like this it will omit the unwanted fields in graphql.
apollo.create({
link: http,
cache: new InMemoryCache({
addTypename: false
})
});
Second Way
By using the omit-deep package and use it as a middleware
const cleanTypeName = new ApolloLink((operation, forward) => {
if (operation.variables) {
operation.variables = omitDeep(operation.variables,'__typename')
}
return forward(operation).map((data) => {
return data;
});
});
Third Way
Creating a custom middleware and inject in the apollo
const cleanTypeName = new ApolloLink((operation, forward) => {
if (operation.variables) {
const omitTypename = (key, value) => (key === '__typename' ? undefined : value);
operation.variables = JSON.parse(JSON.stringify(operation.variables), omitTypename);
}
return forward(operation).map((data) => {
return data;
});
});
and inject the middleware
const httpLinkWithErrorHandling = ApolloLink.from([
cleanTypeName,
retry,
error,
http,
]);
If you use fragments with the queries/mutations Second Way & Third Way is recommended.
Preferred method is Third Way Because it does not have any third pary dependency and no cache performance issues
If you want to wipe up __typename from GraphQL response (from the root and its children), you can use graphql-anywhere package.
Something like:
const wipedData = filter(inputFragment, rcvData);
inputFragment is a fragment defines the fields (You can see details here)
rcvData is the received data from GraphQL query
By using the filter function, the wipedData includes only required fields you need to pass as mutation input.
Here's what I did, to support file uploads as well. It's a merge of multiple suggestions I found on the Github thread here: Feature idea: Automatically remove __typename from mutations
import { parse, stringify } from 'flatted';
const cleanTypename = new ApolloLink((operation, forward) => {
const omitTypename = (key, value) => (key === '__typename' ? undefined : value);
if ((operation.variables && !operation.getContext().hasUpload)) {
operation.variables = parse(stringify(operation.variables), omitTypename);
}
return forward(operation);
});
Hooking up the rest of my client.tsx file, simplified:
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createUploadLink } from 'apollo-upload-client';
import { ApolloClient } from 'apollo-client';
import { setContext } from 'apollo-link-context';
import { ApolloLink } from 'apollo-link';
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN);
return {
headers: {
...headers,
authorization: token ? `Bearer ${ token }` : '',
},
};
});
const httpLink = ApolloLink.from([
cleanTypename,
authLink.concat(upLoadLink),
]);
const client = new ApolloClient({
link: httpLink,
cache,
});
export default client;
Now when I call mutations that are of type upload, I simply set the context hasUpload to true, as shown here:
UpdateStation({variables: { input: station }, context: {hasUpload: true }}).then()
For those looking for a TypeScript solution:
import cloneDeepWith from "lodash/cloneDeepWith";
export const omitTypenameDeep = (
variables: Record<string, unknown>
): Record<string, unknown> =>
cloneDeepWith(variables, (value) => {
if (value && value.__typename) {
const { __typename, ...valWithoutTypename } = value;
return valWithoutTypename;
}
return undefined;
});
const removeTypename = new ApolloLink((operation, forward) => {
const newOperation = operation;
newOperation.variables = omitTypenameDeep(newOperation.variables);
return forward(newOperation);
});
// ...
const client = new ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([removeTypename, httpLink]),
});
I've just published graphql-filter-fragment to help with this use case. I've wrote in a bit more detail about the CRUD use case I had that led me to this approach.
Example:
import {filterGraphQlFragment} from 'graphql-filter-fragment';
import {gql} from '#apollo/client/core';
const result = filterGraphQlFragment(
gql`
fragment museum on Museum {
name
address {
city
}
}
`,
{
__typename: 'Museum',
name: 'Museum of Popular Culture',
address: {
__typename: 'MuseumAddress',
street: '325 5th Ave N',
city: 'Seattle'
}
}
);
expect(result).toEqual({
name: 'Museum of Popular Culture',
address: {
city: 'Seattle'
}
});
Here is my solution. Vanilla JS, recursive, and does not mutate the original object:
const removeAllTypenamesNoMutate = (item) => {
if (!item) return;
const recurse = (source, obj) => {
if (!source) return;
if (Array.isArray(source)) {
for (let i = 0; i < source.length; i++) {
const item = source[i];
if (item !== undefined && item !== null) {
source[i] = recurse(item, item);
}
}
return obj;
} else if (typeof source === 'object') {
for (const key in source) {
if (key === '__typename') continue;
const property = source[key];
if (Array.isArray(property)) {
obj[key] = recurse(property, property);
} else if (!!property && typeof property === 'object') {
const { __typename, ...rest } = property;
obj[key] = recurse(rest, rest);
} else {
obj[key] = property;
}
}
const { __typename, ...rest } = obj;
return rest;
} else {
return obj;
}
};
return recurse(JSON.parse(JSON.stringify(item)), {});
};
Below approach worked for me so far in my use case.
const {
loading,
error,
data,
} = useQuery(gqlRead, {
variables: { id },
fetchPolicy: 'network-only',
onCompleted: (data) => {
const { someNestedData } = data;
const filteredData = removeTypeNameFromGQLResult(someNestedData);
//Do sth with filteredData
},
});
//in helper
export const removeTypeNameFromGQLResult = (result: Record<string, any>) => {
return JSON.parse(
JSON.stringify(result, (key, value) => {
if (key === '__typename') return;
return value;
})
);
};

Categories

Resources