How to use graphql dataloader loadMany function properly in nestjs? - express

I want to use dataloader loadMany function in nestjs. Can any help me to use it properly-
dataloader.service.ts-
#Injectable()
export class DataloaderService {
constructor(private readonly categoryService: CategoryService) { }
createLoaders(): IDataloaders {
const subCategoryLoader = new DataLoader<ObjectId, Subcategory>(
async (keys: readonly ObjectId[]) =>
this.categoryService.findSubCategoryByBatch(keys as ObjectId[])
);
return {
subCategoryLoader
};
}
}
Then I category service(findSubCategoryByBatch)-
async findSubCategoryByBatch(Ids: ObjectId[]): Promise<(Subcategory | Error)[]> {
const categories = await this.subCategoryModel.find({ _id: { $in: Ids } });
const mappedResults = Ids.map(
(id) =>
categories.find((result) => result.id === id) ||
new Error(`Could not load owner ${id}`),
);
return mappedResults;
}
Then I call it -
#ResolveField('subCategory', () => [Subcategory])
getSubCategory(
#Parent() category: Category,
#Context() { loaders }: IGraphQLContext
) {
return loaders.subCategoryLoader.loadMany(category.subCategory)
}
Notice: I use loadMany function because subCategory is a array IDs.
But I am getting error. Here How to write function for loadMany function. Please help me. I need it. Please help me. please help me.

Related

Delete item from pinia state

I am new to vue and I have just started using pinia. I wanna delete an item from array but it does not work
here is my store
import {defineStore} from 'pinia'
export interface ObjectDto {
input: string,
}
interface ObjectDtoInterface {
objects: Array<ObjectDto>
}
export const useSearchHistoryStore = defineStore('objectsStore', {
state: (): ObjectDtoInterface => {
return {
objects: [] as ObjectDto[]
}
},
actions: {
add(dto: ObjectDto) {
if (this.objects
.filter(shd => dto.input === shd.input)
.length === 0) {
this.objects.unshift(dto)
}
},
delete(obj: ObjectDto) {
this.objects = this.objects.filter(e => !(e.input === obj.input))
}
}
})
and here is the function from different .ts file
function delete(obj: ObjectDto) {
objectsStore.delete(obj)
}
add action works perfect, it adds item to the state but when I try to delete an item, nothing happens. The data I pass to delete method is 100% good because I checked this many times
Filter does not mutate the original object, you need to reasing
delete(obj: ObjectDto) {
this.objects = this.objects.filter(e => !(e.input === obj.input))
}
more info https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

How do I change the alias of a column in Typeorm?

I want to change the name of a field with my QueryBuilder in the response body i.e a field called id, I want it to output as staff_id, I am having difficulties with that.
import { getRepository, Like } from 'typeorm';
import { RoomEntity } from '#entity/room.entity';
import { HttpException } from '#exceptions/HttpException';
import { isEmpty } from '#utils/util';
import { Room } from '#/interfaces/room.interface';
class RoomService {
public room = RoomEntity;
public async findQueryRoom(): Promise<Room[]> {
const rooms = await getRepository(this.room)
.createQueryBuilder('room')
.select(['room.id as staff_id', 'room.name'])
.getMany();
return rooms;
}
}
export default RoomService;
In the controller, I have:
import { NextFunction, Request, Response } from 'express';
import { Room } from '#interfaces/room.interface';
import RoomService from '#services/room.service';
class RoomsController {
public roomService = new RoomService();
public getRoomsByQuery = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const findAllRoomsData: Room[] = await this.roomService.findQueryRoom();
res.status(200).json({ data: findAllRoomsData, message: 'found all' });
} catch (error) {
next(error);
}
};
}
export default RoomsController;
The response I have in my postman does not include the staff_id field, I see
"data": [
{
"name": "Soba"
}
]
How can I solve this problem?
I don't know if you have gotten the solution already, but for those that are facing the same problem using select column alias with TypeORM. I face the same problem some time ago and below was my solution I don't know if it's a convincing solution but it solve my problem.
public async findQueryRoom(): Promise<Room[]> {
const rooms = await getRepository(this.room)
.createQueryBuilder('room')
.select(['room.id AS staff_id', 'room.name AS name'])
.groupBy("room.id")
.getRawMany();
return rooms;
}
Why .groupBy("room.id") when .groupBy("room.id") was absent it keep on selecting each row as many as the total row for example if there are 5 rows in total it will return each row 5 times that is why I introduced .groupBy("room.id") to rid of that.
For 'room.name AS name' if you leave 'room.name' it will return room_name instead of name.

Nest JS authorization with CASL doesn't work as expected

EXPECTING:
Be able to get user info with id equal to my id only (which is saved in JWT token).
CURRENT RESULT:
I am able to get info about all users with some id.
Used Nest Js docs while creating this solution.
Do appreciate your help.
/casl-ability.factory.ts
type Subjects = InferSubjects<typeof User | typeof Role | 'User'> | 'all';
export type AppAbility = Ability<[Action, Subjects]>;
export class CaslAbilityFactory {
createForUser(userDataFromJWT: JwtAccessTokenInput) {
const { can, cannot, build } = new AbilityBuilder<
Ability<[Action, Subjects]>
>(Ability as AbilityClass<AppAbility>);
// TESTING THIS CASE
can(Action.Read, User, {
id: userDataFromJWT.sub,
});
return build({
detectSubjectType: (item) =>
item.constructor as ExtractSubjectType<Subjects>,
});
}
private hasRole(roles: unknown[], role: UserRoles): boolean {
return roles.includes(role);
}
}
/getUser.policyHandler.ts
export class GetUserPolicyHandler implements IPolicyHandler {
handle(ability: AppAbility) {
return ability.can(Action.Read, User);
}
}
/types.ts
export enum Action {
Manage = 'manage',
Create = 'create',
Read = 'read',
Update = 'update',
Delete = 'delete',
}
export interface IPolicyHandler {
handle(ability: AppAbility): boolean;
}
type PolicyHandlerCallback = (ability: AppAbility) => boolean;
export type PolicyHandler = IPolicyHandler | PolicyHandlerCallback;
/policies.guard.ts
#Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private reflector: Reflector,
private caslAbilityFactory: CaslAbilityFactory,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const policyHandlers =
this.reflector.get<PolicyHandler[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
) || [];
const ctx = GqlExecutionContext.create(context);
const { user }: { user: JwtAccessTokenInput } = ctx.getContext().req;
const ability = this.caslAbilityFactory.createForUser(user);
return policyHandlers.every((handler) =>
this.execPolicyHandler(handler, ability),
);
}
private execPolicyHandler(handler: PolicyHandler, ability: AppAbility) {
if (typeof handler === 'function') {
return handler(ability);
}
return handler.handle(ability);
}
}
user.resolver.ts
#Resolver(() => User)
export class UserResolver {
constructor(private readonly userService: UserService) {}
#Query(() => User, { name: 'user' })
#UseGuards(PoliciesGuard)
#CheckPolicies(new GetUserPolicyHandler())
#UseInterceptors(UserNotExistsByIDInterceptor)
async findOne(#Args('id', { type: () => Int }) id: number): Promise<User> {
return await this.userService.findOne(id);
}
}
possible duplicate of NestJS + CASL + Mongoose: CASL cannot infer subject type from Mongoose Schema
if you're using mongoose you need to inject the model to allow InferSubjects to retrieve the type thus allowing you to use filters and fields.

Angular 5 HTTPClient not returning results for RouteResolver

I have to say HttpClient Observables, subscriptions etc are pretty hard/time consuming to get right.
I have been working on a problem for a while now and tearing my hair out. I have a service that I need to be able to perform a mapping function on.
loadAllSummary(organisationId: number) {
return this.http.get('/api/aircrafts/organisations/' + organisationId)
.pipe(
map(data => data.forEach(datum => {
console.log('why am i not getting here! ' + JSON.stringify(data));
return this.mapToSummary(datum);
}))
);
}
with the mapToSummary() method:
private mapToSummary(aircraft: Aircraft): IAircraftSummary {
const lastDate: Date = new Date(Math.max.apply(null, aircraft.workorders.map(function(e) {
return new Date(e.date);
})));
return new AircraftSummary({
lastWork: lastDate,
rego: aircraft.registration,
make: aircraft.make,
model: aircraft.model,
contact: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.principal : aircraft.operator.principal,
phone: (aircraft.owner.type.endsWith('primary')) ? aircraft.owner.contact.phone : aircraft.operator.contact.phone
});
}
Now, I need these summaries as input data to a view, so I borrowed code from the interwebs and created this ResolverService:
#Injectable()
export class AircraftsResolverService implements Resolve<IAircraftSummary[]> {
constructor(private service: AircraftService,
private router: Router) { }
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IAircraftSummary[]> {
console.log('called AircraftsResolverService')
const id = route.params['id'];
if (isNaN(+id)) {
console.log(`Organisation id was not a number: ${id}`);
this.router.navigate(['/login']);
return Observable.of(null);
}
return this.service.loadAllSummary(+id)
.map(summaries => {
console.log(summaries)
if (summaries) {
return summaries;
}
console.log(`Summaries were not found: ${id}`);
this.router.navigate(['/organisations/', +id]);
return null;
})
.catch(error => {
console.log(`Retrieval error: ${error}`);
this.router.navigate(['/organisations/', +id]);
return Observable.of(null);
});
}
}
Which I then refer to in the ngOnInit call...
ngOnInit() {
this.currentUser = this.authenticationService.returnCurrentUser();
this.route.data
.subscribe(({aircrafts}) => {
this.aircrafts = aircrafts;
const id = +this.route.snapshot.paramMap.get('id');
console.log(' where are my aircraft!' + JSON.stringify(aircrafts));
this.ELEMENT_DATA = aircrafts;
this.displayedColumns = ['Last Work', 'Rego', 'Make', 'Model', 'Contact', 'Phone'];
this.dataSource = new MatTableDataSource(this.ELEMENT_DATA);
this.dataSource.sort = this.sort;
console.log(id);
if (id) {
this.organisationService.getById(id).subscribe(org => {
this.organisation = org;
});
} else {
console.log('its bad');
}
});
console.log(this.dataSource);
}
The console log under the subscribe is undefined and the console.logs under the service never get triggered. So once again, I find myself not understanding why subscription fire or not fire, or whatever it is that they do.
How do I get past this? thanks everyone.
EDIT: appears that the problem is actually in the ResolverService, I have been able to determine that the data service is getting the results and that they are correct. For some reason, the resolver service can't see them.
The answer was in the route resolver, or rather the app-routing-module. I should have included it in the question, because some of the angular saltys would have picked it up
I was trying to do this:.
{ path: 'organisations/:orgId/aircrafts/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
But you can't, you have to do this:
{ path: 'organisations/aircrafts/:orgId/:id', component: AircraftsComponent, resolve: {aircrafts : AircraftsResolverService}, canActivate: [AuthGuard] },
results in very non-resty urls, but, hey, whatever works, right?

Filtering normalized data structure

Forgive me, I'm new to normalizr+redux. I've managed to normalize my data and create a reducer and end up with :
state = {
installations:{
"1":{...},
"2":{...}
}
}
I would then like to filter this data for use in a UI component into two separate categories (in this case where the installation.operator is equal to the current user). I've managed an implementation that works however it seems exhaustive:
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let assignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator == state.login;
})
.map(i => {
return state.installations[i];
});
let unassignedInstallations = Object.keys(state.installations)
.filter(i => {
return state.installations[i].operator != state.login;
})
.map(i => {
return state.installations[i];
});
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
I'm also new to ES6 and am not across all the new syntax shortcuts etc so I suspect there are much better ways to do this.
Is there a more succinct approach with a similar outcome?
you can do this with only one reduce():
const mapStateToProps = (state, ownProps) => {
console.log("mapStateToProps", state.installations);
let {assignedInstallations,
unassignedInstallations } = Object.keys(state.installations)
.reduce(function(acc, cur, i){
if(state.installations[i].operator == state.login){
acc.assignedInstallations.push(state.installations[i]);
}else{
acc.unassignedInstallations .push(state.installations[i]);
}
return acc
}, {assignedInstallations: [], unassignedInstallations: [] })
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};
lodash (An utility library) have a notion of collection (Here is an example https://lodash.com/docs/4.17.4#filter for filter function). It takes as input Object or Array and returns an Array. It seems to fit to your needs. Here is the refactored code:
import {
filter,
} from 'lodash'
const mapStateToProps = (state, ownProps) => {
let assignedInstallations = filter(state.installations, installation => installation.operator == state.login);
let unassignedInstallations = filter(state.installations, installation => installation.operator != state.login);
return {
assignedInstallations,
unassignedInstallations,
loginUserId: state.login
};
};