I have a few decorators that when called, I want to setMetadata to use it in my logging,
In my controller, I have these:
#Post("somePath")
#Permission("somePermission")
#UseGuards(JwtAuthGuard)
#HttpCode(200)
#Grafana(
"endpoint",
"functionalmap"
)
async getSubscriptionFromPwebFormFilter(
#Body(ValidationPipe) someDto: someDtoType
): Promise<ISuccessResponse> {
// some logic
}
In my decorators I want to set some data into the metadata to use in my logging inteceptor,
Grafana decorator:
export const Grafana = (functionalMap: string, endpoint: string) =>
applyDecorators(
SetMetadata("endpoint", endpoint),
SetMetadata("functionalMap", functionalMap)
);
AuthGuard decorator:
#Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {
constructor(
private readonly reflector: Reflector,
private readonly someService: SomeService
) {
super();
}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const role = this.reflector.get<string>("permission", context.getHandler());
const request = context.switchToHttp().getRequest();
const { valueToLog } = request.body;
const jwtToken = request.headers.authorization;
console.log("check value exist", valueToLog);
SetMetadata("valueToLog", valueToLog);
}
Now, in my logging interceptor, I am getting all the values of the metadata this way:
#Injectable()
export default class LoggingInterceptor {
constructor(private readonly reflector: Reflector) {}
intercept(context: ExecutionContext, next: CallHandler) {
const executionStart = Date.now();
return next.handle().pipe(
tap((responseData) => {
const response = context.switchToHttp().getResponse<ServerResponse>();
const { statusCode } = response;
const valueToLog = this.reflector.get(
"valueToLog",
context.getHandler()
); // this is undefined
const endpoint = this.reflector.get("endpoint", context.getHandler()); // this have value
const functionalMap = this.reflector.get(
"functionalMap",
context.getHandler()
); // this have value
...
// some logic
})
);
}
}
In my case, the value of endpoint and functionalMap can be retrieved from the reflector, however, valueToLog is appearing as undefined,
Is setting of metadata not working for auth guard decorator?
I will be using the term "Decorator" in this explanation so I will first define it.
Decorators are functions that accept information about the decorated declaration.
Ex:
function ClassDecorator(target: typeof DecoratedClass) {
// do stuff
}
// it can then be used like this
#ClassDecorator
class DecoratedClass {}
when calling #SetMetadata(key, value) it returns a decorator. Functions like SetMetadata are referred to as Decorator Factories.
Decorators use the form #expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.
In your example you called SetMetadata("valueToLog", valueToLog). This returns a decorator. Decorators must be called with information about the decorated declaration. To actually attach the metadata you can do something like this:
SetMetadata("valueToLog", valueToLog)(context.getHandler());
Related
As the title says I'm trying to access a method that is declared in one class (Post) from another class (Comments) which is following a singleton pattern. Post class is a service class which has some methods to make API calls. So I need to have access to them from inside Comments class so that I can make API calls.
This is how a simplied version of Post class looks like right now:
#Injectable({
providedIn: 'root'
})
class PostService extends AnotherService {
constructor( auth: AuthService, http: HttpClient ) {
super('string', auth, http);
}
getPost( id: string ) {
return this.http.get(`posts/${id}`);
}
}
This is how the Comments class look like:
class Comments {
private postService: PostService;
private static instance;
private constructor() {}
static createInstance() {
if ( !Comments.instance ) {
Comments.instance = new Comments();
}
return Comments.instance;
}
getComments( id ) {
// these does not even run
this.postService.getPost( id )
.then( post => {
console.log( post );
})
.catch( error => {
console.log( error );
});
}
}
How can I go about accessing it?
=========UPDATE=======
Creating an instance of Comment class in another class called ClassC.
const instance - Comments.createInstance();
instance.getComments( id );
Use a new service to save your comment object data
let say We have a service named SharedDataService.
private _comments: Array<any> =[];// or array<IComment> (a defined interface from your part)
class SharedDataService(){}
get comments():Array<any>{
return this._comments}
set comments(value:Array<any>){
this._comments = value;
}
}
You should init PostService on your Comments Constructor
private constructor(private postService: PostService,private sharedDataService :SharedDataService) {
}
getComments() {
// these does not even run
this.postService.getPost( '1' )
.then( post => {
this.sharedDataService.comments = post // if You get an array of comments here
console.log( post );
console.log(this.comments)// getter function its new value has been set
})
.catch( error => {
console.log( error );
});
get comments(){
this.sharedDataService.comments
}
}
If You want to send two http request in parallel then get their values You should use combineLatest rxjs operator.
Your post service will be like this:
getPost(id: string) {
$postDataHttpRequest = this.http.get(`posts/${id}`);
$commentsDataHttpRequest = this.http.get(`posts/${id}/comments`);
return combineLatest($postDataHttpRequest, $commentsDataHttpRequest)
}
///
This is how the Comments class look like:
private constructor(private postService: PostService) {
}
getComments() {
this.postService.getPost( '1' )
.subscribe( (posts,comments) => {
console.log(posts);
console.log(comments);
},(error)=>{console.log(error)})
}
We use ThrottlerGuard in our NestJS application from #nestjs/throttler package to rate limit connections and it's implemented like this:
#Injectable()
export class RateLimiter extends ThrottlerGuard {
RATE_LIMIT_EXCEEDED = 'Rate limit exceeded.';
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
const wsData = context.switchToWs().getData();
const metadata: MetadataObject = wsData.internalRepr;
const clientTokenMetadata = metadata.get(TOKEN_NAME) as MetadataValue[];
const clientToken = clientTokenMetadata[0].toString();
const tokensArray = await this.storageService.getRecord(clientToken);
if (tokensArray.length >= limit) {
throw new RpcException({
code: Status.UNAVAILABLE,
message: this.RATE_LIMIT_EXCEEDED,
});
}
await this.storageService.addRecord(clientToken, ttl);
return true;
}
}
Now, I need to inject another service, let's say ConfigService with the usage of constructor, but because it is an extended class, I need to call super(); method which required three more original arguments inherited from ThrottlerGuard, but they are not exported from the package. Is there any other way to make it work besides the method with DI in constructor?
What you can do, instead of extending ThrottleGuard is injecting it in your constructor and call canActivate() in handleRequest(). Benefit when not extending you don'
I haven't tested this but you can try the following:
import {ExecutionContext, Injectable} from "#nestjs/common";
import {ConfigService} from "#nestjs/config";
import {InjectThrottlerStorage, ThrottlerGuard, ThrottlerStorage} from '#nestjs/throttler'
#Injectable()
export class RateLimiter {
constructor(
private throttle: ThrottlerGuard,
private configService: ConfigService,
#InjectThrottlerStorage() private storage: ThrottlerStorage) {
}
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
// TODO something with ConfigService
return this.throttle.canActivate(context);
}
}
ThrottleGuard source code
I am using a globals file to set key data needed for the application. Everything works until you use the browser refresh button. Once that is clicked, all the data I am loading from the globals.ts file comes back as undefined when the template is loaded. After the template is loaded, the promise populates the data. I need the promise data to populate before the ng-template binding.
Notice that I am trying to populate the global data by calling this.globals.init(token) from the app.component. Then in the helpful-links template I am trying to build a url based on data passed in from the parent component (myAccount.component). Oddly enough, data in the parent component also uses the globals file, and populates fine. Where did I go wrong?
-app.component
constructor(public globals: Globals){}
async ngOnInit() {
...
this.globals.init(token);
...
}
-Globals.ts
#Injectable()
export class Globals {
async init(token:string): Promise<any>{
if(token === undefined || token === '') {
token = this.oidcSecurityService.getIdToken();
}
const decoded = jwt_decode(token);
console.log(decoded, '<<<<<<<<<--------- decoded!', this.title)
this.email = decoded.email;
await this.get('getuserdata', {}).toPromise()
.then(async (res) => {
// THIS IS THE NEEDED DATA THAT COMES BACK AFTER THE BINDING. all except localstorage is undefined
localStorage.setItem('email', res['body'][0].email);
this.userRole = res['body'][0].title;
this.setUserId(res['body'][0].userid);
this.userid = res['body'][0].userid;
this.email = res['body'][0].email;
this.subscriberid = res['body'][0].fk_tk_subscriber_id;
this.isManagerRole = res['body'][0].title === 'HR Admin' ? true : false;
this.setTitle(res['body'][0].title);
this.profilePic = res['body'][0].profilephotourl;
});
}
-my7Account.component.html file
...
<app-tk-helpful-links [glob]='this.globals'></app-tk-helpful-links>
...
--helpful-links template
#Component({
selector: 'app-tk-helpful-links',
templateUrl: './tk-helpful-links.component.html',
styleUrls: ['./tk-helpful-links.component.scss']
})
export class HelpfulLinksComponent implements OnInit {
#Input() glob: Globals;
public res: {};
subscription: Subscription;
async getHelpfulLinks(): Promise<any> {
if(this.globals.userid === undefined) {
this.globals.init('');
}
return this.dataSvc.get(`gethelpfulLinks/${this.glob.userid}/${this.glob.subscriberid}`, {})
.subscribe(res => {
this.res = res['body'];
});
}
constructor(
private dataSvc: TkDataService,
public globals: Globals
) {
}
async ngOnInit() {
this.getHelpfulLinks();
}
}
Here is the next JavaScript class structure:
// data.service.ts
export class DataService {
public url = environment.url;
constructor(
private uri: string,
private httpClient: HttpClient,
) { }
getAll() {}
getOne(id: number) {}
create(data: any) {}
// etc...
}
Next is the general data model what can use the DataService's methods to communicate the server:
// Model.model.ts
import './data.service';
export class Model extends DataService {
all() {}
get() {
// parse and make some basic validation on the
// DataService.getOne() JSON result
}
// etc...
}
And finally I create a specific data model based on Model.model.ts:
// User.model.ts
import './Model.model.ts';
export class User extends Model {
id: number;
name: string;
email: string;
init() {
// make specific validation on Model.get() result
}
}
If I use the User class in my code, I can call the DataService's getAll() function directly if I want. But this is not a good thing, because in this case I miss the built-in validations.
How can I block the method inheritance on a class?
I'm looking for something like PHP's static method. The child class can use the methods, but his child can't.
I want something like this:
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // undefined
model.all(); // void
const user = new User();
user.getAll(); // undefined
user.all(); // void
Is there any way to do this?
You can prevent it to be built when you call it by adding private keyword to the function private getAll() {}. But private is a TypeScript feature, not Javascript, so if you force it to be built, it is still callable. There'll be no way to totally prevent it this moment.
So if you want it to be prevented in TypeScript, just add private keyword there. But it is not buildable, not return undefined as you expect. Otherwise, just replace the function with an undefined-returned function on children classes
With your code as shown and use case as stated, the only way to get the behavior you want is not to make Model extend DataService at all. There is a subsitution principle which says that if Model extends DataService, then someone should be able to treat a Model instance exactly as they would treat a DataService instance. Indeed, if a Model is a special type of DataService, and someone asks for a DataService instance, you should be able to give them a Model. It's no fair for you to tell them that they can't call the getAll() method on it. So the inheritance tree can't work the way you have it. Instead you could do something like this:
// ultimate parent class of both DataService and Model/User
class BaseDataService {
getOne(id: number) { }
create(data: any) { }
// etc...
}
// subclass with getAll()
class DataService extends BaseDataService {
getAll() {}
}
// subclass without getAll()
class Model extends BaseDataService {
all() { }
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // void
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
That works exactly as you've specified. Perfect, right?
Well, I get the sinking feeling that you will try this and get upset that you cannot call this.getAll() inside the implementation of Model or User... probably inside the suspiciously-empty body of the all() method in Model. And already I'm getting the urge to defensively point to the Minimum, Complete, and Verifiable Example article, since the question as stated doesn't seem to require this.
If you do require that, you still can't break the substitution principle. Instead I'd suggest making getAll() a protected method, and expose an all() method on DataService:
class DataService {
getOne(id: number) { }
create(data: any) { }
protected getAll() { }
all() {
this.getAll();
}
// etc...
}
class Model extends DataService {
all() {
this.getAll();
}
get() { }
// etc...
}
class User extends Model {
id!: number;
name!: string;
email!: string;
init() { }
}
const dataService = new DataService();
dataService.getAll(); // error
dataService.all(); // okay
const model = new Model();
model.getAll(); // error
model.all(); // okay
const user = new User();
user.getAll(); // error
user.all(); // okay
and live with the fact that getAll() is a purely internal method never meant to see the light of day.
Okay, hope one of those helps; good luck!
I have my service as PortAllocationService below
#Injectable({
providedIn: 'root'
})
export class PortAllocationService {
businessSwitchName: any;businessSwitchIp: any;businessSwitchportName: any;
businessSwitchNodeId: any;routerName: any;routerIp: any;routerDetailsportName: any;
routernodeID: any;aggSwitchName: any;aggSwitchPort: any;aggNodeIP: any;
aggNodeId: any;serviceName: any;convertorDetails: any;handoffPort: any;qosLoopingPort: any;
constructor(private http:HttpClient) { }
portService(da){
return this.http.post(url.portAllocationUrl , da).
subscribe ((response:any) => {
//Port Allocation response is mapped here
console.log(response);
// businessSwitchDetails
this.businessSwitchName = response.businessSwitchDetails.nodeName;
this.businessSwitchIp = response.businessSwitchDetails.nodeIP;
});
}
and my component as below
export class WimaxFormComponent {
data : any = {};
btsIp :any; vCategory:any;nVlan:any;
businessSwitchName:any;businessSwitchIp:any;businessSwitchportName:any;
routerName:any;routerIp:any;aggSwitchName:any;aggSwitchPort:any;
routerDetailsportName:any;routernodeID:any;aggNodeIP: any;aggNodeId: any;
businessSwitchNodeId: any;serviceName: any;convertorDetails: any;
handoffPort: any;qosLoopingPort: any;
serviceId: any;serviceType: any;customerName: any;
vReservedValue:boolean;cVlan: string;sVlan: any;
path: string;
constructor(
private service: PortAllocationService){
}
onChange(){
let portAllocationData = {
"serviceAttribute": {
"serviceType": this.serviceType,
"category": "category",
"name": this.serviceId
},
"btsIp": this.btsIp
}
console.log(portAllocationData);
this.service.portService(portAllocationData);
}
When i call the onChange function the call is made to service and we get the response from server . But i want to access all variable values from my service to Component like for example i have tried in constructor and onChange both as
this.businessSwitchName = service.businessSwitchName // this is coming as undefined
Could you please let me know how to access the variable values into component.
Rather than subscribing to the API call in the service itself, I would simply return the observable made by http.post(). And then move the subscribe to the component itself:
Service:
portService(da){ return this.http.post(url.portAllocationUrl , da) });
Component:
this.service.portService(portAllocationData).subscribe(response => {
// Do stuff with the response here
});
However, if you really want to keep the variables in the service, I would put the API call in the constructor of the service, change the fields in the service to start with an underscore (or some other local/private identifier) and then have get methods for each variable you want to be able to access.
Service:
get businessSwitchName() {
return this._businessSwitchName;
}
Component:
this.service.businessSwitchName