Pagination in TypeORM, NestJS - node.js

I have multiple search condition in my form. if user does not enter anything then all the data should be return. if he gives some search input then only those matching record should be return.
Below code is working fine. the only thing is ,sometimes record are coming around 30-40 with filter condition as well so I have been given requirement to introduce pagination. we have to show 10 record at a time in page with or without filter condition.
Could you please guide me how can I introduce pagination in below code.
async findAll(queryCertificateDto: QueryCertificateDto): Promise<Certificate[]> {
const { certificateNo, requestStatus, protoColNo, noOfSubjects} =queryCertificateDto
const query = this.certificateRepository.createQueryBuilder('certificate');
if (certificateNo) {
query.andWhere('certificate.certificateNo=:certificateNo', { certificateNo });
}
if (requestStatus) {
query.andWhere('certificate.requestStatus=:requestStatus', {
requestStatus,
});
}
if (protoColNo) {
query.andWhere('certificate.protoColNo=:protoColNo', { protoColNo });
}
if (noOfSubjects) {
query.andWhere('certificate.noOfSubjects=:noOfSubjects', { noOfSubjects });
}
const certificates = await query.getMany();
return certificates;
}

export const getAllFaqs = () => async (req: Request, res: Response): Promise<void> => {
const {
query: { userType ,page ,perPage},
} = req;
const faqsPagesRepository = getCustomRepository(FaqsPageRepository);
let where: FindConditions<Faqs> = {};
if (userType) {
where = { ...where, userType };
}
const limit =Number(perPage);
const offset=(Number(page)-1)*limit;
const result = await faqsPagesRepository.findAndCount({
where,
take:limit,
skip:offset,
});
res.status(200).json({ result });
};

Related

How to access parent query arguments in GraphQL and Nestjs execution context

Let's say we have a bookshop and an author entity, to show the author their earnings stat, we want to check if the authenticated user is indeed the author themselves. So we have:
#UseGuards(GqlAuthGuard)
#ResolveField(() => [Eearning], { name: 'earnings' })
async getEarnings(
#Parent() author: Author,
#GqlUser() user: User,
) {
if (user.id !== author.id)
throw new UnauthorizedException(
'Each author can only view their own data',
);
// rest of the function implementation
}
We could query this:
query {
author(id: "2bd79-6d7f-76a332b06b") {
earnings {
sells
}
}
}
Now imagine we want to use a custom Guard instead of that if statement. Something like below:
#Injectable()
export class AutherGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
// const artistId = ?
}
}
How can I access the id argument given to the author query when AutherGuard is used for the getEarnings handler?
Not sure how documented is that but the parent object can be accessed through the getRoot method:
const gqlContext = GqlExecutionContext.create(context);
const root = gqlContext.getRoot();
const authorId = root.id;
In fact, we have a helper function that we use like this:
export function getArgs(context: ExecutionContext): any {
if (context.getType<GqlContextType>() === "graphql") {
const gqlContext = GqlExecutionContext.create(context);
return { ...gqlContext.getArgs(), $parent: gqlContext.getRoot() };
} else if (context.getType() === "http") {
return context.switchToHttp().getRequest().params;
}
}
...
const args = getArgs(context);
const authorId = _.get(args, "$parent.id");

Cancel data insert inside beforeInsert event if it already exists in table

There is a need to insert multiple rows in a table. I am using TypeOrm which has .save() method.
.save() method is used to insert data in bulk, and it checks for the inserting primary key ID. If they exist it updates, otherwise inserts.
I want to do the checking on some other field shortLInk. Which is not possible with .save()
So I tried to do inside beforeInsert() event, things are fine but I need to cancel the isnertions if row is find.
Is there any way to achieve it? I couldn't find anything in documentation.
I can throw an error inside beforeInsert() but it will cancel whole insertions.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
return this.quickLinkRepository.save(quickLinks, {});
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
return delete event.entity; // throws error
}
event.entity.shortLink = shortLink;
}
}
When you use a transaction, all the operations are atomic, meaning either all of them are executed or none of them are. you can check for the existence of the shortLink before the insert and if it exists, you can cancel the whole transaction.
async shortLinks(links: Array<string>): Promise<Array<QuickLinkDto>> {
const quickLinks: Array<QuickLinkDto> = links.map((link) => ({
actualLink: link,
}));
const queryRunner = this.quickLinkRepository.manager.connection.createQueryRunner();
try {
await queryRunner.startTransaction();
const result = await this.quickLinkRepository.save(quickLinks, {});
await queryRunner.commitTransaction();
return result;
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
#Injectable()
export class QuickLinkSubscriber
implements EntitySubscriberInterface<QuickLink>
{
constructor(
datasource: DataSource,
#InjectRepository(QuickLink)
private readonly quickLinkRepository: Repository<QuickLink>,
) {
datasource.subscribers.push(this);
}
listenTo() {
return QuickLink;
}
async beforeInsert(event: InsertEvent<QuickLink>) {
const shortLink = await getShortLink(event.entity.actualLink);
const linkExists = await this.quickLinkRepository.findOne({
where: {
shortLink,
},
});
if (linkExists) {
// Discard the insertion if the row already exists
throw new Error('The short link already exists');
}
event.entity.shortLink = shortLink;
}
}

How to pass a child Interface to a parent class?

I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.

getting undefined from async await in typescript node

Use case:
I am trying to insert a record inside the amazon QLDB using Node and typescript.
I am able to insert the record/document successfully and it returns me documentID in return.
there are 2 controllers: EntityController and CommonController
-EntityController extends CommonController
-EntityController has the code for getting req object converting it into the model object and the calling insert() function that has been extended from the CommonController.
problem
I am trying to propagate that documentID to all the way to my API call, but somehow I am getting undefined in the EntityController.
whereas I am able to print the documentID in CommonController.
I am not sure why I am getting undefined when I am clearly returning a value.
const CommonController = require("../template/controller");
import { Request, Response } from 'express';
const tableName:string = "entities";
const EntityModel = require("./model")
class EntityController extends CommonController {
async insertEntitiy(req:Request,res:Response) {
async insertEntitiy(req:any,res:any) {
console.log(req);
console.log("===========");
console.log(req.body);
let entity = new EntityModel();
entity.balance = req.body.balance;
entity.firstName = req.body.firstName;
entity.lastName = req.body.lastName;
entity.email = req.body.email;
try {
let documentIds = await this.insert(tableName,entity);
console.log("--------- inside insertEntity fiunction()---------");
console.log(documentIds);
console.log("------------------");
res.status(200).send(documentIds[0]);
} catch (error) {
console.error(`error in creating Entity: ${error}`);
res.status(500).send({ errMsg: `error in creating Entity: ${error}` });
}
}
}
module.exports = new EntityController();
import { createQldbWriter, QldbSession, QldbWriter, Result, TransactionExecutor } from "amazon-qldb-driver-nodejs";
import { Reader } from "ion-js";
import { error, log } from "../qldb/LogUtil";
import { getFieldValue, writeValueAsIon } from "../qldb/Util";
import { closeQldbSession, createQldbSession } from "../qldb/ConnectToLedger";
module.exports = class Conroller {
async insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result:Array<string>;
try {
session = await createQldbSession();
await session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return (Promise.resolve(result));
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
/**
* Insert the given list of documents into a table in a single transaction.
* #param txn The {#linkcode TransactionExecutor} for lambda execute.
* #param tableName Name of the table to insert documents into.
* #param documents List of documents to insert.
* #returns Promise which fulfills with a {#linkcode Result} object.
*/
async insertDocument(
txn: TransactionExecutor,
tableName: string,
documents: object
): Promise<Array<string>> {
const statement: string = `INSERT INTO ${tableName} ?`;
const documentsWriter: QldbWriter = createQldbWriter();
let documentIds: Array<string> = [];
writeValueAsIon(documents, documentsWriter);
let result: Result = await txn.executeInline(statement, [documentsWriter]);
const listOfDocumentIds: Reader[] = result.getResultList();
listOfDocumentIds.forEach((reader: Reader, i: number) => {
documentIds.push(getFieldValue(reader, ["documentId"]));
});
console.log("---------documentIds---------");
console.log(documentIds);
console.log("------------------");
return (documentIds);
}
}
ouptut :
---------documentIds---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
---------result inside insert fiunction()---------
[ '4o5UZjMqEdgENqbP9l7Uhz' ]
--------- inside insertEntity fiunction()---------
undefined
As #daniel-w-strimpel pointed out in the comments, your insert method returns only in the catch part.
Try this:
insert(tablename:string, object:any): Promise<Array<string>> {
let session: QldbSession;
let result: Array<string>;
try {
session = await createQldbSession();
return session.executeLambda(async (txn) => {
result = await this.insertDocument(txn,tablename,object);
console.log("---------result inside insert fiunction()---------");
console.log(result);
console.log("------------------");
return result;
})
} catch (e) {
error(`Unable to insert documents: ${e}`);
return(["Error"]);
} finally {
closeQldbSession(session);
}
}
...
In return session.executeLambda you return the Promise.
In return result; you return the actual value.
More on promises here: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

Extensions not returned in GraphQL query results

I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.

Resources