I am trying to make role guard for graphql field. Something like this:
import { Field, ObjectType } from 'type-graphql';
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import Role from '../role/role.entity';
#ObjectType()
#Entity()
class User {
#Field()
#PrimaryGeneratedColumn()
readonly id: number;
#Field()
#Column()
#Guard('USER_SEE_NAME') //this line
name: string;
#Field()
#Column()
surname: string;
}
export default User;
The goal is that if a user does not have a required role the field will be sent to the client with null value.
I have found out that I should use class-transformer but I haven't found any examples of nestjs. I have also looked into nestjs documentation but there are only examples of built-in decorators and they are not used in ObjectType.
I would use Authorized decorator but I need to access nestjs context to get userId and I haven't found a way to do it.
Do you now about some examples or a ways to do it?
So after a few days I found a solution. I wrote a custom Interceptor that looks like this:
import {
Injectable,
ExecutionContext,
CallHandler,
ClassSerializerInterceptor,
Inject,
} from '#nestjs/common';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Observable } from 'rxjs';
// eslint-disable-next-line import/no-extraneous-dependencies
import { map } from 'rxjs/operators';
import { GqlExecutionContext } from '#nestjs/graphql';
import { ClassTransformOptions } from '#nestjs/common/interfaces/external/class-transform-options.interface';
import { PlainLiteralObject } from '#nestjs/common/serializer/class-serializer.interceptor';
import { CLASS_SERIALIZER_OPTIONS } from '#nestjs/common/serializer/class-serializer.constants';
import { loadPackage } from '#nestjs/common/utils/load-package.util';
import AuthService from './auth.service';
const REFLECTOR = 'Reflector';
let classTransformer: any = {};
#Injectable()
class ResourceInterceptor extends ClassSerializerInterceptor {
constructor(
#Inject(AuthService) private authService: AuthService,
#Inject(REFLECTOR) protected readonly reflector: any,
) {
super(reflector);
classTransformer = loadPackage('class-transformer', 'ClassSerializerInterceptor', () =>
// eslint-disable-next-line global-require
require('class-transformer'),
);
// eslint-disable-next-line global-require
require('class-transformer');
}
serializeCustom(
response: PlainLiteralObject | Array<PlainLiteralObject>,
options: ClassTransformOptions,
user: number,
): PlainLiteralObject | PlainLiteralObject[] {
const isArray = Array.isArray(response);
if (!(typeof response === 'object') && response !== null && !isArray) {
return response;
}
return isArray
? (response as PlainLiteralObject[]).map(item => this.transformToClass(item, options))
: this.transformToGuard(this.transformToClass(response, options), user);
}
transformToClass(plainOrClass: any, options: ClassTransformOptions): PlainLiteralObject {
return plainOrClass && plainOrClass.constructor !== Object
? classTransformer.classToClass(plainOrClass, options)
: plainOrClass;
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const options = this.getContextOptionsCustom(context);
const ctx = GqlExecutionContext.create(context);
const { user } = ctx.getContext().req;
return next.handle().pipe(
map((res: PlainLiteralObject | Array<PlainLiteralObject>) => {
return this.serializeCustom(res, options, user);
}),
);
}
private getContextOptionsCustom(context: ExecutionContext): ClassTransformOptions | undefined {
return (
this.reflectSerializeMetadataCustom(context.getHandler()) ||
this.reflectSerializeMetadataCustom(context.getClass())
);
}
private reflectSerializeMetadataCustom(
obj: object | Function,
): ClassTransformOptions | undefined {
return this.reflector.get(CLASS_SERIALIZER_OPTIONS, obj);
}
async transformToGuard(response, userId: number) {
// eslint-disable-next-line no-restricted-syntax
for (const key of Object.keys(response)) {
const item = response[key];
// eslint-disable-next-line no-underscore-dangle
if (typeof item === 'object' && item !== null && item.__RESOURCE_GUARD__ === true) {
// eslint-disable-next-line no-await-in-loop
response[key] = (await this.authService.hasAccess(userId, item.resources))
? response[key].value
: null;
}
}
return response;
}
}
export default ResourceInterceptor;
Usage:
#UseInterceptors(ResourceInterceptor)
async userGetLogged(#CurrentUser() userId: number) {
return this.userService.findById(userId);
}
Related
Building state with ngxs in angular
//Component.ts
ngOnInit(): void {
const dataInsdeStore = this.store.selectSnapshot(MarketState);
if(!dataInsdeStore.loaded){
const category = this.marketplaceService.getMarketplaceCategories().subscribe(cat => {
const item = this.marketplaceService.getMarketplaceItems().subscribe(i => {
const obj = {
categories: cat,
items: i,
loaded: true
}
this.store.dispatch(new SetMarketAction(obj))
})
})
}
const test = this.store.selectSnapshot(MarketState);
this.store.dispatch(new GetMarketStateAction())
}
Upon checking wether the state is empty or not, and dispatching SetMarketAction I get empty object anyways
//Market.actions.ts
import { MarketStateInterface } from "../interfaces/interfaces";
class SetMarketAction {
static readonly type = '[MARKET] Set';
constructor(public marketState : MarketStateInterface){}
}
class GetMarketStateAction {
static readonly type = '[MARKET] Get';
}
export { SetMarketAction, GetMarketStateAction };
//Market.state.ts
import { Action, State, StateContext } from '#ngxs/store';
import { MarketStateInterface } from '../interfaces/interfaces';
import { Injectable } from '#angular/core';
import { GetMarketStateAction, SetMarketAction } from './market.actions';
#State<MarketStateInterface>({
name: 'market',
defaults: {
categories: [],
items: [],
loaded: false,
},
})
#Injectable()
export class MarketState {
constructor() {}
#Action(GetMarketStateAction)
getMarket(ctx: StateContext<MarketStateInterface>) {
const state = ctx.getState();
console.log(state);
return state;
}
#Action(SetMarketAction)
setMarket(ctx: StateContext<MarketStateInterface>, action: SetMarketAction) {
ctx.setState(action.marketState);
}
}
Every time I conosle.log the state at any given time I get the empty array, the interface of the state is correct
I am using mobx and decimal.js.
This is my store:
import Decimal from "decimal.js";
import { makeObservable, observable, action } from "mobx";
class MyStore {
public value: Decimal | null = null;
constructor() {
makeObservable(this, {
value: observable,
setValue: action,
});
}
public setValue() {
this.value = new Decimal(100);
}
}
export { MyStore };
This is my component:
import { useStoreValue } from "../../state/StoreContext";
import { observer } from "mobx-react-lite";
const MyPage = observer(() => {
const value = useStoreValue((rootStore) => rootStore.myStore.value);
return <span>{value.mul(5)}</span>;
});
export { MyPage };
As a result I get the following exception:
useObserver.ts:119 Uncaught TypeError: _value.mul is not a function
Any idea what I am missing?
A quick note and edit, it looks like this tutorial might be a winner
https://recursive.codes/blog/post/37
I am using the twilio conversation javascript client sdk on a angular 8 project.
Subscriptions, and async operations are still something I am working on understanding. My entire component that I am using twilio conversations on is below. After i will list my problems.
import {Component, Input, OnInit} from '#angular/core';
import {Client as ConversationsClient} from '#twilio/conversations';
#Component({
selector: 'app-shochat-contentcreator-chat',
templateUrl: './shochat-contentcreator-chat.component.html',
styleUrls: ['./shochat-contentcreator-chat.component.scss']
})
export class ShochatContentcreatorChatComponent implements OnInit {
constructor() { }
#Input() twiliochattoken = null;
conversationsclient;
currentconnectionstate = null;
ngOnInit(): void {
console.log('here we are and stuff tho');
let initConversations = async () => {
this.conversationsclient = await ConversationsClient.create(this.twiliochattoken);
this.conversationsclient.join().then((result) => {
console.log('here is the result of joining the conversation');
console.log(result);
});
}
this.conversationsclient.on("connectionStateChanged", (state) => {
if (state == "connecting") {
this.currentconnectionstate = 'connecting';
}
if (state == "connected") {
this.currentconnectionstate = 'connected';
}
if (state == 'disconnecting') {
this.currentconnectionstate = 'disconnecting';
}
if (state == 'disconnected') {
this.currentconnectionstate = 'disconnected';
}
if (state == 'denied') {
this.currentconnectionstate = 'denied';
}
});
this.conversationsclient.on("conversationJoined", (conversation) => {
console.log('here is the result of the conversationJoined hook');
console.log(conversation);
});
}
}
The below code snippet from the above is the problem:
this.conversationsclient.on("connectionStateChanged", (state) => {
if (state == "connecting") {
this.currentconnectionstate = 'connecting';
}
......
I am getting the error that the code cannot perform the .on function on undefined. Which makes sense, the above function is being called on the init function.
conversationsclient is undefined still. However what is the proper way to put this code? Inside the await ConversationsClient.create(.....) code?
Will that create the subscription that I want for when state changes?
Also how is my code looking based on its intent. I feel like I have missed the mark and not sure if I am close or far from hitting it.
im referencing the following docs
https://www.twilio.com/docs/chat/initializing-sdk-clients
This tutorial has the answer. I need to use a service.
chatservice:
import {EventEmitter, Injectable} from '#angular/core';
import * as Twilio from 'twilio-chat';
import Client from "twilio-chat";
import {Util} from "../util/util";
import {Channel} from "twilio-chat/lib/channel";
import {Router} from "#angular/router";
import {AuthService} from "./auth.service";
#Injectable()
export class ChatService {
public chatClient: Client;
public currentChannel: Channel;
public chatConnectedEmitter: EventEmitter<any> = new EventEmitter<any>()
public chatDisconnectedEmitter: EventEmitter<any> = new EventEmitter<any>()
constructor(
private router: Router,
private authService: AuthService,
) { }
connect(token) {
Twilio.Client.create(token).then( (client: Client) => {
this.chatClient = client;
this.chatConnectedEmitter.emit(true);
}).catch( (err: any) => {
this.chatDisconnectedEmitter.emit(true);
if( err.message.indexOf('token is expired') ) {
localStorage.removeItem('twackToken');
this.router.navigate(['/']);
}
});
}
getPublicChannels() {
return this.chatClient.getPublicChannelDescriptors();
}
getChannel(sid: string): Promise<Channel> {
return this.chatClient.getChannelBySid(sid);
}
createChannel(friendlyName: string, isPrivate: boolean=false) {
return this.chatClient.createChannel({friendlyName: friendlyName, isPrivate: isPrivate, uniqueName: Util.guid()});
}
}
component:
ngOnInit() {
this.isConnecting = true;
this.chatService.connect(localStorage.getItem('twackToken'));
this.conSub = this.chatService.chatConnectedEmitter.subscribe( () => {
this.isConnected = true;
this.isConnecting = false;
this.getChannels();
this.chatService.chatClient.on('channelAdded', () => {
this.getChannels();
});
this.chatService.chatClient.on('channelRemoved', () => {
this.getChannels();
});
this.chatService.chatClient.on('tokenExpired', () => {
this.authService.refreshToken();
});
})
this.disconSub = this.chatService.chatDisconnectedEmitter.subscribe( () => {
this.isConnecting = false;
this.isConnected = false;
});
}
I have this code where I connect all my reducers:
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import { login } from './login';
import { register } from './register';
import { dashboard } from './dashboard';
import { refreshedToken } from './refreshedToken';
import { updateUserReducer } from './updateUser';
import {History} from "history";
const rootReducer = (history: History) =>
combineReducers({
router: connectRouter(history),
dashboard,
login,
refreshedToken,
register,
updateUserReducer,
});
export default rootReducer;
export type RootState = ReturnType < typeof rootReducer >
I my component I do:
const selector = useSelector((s: RootState) => s);
also I get data from selector:
const httpResponse = selector.updateUserReducer.response;
The issue is that I get an error from TypeScript when I hover over updateUserReducer:
TS2339: Property 'updateUserReducer' does not exist on type 'Reducer { router: RouterState ; dashboard: { dashboardRequest: boolean; user: any; error: string; dashboardSuccess: boolean; } | { dashboardRequest: boolean; user: {}; error: any; dashboardSuccess: boolean; }; login: { ...; } | { ...; }; refreshedToken: { ...; } | { ...; }; register: { ...; }; ...'.
Why does this appear and how to solve the issue?
here is redux docs on how to use typescript with redux and react-redux
this code is fine i think the problem is with the return type of your reducer. check the document step by step for finding your exact problem.
I am trying to do a get request with Angular2 and Firebase database. The post request works perfectly well but a get request won't work. I don't know what am doing so wrong.
Here is my list.component
export class ListComponent implements OnInit {
notes = []
constructor(
private store: Store,
private noteService: ListingService
) {
}
ngOnInit() {
this.noteService.getNotes()
.subscribe();
this.store.changes.pluck('notes')
.subscribe((notes: any) => { this.notes = notes; console.log(this.notes)});
}
onCreateNote(note) {
this.noteService.createNote(note)
.subscribe();
}
onNoteChecked(note) {
this.noteService.completeNote(note)
.subscribe();
}
}
Here is my api.service
export class ApiService {
headers: Headers = new Headers({
'Content-Type': 'application/json',
Accept: 'application/json'
});
api_url: string = 'https://someapp-94b34.firebaseio.com/';
constructor(private http: Http) {
}
private getJson(response: Response) {
return response.json();
}
private checkForError(response: Response): Response {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
var error = new Error(response.statusText)
error['response'] = response;
console.error(error);
throw error;
}
}
get(path: string): Observable<any> {
return this.http.get(`${this.api_url}${path}.json`, { headers: this.headers })
.map(this.checkForError)
.catch(err => Observable.throw(err))
.map(this.getJson)
}
post(path: string, body): Observable<any> {
return this.http.post(
`${this.api_url}${path}.json`,
JSON.stringify(body),
{ headers: this.headers }
)
.map(this.checkForError)
.catch(err => Observable.throw(err))
.map(this.getJson)
}
}
Here is my listing.service
export class ListingService {
path: string = 'notes';
constructor(private storeHelper: StoreHelper, private apiService: ApiService) {}
createNote(note: Note) {
return this.apiService.post(this.path, note)
.do(savedNote => this.storeHelper.add('notes', savedNote))
}
getNotes() {
return this.apiService.get(this.path)
.do(res => this.storeHelper.update('notes', res.data));
}
completeNote(note: Note) {
return this.apiService.delete(`${this.path}/${note.id}`)
.do(res => this.storeHelper.findAndDelete('notes', res.id));
}
}
Here is my store.ts
export interface Note {
color: string,
title: string,
value: string,
id?: string | number,
createdAt?: string,
updatedAt?: string,
userId?: string
}
export interface State {
notes: Array<Note>
}
const defaultState = {
notes: []
}
const _store = new BehaviorSubject<State>(defaultState);
#Injectable()
export class Store {
private _store = _store;
changes = this._store.asObservable().distinctUntilChanged()
setState(state: State) {
this._store.next(state);
}
getState(): State {
return this._store.value;
}
purge() {
this._store.next(defaultState);
}
}
Here is my store-helper.ts
export class StoreHelper {
constructor(private store: Store) {}
update(prop, state) {
const currentState = this.store.getState();
this.store.setState(Object.assign({}, currentState, { [prop]: state }));
}
add(prop, state) {
const currentState = this.store.getState();
const collection = currentState[prop];
this.store.setState(Object.assign({}, currentState, { [prop]: [state, ...collection] }));
}
findAndUpdate(prop, state) {
const currentState = this.store.getState();
const collection = currentState[prop];
this.store.setState(Object.assign({}, currentState, {[prop]: collection.map(item => {
if (item.id !== state.id) {
return item;
}
return Object.assign({}, item, state)
})}))
}
findAndDelete(prop, id) {
const currentState = this.store.getState();
const collection = currentState[prop];
this.store.setState(Object.assign({}, currentState, {[prop]: collection.filter(item => item.id !== id)}));
}
}
And here is how am injecting the services to my app.module provider index.ts
import * as services from './services';
import { Store } from './store';
export const mapValuesToArray = (obj) => Object.keys(obj).map(key => obj[key]);
export const providers = [
Store,
...mapValuesToArray(services)
];
And app.module
import { providers } from './index'
providers: [providers, AnimationService]
The posting request works just well but the get request doesn't.
Here is the error I get:
Error encountered resolving symbol values statically.
Function calls are not supported.
Consider replacing the function or lambda with a reference to an exported function (position 3:33 in the original .ts file), resolving symbol mapValuesToArray in D:/angular2/someapp/src/app/index.ts, resolving symbol providers in D:/angular2/someapp/src/app/index.ts, resolving symbol providers in D:/angular2/someapp/src/app/index.ts, resolving symbol AppModule in D:/angular2/someapp/src/app/app.module.ts, resolving symbol AppModule in D:/angular2/someapp/src/app/app.module.ts
The AoT compiler cannot statically analyse the services you are providing, as you are using the Object.keys method to enumerate them.
You could solve the problem by adding an export to ./services.ts that explicitly lists the services:
export const SERVICE_PROVIDERS = [
ServiceOne,
ServiceTwo,
ServiceThree
];
Your import would then look like this:
import { SERVICE_PROVIDERS } from "./services";
import { Store } from './store';
export const providers = [
...SERVICE_PROVIDERS,
Store
];