Aurelia - Hiding routes in navmenu ends up displaying nothing - aurelia

I wanted to hide routes that were the user roles and I found THIS question on SO that is similar. I tried to implement it in my typescript project but its returning nothing and I am not sure why.
This is my implementation as it stands.
import { autoinject, bindable, bindingMode } from "aurelia-framework";
import { Router } from 'aurelia-router'
#autoinject
export class Navmenu {
public userName: string = 'anonymous';
private userRole = localStorage.getItem("user_role");
constructor(public authService: AuthService, public router: Router) {
this.userName = authService.getUserName();
console.log("userRole: ", this.userRole);
}
get routes() {
return this.router.navigation.filter(r => r.settings.roles === this.userRole );
}
}
My console.log shows "Admin" in the console but my filter doesnt filter it.
Here is how I have structured a route:
{
route: ["", "scheduler"],
name: "scheduler",
settings: {
icon: "scheduler",
auth: true,
roles: ["Employee", "Admin"], //These are my roles for this route.
pos: "left"
},
moduleId: PLATFORM.moduleName("../components/scheduler/scheduler"),
nav: true,
title: "scheduler"
},
Roles is an array.
How do I structure my filter so that it matches any userRole and thus returns a subset of filtered routes?

Look at this line in your router config:
roles: ["Employee", "Admin"]
Then at this in your getter:
r.settings.roles === this.userRole
roles is an array whereas this.userRole is a string, so the === operator will always return with false. Use indexOf or some instead:
return this.router.navigation.filter(r => r.settings.roles.indexOf(this.userRole) > -1);
or
return this.router.navigation.filter(r => r.settings.roles.some(t => t === this.userRole));

Related

NestJS - No metadata for "EmployeeRepository" was found with authentication

I try to do some authentication for my nestjs app but I got stuck with this error and I don't know where to look at
My app.module.ts
#Module({
imports: [
AgenciesModule,
ActionsModule,
AuthModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: '************',
username: '*********',
password: '*********',
database: '*********',
synchronize: true,
autoLoadEntities: true,
}),
EmployeesModule,
],
})
export class AppModule {}
My auth.service.ts
import { EmployeeRepository } from 'src/employees/entities/employee.repository';
#Injectable()
export class AuthService {
constructor(
#InjectRepository(EmployeeRepository)
private employeeRepository: EmployeeRepository,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.employeeRepository.findOne({
where: { email, password },
});
// this work with postman if I put false data
//const user = {
// email: "email",
// password: "password",
//}
if (user && user.email === email && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
}
My auth.controller.ts
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('login')
async login(#Body() body) {
return this.authService.login(body.email, body.password);
}
}
My auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
And my employee.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Employee } from './employee.entity';
#EntityRepository(Employee)
export class EmployeeRepository extends Repository<Employee> {}
I didn't put the different import for each file but I can provide them if needed
I checked all the file name and path from the differents import and they are all correct and I also updated my packages just in case.
These posts dosen't help :
NestJS - No metadata for "<Entity>" was found
No metadata for "User" was found using TypeOrm
Try this in auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])]
Change like this
imports: [TypeOrmModule.forFeature([Employee])]

How to send user_id to Google Analytics 4 when use vue-gtag, nuxtjs

I have a project with Nuxtjs.
I'm using https://github.com/MatteoGabriele/vue-gtag. I tried to send user_id to Google Analytics 4 when id of user exists with code blow but not working. I have checked in GA4 but not has data "Signed in with user ID includes yes":
import Vue from "vue";
import VueGtag from "vue-gtag";
export default ({ $config, $auth }) => {
const obj = {
config: {
id: 'G-XXXXXXXXXX'
}
}
if ($auth?.loggedIn) {
obj.config.params = {user_id: $auth.user.sub}
}
Vue.use(VueGtag, obj, app.router)
}
Code below is working:
import Vue from "vue";
import VueGtag from "vue-gtag";
export default ({ $config, $auth }) => {
Vue.use(VueGtag, {
config: {
id: 'G-XXXXXXXXXX',
params: {
user_id: '122xzczxc'
}
}
}, app.router)
}
Can i help you? I want to send user_id to GA4 when user_id exists.
To respond properly after the comment and what i think i have understand.
If you change your conditional by a default value like:
if ($auth?.loggedIn) {
obj.config.params = {user_id: '123-update-id'}
}
This not works.
But if you initialize your object like that:
const obj = {
config: {
{
id: 'G-XXXXXXXXXX',
params: {
user_id: '123-default-id'
}
}
}
The condition on $auth?.loggedIn will works and assign the value.
In that case the problem is just you need to have a default structure for your object. In this case the object not create a new property who can cause async or structure problem, it just update a property.
Just do that:
const obj = {
config: {
{
id: 'G-XXXXXXXXXX',
params: {
user_id: ''
}
}
}

An element descriptor's .kind property must be either "method" or "field"

I'm following the documentation for mobx-react-router but upon attempting to run my application I get the following error in the browser:
Uncaught TypeError: An element descriptor's .kind property must be either "method" or "field", but a decorator created an element descriptor with .kind "undefined"
at _toElementDescriptor (app.js:49988)
at _toElementFinisherExtras (app.js:49990)
at _decorateElement (app.js:49980)
at app.js:49976
at Array.forEach (<anonymous>)
at _decorateClass (app.js:49976)
at _decorate (app.js:49958)
at Module../src/App/UserStore.js (app.js:50012)
at __webpack_require__ (bootstrap:19)
at Module../src/index.js (index.js:1)
Here is how I intitialize:
const appContainer = document.getElementById('app');
if(appContainer) {
const browserHistory = createBrowserHistory()
const routingStore = new RouterStore();
const stores = {
users: userStore,
routing: routingStore
}
const history = syncHistoryWithStore(browserHistory, routingStore);
ReactDOM.render(
(
<Provider {...stores}>
<Router history={history}>
< App />
</Router>
</Provider>
),
appContainer);
}
And this is how I use:
#inject('routing')
#inject('users')
#observer
class App extends Component { ...
My UserStore:
import { observable, action, computed } from "mobx"
class UserStore {
#observable users = [];
#action addUser = (user) => {
this.users.push(user)
}
#computed get userCount () {
return this.users.length
}
}
const store = new UserStore();
export default store;
I've tried to Google for this error but it's returning no useful results. Any ideas what I am doing wrong?
If you're using Babel 7 install support for decorators:
npm i -D\
#babel/plugin-proposal-class-properties\
#babel/plugin-proposal-decorators
Then enable it in your .babelrc or webpack.config.js file:
{
"plugins": [
["#babel/plugin-proposal-decorators", { "legacy": true}],
["#babel/plugin-proposal-class-properties", { "loose": true}]
]
}
Note that the legacy mode is important (as is putting the decorators proposal first). Non-legacy mode is WIP.
Reference: https://mobx.js.org/best/decorators.html
While the accepted answer works, for anyone coming here from the same error message but a different context, this is likely because the decorator's signature changed as the proposal progressed.
Using { "legacy": true } uses one method signature, while {"decoratorsBeforeExport": true } uses another. The two flags are incompatible.
You can inspect what's happening with the given example
function log() {
console.log(arguments)
}
class Foo
#log
bar() {
console.log('hello')
}
}
new Foo().bar()
Using {"decoratorsBeforeExport": true } will yield
[Arguments] {
'0': Object [Descriptor] {
kind: 'method',
key: 'bar',
placement: 'prototype',
descriptor: {
value: [Function: bar],
writable: true,
configurable: true,
enumerable: false
}
}
}
Where {"legacy": true } would give you
[Arguments] {
'0': Foo {},
'1': 'bar',
'2': {
value: [Function: bar],
writable: true,
enumerable: false,
configurable: true
}
}
By not using the legacy semantics, writing a decorator is fairly simple. A decorator which returns its 0th argument is a no-op. You can also mutate before returning. Using the new semantics, you can describe a decorator as follows:
function log(obj) {
console.log('I am called once, when the decorator is set')
let fn = obj.descriptor.value
obj.descriptor.value = function() {
console.log('before invoke')
fn()
console.log('after invoke')
}
return obj
}
I changed the observable like so and it works (although not sure why):
import { observable, action, computed } from "mobx"
class UserStore {
#observable users;
constructor() {
this.users = []
}
#action addUser = (user) => {
this.users.push(user)
}
}
const store = new UserStore();
export default store;

Render different view dynamically in Aurelia

Is there any way in aurelia I can render different view dynamically.
async Activate(booking) {
//booking: is the route param
const hasRecord = await this.service.RecordExists(booking);
if (hasRecord) {
map(booking,form);
}
return {
//Render different template
}
}
You should try to tackle this issue in another way. Why would you want to navigate to a ViewModel and trigger its creation, just in order to not use it and load another ViewModel? Seems inefficient at best right?
Aurelia exposes pipelines on the router, you should do this check there and redirect accordingly. Look at the PreActivate step here, you could write something like this (pseudo code):
configureRouter(config, router) {
function step() {
return step.run;
}
step.run = async (navigationInstruction, next) => {
if(await this.service.RecordExists(navigationInstruction.queryParams...)
{
return next()
} else {
next.cancel(new Redirect('your other page'))
}
};
config.addPreActivateStep(step)
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
{ route: 'files/*path', name: 'files', moduleId: 'files/index', href:'#files', nav: true }
]);
}
EDIT
You can have cases where you don't want a redirect, for example you have users wanting to bookmark baseurl/businessobject/id, and the url is navigatable before the object actually exists
Then you can use the getViewStrategy() function on your ViewModel:
getViewStrategy(){
if(this.businessObj){
return 'existingObjectView.html';
} else {
return 'nonExisting.html';
}
}

Aurelia load routes dynamically / from fetch

I want to load menu options dynamically. so I'm wondering the best approach
I am able to use the code below to add routes after the page is loaded. This works for normal navigation, but does not work during a refresh.
Can configure router return a promise / how do I load menu items into the route?
#inject(HttpClient)
export class DocumentMenu {
router: Router;
documents : IDocument[];
heading = 'Document Router';
constructor(public http: HttpClient) {}
activate(): void {
this.http.fetch('http://localhost:17853/Document/GetDocuments?folderID=13244')
.then<IDocument[]>(response => response.json())
.then<IDocument[]>(docs => {
if ( docs ){
for( var doc of docs){
this.router.addRoute( { route : doc.DocumentID.toString(), name : doc.Name, moduleId: './documents/document', nav:true, title: doc.Name });
}
this.router.refreshNavigation();
}
return docs;
});
}
configureRouter(config: RouterConfiguration, router: Router) {
var routes = new Array();
routes.push(
{ route: 'index', name: 'index-name', moduleId: './documents/index', nav: false, title: 'Documents' } );
routes.push( { route: '', redirect: 'index' } );
config.map( routes );
this.router = router;
}
}
This does not answer your question, but I think it may be helpful to you and others with a similar issue.
The Dynamic Route Anti-Pattern
Your application has a number of different routes, all of which vary based on the state of the application. Therefore, you must first fetch the data, and then build the routes, and then register them with the router.
The reason this is an anti-pattern is because you will continuously need to update the router based on the state of the application, when Aurelia itself is built with static ways of describing dynamic content.
Dynamically Routing Homogeneous Data
Let's say you are building Google Drive, and you have a number of various files that could change as the user adds and removes them. For this case you have two categories of routes: Folders and Documents. Therefore, you make one route for each.
configureRouter(config) {
config.map([
{ route: 'folder/:id', moduleId: 'folder' }
{ route: 'document/:id', moduleId: 'document' }
}
}
class FolderViewModel {
activate({ id }) {
// get your specific folder data and load it into your folder view model
this.fetch('getDocuments?folderId=${id}')
}
}
class DocumentViewModel {
activate({ id }) {
// get your specific document and load it into your document view model
this.fetch('getDocuments?documentId=${id}')
}
}
Dynamically Routing Hetergeneous Data
Let's say instead you want to build YouTube. When user mjd10d logs in, he is welcome to watch videos to his heart's content, but he is not a premium content creator, and doesn't have access to the content creation portion of the site. The best way to handle this is to leave all possible routes in your application, and filter them based on the user's credentials in an AuthorizeStep.
configureRouter(config, router) {
config.addPipelineStep('authorize', AuthorizeStep);
}
#inject(UserSession)
class AuthorizeStep {
constructor(UserSession) {
this.user = UserSession;
}
run(navigationInstruction, next) {
var instructions = navigationInstruction.getAllInstructions()
if (!this.authorized(instructions.config)) {
return Redirect('404');
}
return next();
}
authorized(routeConfig) {
// something smart that returns false if unauthorized
return this.user.permissionLevel > routeConfig.requiredPermission;
}
}
Though not all cases will be authorization related, you can always register your own pipeline step using the addPipelineStep API
You can add routes dynamically (at startup or anytime for that matter) by having a single fixed (static) route in the "configureRouter" method (in app.ts), to which you then add all the other routes dynamically, when your fetch completes, like so:
configureRouter(config, router) {
config.title = 'SM';
//configure one static route:
config.map([
{ route: ['', 'welcome'], name: 'welcome', moduleId: 'welcome/welcome', title: 'Welcome' }
]);
routeMaps(this.navRepo) //your repo/service doing the async HTTP fetch, returning a Promise<Array<any>> (i.e., the routes)
.then(r => {
r.forEach(route => this.router.addRoute(route));
//once all dynamic routes are added, refresh navigation:
this.router.refreshNavigation();
});
this.router = router;
}
The "routeMaps" function is just a wrapper around the repo call and a mapping of the result to the Array of route items.
You can return a promise in activate. if activate() returns a promise, configureRouter() doesnt fire until the promise returned in activate() is resolved.
I ended up preparing the routes in activate like below:
activate(){
return this.http.fetch('url')
.then(response => response.json())
.then(docs => {
this.routerMapped = docs;
});
}
configureRouter(config, router) {
//build the routes from this.routermapped if necessary
config.map( this.routerMapped );
this.router = router;
}
To make this work, I created the routes in the constructor with a synchronous request
export class DocumentMenu {
...
routes : RouteConfig[];
constructor(http: HttpClient) {
this.http = http;
var folderID = window.location.hash.split('/')[2]
this.routes = new Array<RouteConfig>();
this.routes.push ( { route: 'index', name: 'index-name', moduleId: './documents/index', nav: false, title: 'Documents' });
this.routes.push ( { route: '', redirect: 'index' } );
for( var route of this.getRoutes( folderID )){
this.routes.push( route );
}
}
getRoutes(folderID: string) : RouteConfig[]
{
var routes = new Array<RouteConfig>();
var docsURL = 'http://localhost:17853/Document/GetDocuments?folderID=' + folderID;
// synchronous request
var docsResp = $.ajax({
type: "GET",
url: docsURL,
async: false,
cache:false
}).responseText;
var docs = JSON.parse( docsResp );
for( var doc of docs ){
routes.push( { route : doc.DocumentID.toString(), name : doc.Name, moduleId: './documents/document', nav:true, title: doc.Name });
}
return routes;
}
configureRouter(config: RouterConfiguration, router: Router) {
config.map( this.routes );
this.router = router;
}
...