diff --git a/backend/src/graphql-resolvers/user.resolver.ts b/backend/src/graphql-resolvers/user.resolver.ts index f14ec5a..4d34d4e 100644 --- a/backend/src/graphql-resolvers/user.resolver.ts +++ b/backend/src/graphql-resolvers/user.resolver.ts @@ -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() diff --git a/backend/src/graphql-types/user.ts b/backend/src/graphql-types/user.ts index a08bebc..f696d2c 100644 --- a/backend/src/graphql-types/user.ts +++ b/backend/src/graphql-types/user.ts @@ -21,6 +21,8 @@ export class LoginResult { token: string; @field() user: UserResult; + @field(returns => [String]) + roles: string[]; } @objectType() diff --git a/backend/src/observers/user-seeder.observer.ts b/backend/src/observers/user-seeder.observer.ts index c36961d..ad23df7 100644 --- a/backend/src/observers/user-seeder.observer.ts +++ b/backend/src/observers/user-seeder.observer.ts @@ -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 diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 133b02c..a7fe5d5 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -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} + ] } ]; diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index cdf9e56..b1c6ba3 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -6,7 +6,7 @@ Menü Főoldal - {{ item.title }} + {{ item.title }} diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 2006876..20441ec 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -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 { + 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 { @@ -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 { diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c8f765c..8063990 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -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, diff --git a/frontend/src/app/auth/login.service.ts b/frontend/src/app/auth/login.service.ts index 2d62ce0..442d003 100644 --- a/frontend/src/app/auth/login.service.ts +++ b/frontend/src/app/auth/login.service.ts @@ -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(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 { @@ -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 { diff --git a/frontend/src/app/graphql/user.graphql b/frontend/src/app/graphql/user.graphql index a6fb9cd..ef2cefb 100644 --- a/frontend/src/app/graphql/user.graphql +++ b/frontend/src/app/graphql/user.graphql @@ -7,6 +7,7 @@ mutation Login($email: String!, $password: String!) { isAdmin name } + roles } } diff --git a/frontend/src/app/start.component.ts b/frontend/src/app/start.component.ts new file mode 100644 index 0000000..20e9ba3 --- /dev/null +++ b/frontend/src/app/start.component.ts @@ -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: '

Üdv, {{ loginService.user?.name }}!

' +}) +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; + } +} diff --git a/frontend/src/app/students/courses/courses.component.css b/frontend/src/app/students/courses/courses.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/students/courses/courses.component.html b/frontend/src/app/students/courses/courses.component.html new file mode 100644 index 0000000..1aa8288 --- /dev/null +++ b/frontend/src/app/students/courses/courses.component.html @@ -0,0 +1 @@ +

courses works!

diff --git a/frontend/src/app/students/courses/courses.component.spec.ts b/frontend/src/app/students/courses/courses.component.spec.ts new file mode 100644 index 0000000..bb61c4c --- /dev/null +++ b/frontend/src/app/students/courses/courses.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CoursesComponent } from './courses.component'; + +describe('CoursesComponent', () => { + let component: CoursesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CoursesComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CoursesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/students/courses/courses.component.ts b/frontend/src/app/students/courses/courses.component.ts new file mode 100644 index 0000000..98b26ed --- /dev/null +++ b/frontend/src/app/students/courses/courses.component.ts @@ -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 { + } + +} diff --git a/frontend/src/app/students/dashboard/dashboard.component.css b/frontend/src/app/students/dashboard/dashboard.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/students/dashboard/dashboard.component.html b/frontend/src/app/students/dashboard/dashboard.component.html new file mode 100644 index 0000000..4a11d21 --- /dev/null +++ b/frontend/src/app/students/dashboard/dashboard.component.html @@ -0,0 +1,6 @@ + +

Teljesitett tárgyak

+ + + +
diff --git a/frontend/src/app/students/dashboard/dashboard.component.spec.ts b/frontend/src/app/students/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000..b491468 --- /dev/null +++ b/frontend/src/app/students/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DashboardComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/students/dashboard/dashboard.component.ts b/frontend/src/app/students/dashboard/dashboard.component.ts new file mode 100644 index 0000000..e24bed2 --- /dev/null +++ b/frontend/src/app/students/dashboard/dashboard.component.ts @@ -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 { + } + +} diff --git a/frontend/src/app/students/students.module.ts b/frontend/src/app/students/students.module.ts new file mode 100644 index 0000000..dd23305 --- /dev/null +++ b/frontend/src/app/students/students.module.ts @@ -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 { +}