Student dashboard, automatic redirection for home page

This commit is contained in:
Norbi Peti 2022-05-14 20:27:38 +02:00
parent ef5e2f6cfb
commit 441b6ce68d
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
19 changed files with 219 additions and 14 deletions

View file

@ -1,7 +1,7 @@
import { arg, authorized, GraphQLBindings, ID, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql'; import { arg, authorized, GraphQLBindings, ID, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql';
import { User } from '../models'; import { User } from '../models';
import { repository } from '@loopback/repository'; import { repository } from '@loopback/repository';
import { RevTokenRepository, UserRepository } from '../repositories'; import { CourseUserRepository, RevTokenRepository, UserRepository } from '../repositories';
import { Context, inject } from '@loopback/core'; import { Context, inject } from '@loopback/core';
import { SzakdolgozatUserService } from '../services'; import { SzakdolgozatUserService } from '../services';
import { TokenServiceBindings, UserServiceBindings } from '@loopback/authentication-jwt'; import { TokenServiceBindings, UserServiceBindings } from '@loopback/authentication-jwt';
@ -18,6 +18,7 @@ export class UserResolver {
constructor( constructor(
@repository('UserRepository') private readonly userRepository: UserRepository, @repository('UserRepository') private readonly userRepository: UserRepository,
@repository('RevTokenRepository') private readonly revTokenRepo: RevTokenRepository, @repository('RevTokenRepository') private readonly revTokenRepo: RevTokenRepository,
@repository('CourseUserRepository') private readonly courseUserRepo: CourseUserRepository,
@inject(UserServiceBindings.USER_SERVICE) private readonly userService: SzakdolgozatUserService, @inject(UserServiceBindings.USER_SERVICE) private readonly userService: SzakdolgozatUserService,
@inject(GraphQLBindings.RESOLVER_DATA) private readonly resolverData: ResolverData, @inject(GraphQLBindings.RESOLVER_DATA) private readonly resolverData: ResolverData,
@inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService, @inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService,
@ -50,7 +51,17 @@ export class UserResolver {
// create a JSON Web Token based on the user profile // create a JSON Web Token based on the user profile
const token = await this.jwtService.generateToken(userProfile); const token = await this.jwtService.generateToken(userProfile);
return {token, user}; let roles = [];
if (user.isAdmin) {
roles.push('admin');
}
if ((await this.courseUserRepo.count({userId: user.id, role: 'teacher'})).count) {
roles.push('teacher');
}
if ((await this.courseUserRepo.count({userId: user.id, role: 'student'})).count) {
roles.push('student');
}
return {token, user, roles};
} }
@authorized() @authorized()

View file

@ -21,6 +21,8 @@ export class LoginResult {
token: string; token: string;
@field() @field()
user: UserResult; user: UserResult;
@field(returns => [String])
roles: string[];
} }
@objectType() @objectType()

View file

@ -3,6 +3,8 @@ import { UserRepository } from '../repositories';
import { User } from '../models'; import { User } from '../models';
import { Mock, MockFactory } from 'mockingbird'; import { Mock, MockFactory } from 'mockingbird';
import { genSalt, hash } from 'bcryptjs'; import { genSalt, hash } from 'bcryptjs';
// noinspection ES6UnusedImports
import { repository } from '@loopback/repository';
/** /**
* This class will be bound to the application as a `LifeCycleObserver` during * This class will be bound to the application as a `LifeCycleObserver` during

View file

@ -1,14 +1,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { AuthCheck } from './auth-check'; import { AuthCheck } from './auth-check';
import { StartComponent } from './start.component';
const routes: Routes = [ const routes: Routes = [
{
path: '',
children: [
{path: 'auth', loadChildren: async () => (await import('./auth/auth.module')).AuthModule}
]
},
{ {
path: '', path: '',
canActivate: [AuthCheck], canActivate: [AuthCheck],
@ -22,8 +17,25 @@ const routes: Routes = [
path: 'subjects', path: 'subjects',
loadChildren: async () => (await import('./subjects/subjects.module')).SubjectsModule, loadChildren: async () => (await import('./subjects/subjects.module')).SubjectsModule,
data: {title: 'Tárgyak'} data: {title: 'Tárgyak'}
},
{
path: 'student',
loadChildren: async () => (await import('./students/students.module')).StudentsModule,
data: {title: 'Hallagtói kezdőlap'}
},
{
path: '',
children: [
{path: '', component: StartComponent}
]
} }
] ]
},
{
path: '',
children: [
{path: 'auth', loadChildren: async () => (await import('./auth/auth.module')).AuthModule}
]
} }
]; ];

View file

@ -6,7 +6,7 @@
<mat-toolbar>Menü</mat-toolbar> <mat-toolbar>Menü</mat-toolbar>
<mat-nav-list> <mat-nav-list>
<a mat-list-item routerLink="/">Főoldal</a> <a mat-list-item routerLink="/">Főoldal</a>
<a mat-list-item *ngFor="let item of getMenuItems()" [routerLink]="'/' + item.path">{{ item.title }}</a> <a mat-list-item *ngFor="let item of getMenuItems() | async" [routerLink]="'/' + item.path">{{ item.title }}</a>
</mat-nav-list> </mat-nav-list>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content>

View file

@ -22,7 +22,8 @@ export class AppComponent implements OnInit {
menu: MenuItem[] = [ menu: MenuItem[] = [
{path: 'users', requiredRole: 'admin'}, {path: 'users', requiredRole: 'admin'},
{path: 'subjects', requiredRole: 'admin'} {path: 'subjects', requiredRole: 'admin'},
{path: 'student', requiredRole: 'student'}
]; ];
pageTitle: string; pageTitle: string;
@ -129,8 +130,11 @@ export class AppComponent implements OnInit {
await this.router.navigate(['/']); await this.router.navigate(['/']);
} }
getMenuItems(): MenuItem[] { getMenuItems(): Observable<MenuItem[]> {
return this.menu.filter(item => item.requiredRole === 'admin' ? this.loginService.user?.isAdmin : true); // TODO: Roles return this.loginService.rolesChanged.pipe(map(roles => {
const menu = this.menu.filter(({requiredRole}) => roles && requiredRole ? roles.includes(requiredRole) : !requiredRole);
return menu;
}));
} }
async routeActivated($event: any): Promise<void> { async routeActivated($event: any): Promise<void> {
@ -149,7 +153,7 @@ export class AppComponent implements OnInit {
} }
type MenuItem = { path: string, requiredRole: 'admin', title?: string }; // TODO: Role type MenuItem = { path: string, requiredRole: 'admin' | 'teacher' | 'student', title?: string };
type RouteSegment = { title: string, url: string, params: Params }; type RouteSegment = { title: string, url: string, params: Params };
export interface CustomTitleComponent { export interface CustomTitleComponent {

View file

@ -18,10 +18,12 @@ import { LoginService } from './auth/login.service';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { AuthCheck } from './auth-check'; import { AuthCheck } from './auth-check';
import { GraphQLModule } from './graphql.module'; import { GraphQLModule } from './graphql.module';
import { StartComponent } from './start.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent AppComponent,
StartComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,

View file

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { LoginGQL, LogoutGQL, RegisterGQL, UserResult } from '../services/graphql'; import { LoginGQL, LogoutGQL, RegisterGQL, UserResult } from '../services/graphql';
import { ReplaySubject } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -8,6 +9,8 @@ export class LoginService {
private tokenP: string; private tokenP: string;
private userP: UserResult; private userP: UserResult;
private rolesP: string[];
private rolesChangedP = new ReplaySubject<string[]>(1);
get token(): string { get token(): string {
return this.tokenP; return this.tokenP;
@ -17,9 +20,17 @@ export class LoginService {
return this.userP; return this.userP;
} }
get roles(): string[] {
return this.rolesP;
}
public readonly rolesChanged = this.rolesChangedP.asObservable();
constructor(private loginGQL: LoginGQL, private logoutGQL: LogoutGQL, private registerGQL: RegisterGQL) { constructor(private loginGQL: LoginGQL, private logoutGQL: LogoutGQL, private registerGQL: RegisterGQL) {
this.tokenP = window.localStorage.getItem('token'); this.tokenP = window.localStorage.getItem('token');
this.userP = JSON.parse(window.localStorage.getItem('user')); this.userP = JSON.parse(window.localStorage.getItem('user'));
this.rolesP = JSON.parse(window.localStorage.getItem('roles'));
this.rolesChangedP.next(this.rolesP);
} }
async createUser(email: string, password: string, name: string): Promise<void> { async createUser(email: string, password: string, name: string): Promise<void> {
@ -31,8 +42,11 @@ export class LoginService {
const resp = await this.loginGQL.mutate({email, password}).toPromise(); const resp = await this.loginGQL.mutate({email, password}).toPromise();
this.tokenP = resp.data.login.token; this.tokenP = resp.data.login.token;
this.userP = resp.data.login.user; this.userP = resp.data.login.user;
this.rolesP = resp.data.login.roles;
window.localStorage.setItem('token', this.tokenP); window.localStorage.setItem('token', this.tokenP);
window.localStorage.setItem('user', JSON.stringify(this.userP)); window.localStorage.setItem('user', JSON.stringify(this.userP));
window.localStorage.setItem('roles', JSON.stringify(this.rolesP));
this.rolesChangedP.next(this.rolesP);
return true; return true;
} catch (e) { } catch (e) {
if (e.status === 401 || e.status === 422) { if (e.status === 401 || e.status === 422) {
@ -47,6 +61,8 @@ export class LoginService {
this.userP = null; this.userP = null;
window.localStorage.removeItem('token'); window.localStorage.removeItem('token');
window.localStorage.removeItem('user'); window.localStorage.removeItem('user');
window.localStorage.removeItem('roles');
this.rolesChangedP.next();
} }
async logout(): Promise<void> { async logout(): Promise<void> {

View file

@ -7,6 +7,7 @@ mutation Login($email: String!, $password: String!) {
isAdmin isAdmin
name name
} }
roles
} }
} }

View file

@ -0,0 +1,41 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { LoginService } from './auth/login.service';
import { Subscription } from 'rxjs';
@Component({
template: '<h1>Üdv, {{ loginService.user?.name }}!</h1>'
})
export class StartComponent implements OnInit, OnDestroy {
sub: Subscription;
constructor(private router: Router, public loginService: LoginService) {
console.log('Wtf');
}
ngOnInit(): void {
// tslint:disable-next-line:no-shadowed-variable
this.sub = this.loginService.rolesChanged.subscribe(roles => {
if (!roles) {
this.router.navigate(['auth', 'login']);
return;
}
});
const roles = this.loginService.roles;
console.log('Roles', roles);
if (roles.includes('admin')) {
return;
}
if (roles.includes('teacher') && !roles.includes('student')) {
this.router.navigate(['teacher']);
} else if (roles.includes('student') && !roles.includes('teacher')) {
this.router.navigate(['student']);
}
}
ngOnDestroy(): void {
console.log('Unsubscribing');
this.sub?.unsubscribe();
this.sub = null;
}
}

View file

@ -0,0 +1 @@
<p>courses works!</p>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CoursesComponent } from './courses.component';
describe('CoursesComponent', () => {
let component: CoursesComponent;
let fixture: ComponentFixture<CoursesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CoursesComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CoursesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-courses',
templateUrl: './courses.component.html',
styleUrls: ['./courses.component.css']
})
export class CoursesComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}

View file

@ -0,0 +1,6 @@
<mat-card>
<mat-card-header><h2>Teljesitett tárgyak</h2></mat-card-header>
<mat-card-content>
<mat-progress-bar [value]="50"></mat-progress-bar>
</mat-card-content>
</mat-card>

View file

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DashboardComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}

View file

@ -0,0 +1,25 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardComponent } from './dashboard/dashboard.component';
import { CoursesComponent } from './courses/courses.component';
import { MatCardModule } from '@angular/material/card';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { RouterModule, Routes } from '@angular/router';
import { RouteData } from '../app-routing.module';
const routes: Routes = [
{path: '', component: DashboardComponent, data: {title: 'Kezdőlap'} as RouteData},
{path: 'courses', component: CoursesComponent, data: {title: 'Kurzusok'}}
];
@NgModule({
declarations: [DashboardComponent, CoursesComponent],
imports: [
CommonModule,
MatCardModule,
MatProgressBarModule,
RouterModule.forChild(routes)
]
})
export class StudentsModule {
}