Student dashboard, automatic redirection for home page
This commit is contained in:
parent
ef5e2f6cfb
commit
441b6ce68d
19 changed files with 219 additions and 14 deletions
backend/src
frontend/src/app
|
@ -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()
|
||||
|
|
|
@ -21,6 +21,8 @@ export class LoginResult {
|
|||
token: string;
|
||||
@field()
|
||||
user: UserResult;
|
||||
@field(returns => [String])
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
@objectType()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -7,6 +7,7 @@ mutation Login($email: String!, $password: String!) {
|
|||
isAdmin
|
||||
name
|
||||
}
|
||||
roles
|
||||
}
|
||||
}
|
||||
|
||||
|
|
41
frontend/src/app/start.component.ts
Normal file
41
frontend/src/app/start.component.ts
Normal 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;
|
||||
}
|
||||
}
|
0
frontend/src/app/students/courses/courses.component.css
Normal file
0
frontend/src/app/students/courses/courses.component.css
Normal file
1
frontend/src/app/students/courses/courses.component.html
Normal file
1
frontend/src/app/students/courses/courses.component.html
Normal file
|
@ -0,0 +1 @@
|
|||
<p>courses works!</p>
|
25
frontend/src/app/students/courses/courses.component.spec.ts
Normal file
25
frontend/src/app/students/courses/courses.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
16
frontend/src/app/students/courses/courses.component.ts
Normal file
16
frontend/src/app/students/courses/courses.component.ts
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
16
frontend/src/app/students/dashboard/dashboard.component.ts
Normal file
16
frontend/src/app/students/dashboard/dashboard.component.ts
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
25
frontend/src/app/students/students.module.ts
Normal file
25
frontend/src/app/students/students.module.ts
Normal 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 {
|
||||
}
|
Loading…
Add table
Reference in a new issue