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 { User } from '../models';
import { repository } from '@loopback/repository';
import { RevTokenRepository, UserRepository } from '../repositories';
import { CourseUserRepository, RevTokenRepository, UserRepository } from '../repositories';
import { Context, inject } from '@loopback/core';
import { SzakdolgozatUserService } from '../services';
import { TokenServiceBindings, UserServiceBindings } from '@loopback/authentication-jwt';
@ -18,6 +18,7 @@ export class UserResolver {
constructor(
@repository('UserRepository') private readonly userRepository: UserRepository,
@repository('RevTokenRepository') private readonly revTokenRepo: RevTokenRepository,
@repository('CourseUserRepository') private readonly courseUserRepo: CourseUserRepository,
@inject(UserServiceBindings.USER_SERVICE) private readonly userService: SzakdolgozatUserService,
@inject(GraphQLBindings.RESOLVER_DATA) private readonly resolverData: ResolverData,
@inject(TokenServiceBindings.TOKEN_SERVICE) public jwtService: TokenService,
@ -50,7 +51,17 @@ export class UserResolver {
// create a JSON Web Token based on the user profile
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()

View file

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

View file

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

View file

@ -1,14 +1,9 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthCheck } from './auth-check';
import { StartComponent } from './start.component';
const routes: Routes = [
{
path: '',
children: [
{path: 'auth', loadChildren: async () => (await import('./auth/auth.module')).AuthModule}
]
},
{
path: '',
canActivate: [AuthCheck],
@ -22,8 +17,25 @@ const routes: Routes = [
path: 'subjects',
loadChildren: async () => (await import('./subjects/subjects.module')).SubjectsModule,
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-nav-list>
<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-sidenav>
<mat-sidenav-content>

View file

@ -22,7 +22,8 @@ export class AppComponent implements OnInit {
menu: MenuItem[] = [
{path: 'users', requiredRole: 'admin'},
{path: 'subjects', requiredRole: 'admin'}
{path: 'subjects', requiredRole: 'admin'},
{path: 'student', requiredRole: 'student'}
];
pageTitle: string;
@ -129,8 +130,11 @@ export class AppComponent implements OnInit {
await this.router.navigate(['/']);
}
getMenuItems(): MenuItem[] {
return this.menu.filter(item => item.requiredRole === 'admin' ? this.loginService.user?.isAdmin : true); // TODO: Roles
getMenuItems(): Observable<MenuItem[]> {
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> {
@ -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 };
export interface CustomTitleComponent {

View file

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

View file

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

View file

@ -7,6 +7,7 @@ mutation Login($email: String!, $password: String!) {
isAdmin
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 {
}