NestJS - Correct way of setting redis subscribers in a custom provider - redis

I'm building a simple application that will subscribe to a number of channels in a redis database.
Given the following code in the redis service:
// redis.service.ts
import { Injectable } from '#nestjs/common';
const NRP = require('node-redis-pubsub');
const config = {
port: 6379,
};
#Injectable()
export class RedisService {
private nrpClient = new NRP(config)
constructor() {
this.simpleListener()
}
simpleListener() {
this.nrpClient.on('__keyspace#0__:mykey', (data) => {
console.log(data)
})
}
}
and the code in the redis module:
// redis.module.ts
import { Module } from '#nestjs/common';
import { RedisService } from './redis.service';
#Module({
providers: [RedisService]
})
export class RedisModule {}
I've achieved the expected behavior for my application.
If I enter a session in my redis-cli and run:
127.0.0.1:6379> SADD mykey "test"
Then the NestJS logger prints the following:
[Nest] 306987 - 05/04/2021, 9:01:41 PM [NestApplication] Nest application successfully started +5ms
sadd
My question here is concerning my implementation design, rather than it's functionality.
There's a great lack of examples and documentation regarding the subject so I followed my instincts and wrote the code above based on this recommendation.
Then, given that a provided class will be a singleton if registered as a "provider" in the redis module, I simply did that and called the function that creates the subscription in the redis service constructor.
I'm just worried that I'm not following the best guidelines in building a custom NestJS provider, also I don't really know if setting up the listeners in the constructor is the best idea.
Is this the best/correct way of implementing the desired behavior?

Related

How to init several root provided services on app initialization in Angular 8?

I have several initializable services that implement this simple interface:
interface InitializableService {
init(): void;
}
Those services make some global subscriptions (local and external) that I need during all the live of my app. What I do now is inject those services into my app.component.ts and call them one for one. I want to automate this process.
I have found some info about ways to do this and I just found ways to do it when providing the services into an module. I provide that services in root and I do not want to change this, besides the initial arrangement take the same time to just injecting them into app.components.ts.
I tried creating a base class and extend it so each class service that implement it would become "automatic initialized" on app load but I wasn't able to get the list of services that extend that class to call init on them.
I have no idea where to start to achieve this, or if this is even possible with provideIn: 'root' services.
Have you tried to use Modules with providers ?
solution is , to create a module , something like core.module.ts
then import the module into app.module.ts.
The key is , inside of core module , using module with providers , you can initial the those useful services.
Using provide in root , I just don't like it
core.module.ts Will be something like this
import { ModuleWithProviders, NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { UsersData } from './data/users';
import { UsersService } from './mock/users.service';
const DATA_SERVICES = [
{ provide: UsersData, useClass: UsersService },
];
#NgModule({
imports: [
CommonModule,
],
providers: []
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
...DATA_SERVICES,
]
} as ModuleWithProviders;
}
}

Aurelia store connectTo never sets target property

I set the aurelia-store up as per the docs; in the main.ts at the bottom of all the plugins (from the skeleton app with dotnet core) I have as the last plugin defined:
aurelia.use.standardConfiguration()
.plugin(PLATFORM.moduleName('aurelia-store'), { initialState })
Then my app needs to login the user and save their bearer token.
await aurelia.start();
await aurelia.setRoot(PLATFORM.moduleName("modules/login/login.vm"));
In the login class I am trying to use the #connectTo decorator. However it never sets the dependency property. So I am stuck on this simple part at the very start of the app and my work already suggested not to use Aurelia but I said I wanted to for fast POC.
I've copied the docs exactly and still have the issue. Notably, I had to turn off strictNullCheck in the tsconfig to make the doc code parse.
Login.ts
#connectTo({
target: 'state',
selector: {
userToken: (store) => store.state.pipe(pluck('userToken')),
loginRedirected: (store) => store.state.pipe(pluck('loginRedirected'))
}
})
export class Login {
static inject = [Aurelia, Store]
public state: State;
app: Aurelia;
constructor(Aurelia, private store: Store<State>) {
this.app = Aurelia
store.registerAction('ChangeUserToken', this.changeUserToken)
store.registerAction('LoginRedirected', this.loginRedirect)
}
activate() {
... this.state is always undefined.
if (!this.state.loginRedirected) { //error
}
}
}
I expect the this.state property to have a state object populated from the global state store with the initialState values.
e.g.
{ userToken: "", loginRedirected: false }
I just need to set the userToken in login and retrieve it in app.js. This is not possible; what could be missing to make this basic function actually work?
ConnectTo is a helper decorator to avoid manual state subscriptions since the Stream of states is a vanilla rxjs observable. If you take a closer look at the official plugin documentation you will notice that it sets up the subscription in a different lifecycle hook.
That said connectTo cant solve everything and with manual subscription you have the most flexibility.
Dont give up with your quest you just had bad luck of falling into a more complicated scenario of startup timing right at the begin which easy enough might bite you with lots of other Frameworks/Libraries as well. Also make sure to visit the official discourse.aurelia.io forum and post back solutions to SO.

React Native project - cannot make API calls with AwsAmplify through custom library

I have a react-native app (without expo) called myapp.
I have a private custom package called myapp-core, where I handle AwsAmplify services (Auth, Storage) - to do login/signOut/etc.
I want to use myapp-core in myapp project, so I added it as a dependency in package.json ("myapp-core": "file:../myapp-core",) and then yarn install.
The problem I’m facing is that when I call myapp-core.authService.login(username, password) from the mobile project, I catch the error:
“ { “line”:177826, “column”: 17, “sourceURL”:
“http://10.0.2.2:8081/index.delta?platform=android&dev=true&minify=false”
} ”
From my research, that means my custom library cannot make api calls - but I don’t know exactly.
When I use aws-amplify's Auth object directly in my mobile project, it works.
Hopefully relevant code:
/**=============================**/
/** myapp/CoreServices.js **/
import { AmplifyService } from “myapp-core";
export default class CoreServices {
constructor() {
AmplifyService.configure();
const auth = AmplifyService.authService();
auth
.login(“myusername”, “mypassword”)
.then(user => console.warn("success", user))
.catch(error => console.warn("error", error));
}
}
/**=============================**/
/** myapp-core/AmplifySevice.js **/
import Amplify from 'aws-amplify';
import AuthService from '../AuthService/AuthService';
import awsConfigs from '../aws-exports';
class AmplifyService {
static authServiceInstance = null;
static storageServiceInstance = null;
static configure(config = awsConfigs) {
if (config === null || config === undefined) {
throw new Error('AmplifyService must be initialized with Auth and Storage configurations.');
}
Amplify.configure({
Auth: { /*...*/ },
Storage: { /*...*/ }
});
}
static authService() {
if (!this.authServiceInstance) {
this.authServiceInstance = new AuthService();
}
return this.authServiceInstance;
}
static storageService() {
console.warn('storage service');
// initialize storage service
// return storage service
}
}
I managed to solve my project's issue.
Maybe someone will benefit from my solution.
The problem didn't have anything to do with AwsAmplify, but with the way I linked the projects: myapp-core with myapp.
The issue was that in the myapp-core I am using the aws-amplify package that I would normally link to the mobile projects (react-native link) but in my case I assumed (wrongly) that it wouldn't be the case.
The solution was to link whatever packages were needed in the iOS/Android projects to install the proper pods/gradle libraries, like react-native link amazon-cognito-identity-js for authentication.
... and now I am finally happy :))
Links that shed some light:
https://github.com/facebook/create-react-app/issues/1492
https://eshlox.net/2018/11/12/aws-amplify-react-native-typeerror-cannot-read-property-computemodpow-of-undefined/
In case somebody thinks this isn't the solution and I got lucky or something, please comment or post another response.

Angular 5/6: protect route (route guard) without redirecting to error route

I have a bit of a pickle.
I am using Route guard (implementing CanActivate interface) to check if user is granted access to particular route:
const routes: Routes = [
{
path: '',
component: DashboardViewComponent
},
{
path: 'login',
component: LoginViewComponent
},
{
path: 'protected/foo',
component: FooViewComponent,
data: {allowAccessTo: ['Administrator']},
canActivate: [RouteGuard]
},
{
path: '**',
component: ErrorNotFoundViewComponent
}
];
Now it works great in protecting the '/protected/foo' route from activating, but I would like to tell the user that route he is trying to access is forbidden (similar to 403 Forbidden you may get from server).
The problem:
How do I show the user this special error view without redirecting him to error route which seams to be the preferred option by so many sources I have found?
And how do I still use my RouteGuard without actually loading the forbidden route, because if I check access inside my FooViewComponent and display different view it kind of defeats point of having RouteGuard in the first place.
Ideally I would like to have my RouteGuard not only returning false in canActivate() method, but also replace component completely with say ErrorForbiddenViewComponent. But I have no idea how to do it, or is it event possible. Any alternatives?
This is how my route guard looks now:
import {Injectable} from '#angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '#angular/router';
import {AuthService} from '../services/auth.service';
#Injectable()
export class RouteGuard implements CanActivate {
constructor(
private router: Router,
private auth: AuthService
) {}
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const { auth, router } = this;
const { allowAccessTo } = next.data;
const identity = auth.getIdentity();
if (
identity &&
allowAccessTo.indexOf(identity.role)
) {
// all good, proceed with activating route
return true;
}
if (identity) {
// TODO show ErrorForbiddenViewComponent instead of redirecting
console.log('403 Forbidden >>', next);
}
else {
// not logged in: redirect to login page with the return url
const [returnUrl, returnQueryParams] = state.url.split('?');
console.log('401 Unauthorised >>', returnUrl, returnQueryParams, next);
router.navigate(['/login'], {queryParams: {returnUrl, returnQueryParams}});
}
return false;
}
}
So I am just preventing route from loading, but I am not redirecting. I only redirect non logged visitors to login route.
Reasoning:
Routes should reflect certain state of application - visiting a route
url should recreate that state
To have error routes (except for 404 Not Found) would mean your application can actually recreate error states. This makes no sense
as why would you keep error state as state of your application? For
debugging purpose one should use logs (console or server), revisiting
error page (i.e. page refresh) might interfere with that.
Also by redirecting to error route app should provide some insights of error to user. For that matter either some parameter would need to
be passed via url or (far worse) keeping the error sate in some error
service and retrieve it upon accessing error route.
Also, ignoring the RouteGuard and just loading the component and checking access inside it may result in some extra dependencies
loaded which would not be used anyway (as user is not allowed), makes
the whole lazy loading much harder.
Does anyone have some kind of solution for this? I also wonder how come that after Angular 2+ being around for so long nobody had this kind of situation before? Everybody is just ok with redirecting?
Also keep in mind that although I am currently using the FooViewComponent synchronously, that may change in future!
I had once worked on the similar problem.
Sharing my stackblitz poc where I have created -
Authenticated Component (with guard)
Login Component
Permission Guard
Route (/auth route is provided with PermissionGuardService guard)
The guard is evaluating the user type and handling the redirection / error accordingly.
The use cases are -
User is not logged in (shows a toast with log in message)
User is not admin (shows a toast with unauthorised message)
User is admin (show a toast with success messaage)
I have stored the user in local storage.
EDIT - DEMO
Let me know if you need a special handling in it and I will update the code base.
Cheers!
After looking at angular2 example provided by Tarun Lalwani in comments of question and after taking deeper look into Dynamic component loader article on Angular docs I have managed to apply it to my code:
I no longer use my RouteGuard when specifying routes:
{
path: 'protected/foo',
component: FooViewComponent,
data: {allowAccessTo: ['Administrator']}, // admin only
canActivate: [RouteGuard]
},
Instead I have created special RouteGuardComponent and here is how I use it:
{
path: 'protected/foo',
component: RouteGuardComponent,
data: {component: FooViewComponent, allowAccessTo: ['Administrator']}
},
This is the code of RouteGuardComponent:
#Component({
selector: 'app-route-guard',
template: '<ng-template route-guard-bind-component></ng-template>
// note the use of special directive ^^
})
export class RouteGuardComponent implements OnInit {
#ViewChild(RouteGuardBindComponentDirective)
bindComponent: RouteGuardBindComponentDirective;
// ^^ and here we bind to that directive instance in template
constructor(
private auth: AuthService,
private route: ActivatedRoute,
private componentFactoryResolver: ComponentFactoryResolver
) {
}
ngOnInit() {
const {auth, route, componentFactoryResolver, bindComponent} = this;
const {component, allowAccessTo} = route.snapshot.data;
const identity = auth.getIdentity();
const hasAccess = identity && allowAccessTo.indexOf(identity.role);
const componentFactory = componentFactoryResolver.resolveComponentFactory(
hasAccess ?
component : // render component
ErrorForbiddenViewComponent // render Forbidden view
);
// finally use factory to create proper component
routeGuardBindComponentDirective
.viewContainerRef
.createComponent(componentFactory);
}
}
Also, this requires special directive to be defined (I am sure this can be done some other way, but I have just applied that Dynamic component example from Angular docs):
#Directive({
selector: '[route-guard-bind-component]'
})
export class RouteGuardBindComponentDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
It isn't full answer to my own question (but its a start), so if somebody provides something better (i.e. a way to still use canActivate and ability to lazy load) I'll make sure to take that into account.
Your RouteGuard can inject whatever service you're using for modal windows, and the .canActivate() can pop the modal without redirection to inform the user without disturbing the current state of the app.
We use toastr and its angular wrapper for this, since it creates a modeless pop-up that self-dismisses after so-many seconds, no OK/Cancel buttons needed.
I've recently come across the same problem. In the end, I couldn't manage to do this using CanActivate guard, so I've implemented the authorisation logic in the component that holds the <router-outlet>.
Here is its template:
<div class="content">
<router-outlet *ngIf="(accessAllowed$ | async) else accessDenied"></router-outlet>
</div>
<ng-template #accessDenied>
<div class="message">
<mat-icon>lock</mat-icon>
<span>Access denied.</span>
</div>
</ng-template>
And its source code:
import { ActivatedRoute, ActivationStart, Router } from '#angular/router';
import { filter, switchMap, take } from 'rxjs/operators';
import { merge, Observable, of } from 'rxjs';
import { Component } from '#angular/core';
#Component({
selector: 'app-panel-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.scss'],
})
export class PanelContentComponent {
/**
* A stream of flags whether access to current route is permitted.
*/
accessAllowed$: Observable<boolean>;
constructor(
permissions: UserPermissionsProviderContract, // A service for accessing user permissions; implementation omitted
route: ActivatedRoute,
router: Router,
) {
const streams: Observable<boolean>[] = [];
/*
The main purpose of this component is to replace `<router-outlet>` with "Access denied"
message, if necessary. Such logic will be universal for all possible route components, and
doesn't require any additional components - you will always have at least one component with
`<router-outlet>`.
This component contains `<router-outlet>`, which by definition means that all possible authorisable
routes are beneath it in the hierarchy.
This implicates that we cannot listen to `route.data` observable of `ActivatedRoute`, because the route
itself in this component will always be the parent route of the one we need to process.
So the only real (the least hacky, IMO) solution to access data of child routes is to listen to
router events.
However, by the time an instance of this component is constructed, all routing events will have been
triggered. This is especially important in case user loads the page on this route.
To solve that, we can merge two streams, the first one of which will be a single access flag
for **activated route**, and the second will be a stream of flags, emitted from router
events (e.g. caused by user navigating through app).
This approach requires that the authorised route is bottom-most in the hierarchy, because otherwise the
last value emitted from the stream created from router events will be `true`.
*/
const deepestChild = this.findDeepestTreeNode(route);
const currentData = deepestChild.routeConfig.data;
// `data.authActions` is just an array of strings in my case
if (currentData &&
currentData.authActions &&
Array.isArray(currentData.authActions) &&
currentData.authActions.length > 0) {
streams.push(
// `hasPermissions(actions: strings[]): Observable<boolean>`
permissions.hasPermissions(currentData.authActions).pipe(take(1))
);
} else {
// If the route in question doesn't have any authorisation logic, simply allow access
streams.push(of(true));
}
streams.push(router.events
.pipe(
filter(e => e instanceof ActivationStart),
switchMap((event: ActivationStart) => {
const data = event.snapshot.data;
if (data.authActions &&
Array.isArray(currentData.authActions) &&
data.authActions.length > 0) {
return permissions.hasPermissions(data.authActions);
}
return of(true);
}),
));
this.accessAllowed$ = merge(...streams);
}
/**
* Returns the deepest node in a tree with specified root node, or the first
* encountered node if there are several on the lowest level.
*
* #param root The root node.
*/
findDeepestTreeNode<T extends TreeNodeLike>(root: T): T {
const findDeepest = (node: T, level = 1): [number, T] => {
if (node.children && node.children.length > 0) {
const found = node.children.map(child => findDeepest(child as T, level + 1));
found.sort((a, b) => a[0] - b[0]);
return found[0];
} else {
return [level, node];
}
};
return findDeepest(root)[1];
}
}
interface TreeNodeLike {
children?: TreeNodeLike[];
}
I've explained the approach in comments in the source code, but in short: access authorisation data in route.data using router events, and replace <router-outlet> with an error message if access is denied.

Dynamic adapter pathForType based on auth id

Is there any way to include a user's authentication uid in a model adapter's path? For example, suppose you have a chat application, and a conversation model to represent a private message session between two users, where the data is stored something like this:
{
"conversations": {
"<userAuthUID>": {
"convo1": {...},
"convo2": {...},
...
},
"<anotherUserAuthUID>": {
...
}
}
}
So, with this structure, the adapter's path would need to be conversations/<currentUserAuthUID>.
(FYI this is an ember-cli project, if that makes any difference.)
It seems to be working using the following:
// adapters/conversation.js
import Ember from 'ember';
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
pathForType: function(type) {
//get the firebase authentication data via the `Firebase` instance
//that i have injected into my app via an initializer
var authData = this.container.lookup('app:firebase').getAuth();
return Ember.String.pluralize(type) + '/' + authData.uid;
}
});