Allow disabling fields, add auth checks with logout, don't allow self-unadmining
This commit is contained in:
parent
079449d980
commit
1f45a3fb84
7 changed files with 54 additions and 16 deletions
|
@ -118,6 +118,7 @@ export class UserController {
|
||||||
description: 'User model count',
|
description: 'User model count',
|
||||||
content: {'application/json': {schema: CountSchema}},
|
content: {'application/json': {schema: CountSchema}},
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async count(
|
async count(
|
||||||
@param.where(User) where?: Where<User>,
|
@param.where(User) where?: Where<User>,
|
||||||
): Promise<Count> {
|
): Promise<Count> {
|
||||||
|
@ -136,6 +137,7 @@ export class UserController {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async find(
|
async find(
|
||||||
@param.filter(User) filter?: Filter<User>,
|
@param.filter(User) filter?: Filter<User>,
|
||||||
): Promise<User[]> {
|
): Promise<User[]> {
|
||||||
|
@ -147,6 +149,7 @@ export class UserController {
|
||||||
description: 'User PATCH success count',
|
description: 'User PATCH success count',
|
||||||
content: {'application/json': {schema: CountSchema}},
|
content: {'application/json': {schema: CountSchema}},
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async updateAll(
|
async updateAll(
|
||||||
@requestBody({
|
@requestBody({
|
||||||
content: {
|
content: {
|
||||||
|
@ -170,6 +173,7 @@ export class UserController {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async findById(
|
async findById(
|
||||||
@param.path.number('id') id: number,
|
@param.path.number('id') id: number,
|
||||||
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
|
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
|
||||||
|
@ -181,6 +185,7 @@ export class UserController {
|
||||||
@response(204, {
|
@response(204, {
|
||||||
description: 'User PATCH success',
|
description: 'User PATCH success',
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async updateById(
|
async updateById(
|
||||||
@param.path.number('id') id: number,
|
@param.path.number('id') id: number,
|
||||||
@requestBody({
|
@requestBody({
|
||||||
|
@ -199,6 +204,7 @@ export class UserController {
|
||||||
@response(204, {
|
@response(204, {
|
||||||
description: 'User DELETE success',
|
description: 'User DELETE success',
|
||||||
})
|
})
|
||||||
|
@authenticate('jwt')
|
||||||
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
||||||
await this.userRepository.deleteById(id);
|
await this.userRepository.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,28 @@ import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { LoginService } from './auth/login.service';
|
import { LoginService } from './auth/login.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
|
|
||||||
constructor(private http: HttpClient, private loginService: LoginService) {
|
constructor(private http: HttpClient, private loginService: LoginService, private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
request(method: 'post' | 'get' | 'delete' | 'patch', url: string, body: any): Promise<any> {
|
request(method: 'post' | 'get' | 'delete' | 'patch', url: string, body: any): Promise<any> {
|
||||||
return this.http.request(method, environment.backendUrl + url, {
|
return this.http.request(method, environment.backendUrl + url, {
|
||||||
body,
|
body,
|
||||||
headers: {Authorization: 'Bearer ' + this.loginService.token}
|
headers: {Authorization: 'Bearer ' + this.loginService.token}
|
||||||
}).toPromise();
|
}).toPromise().catch(e => {
|
||||||
|
if (e.status === 401) {
|
||||||
|
this.loginService.deleteToken();
|
||||||
|
return this.router.navigateByUrl('/auth/login');
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPage<T>(url: string, limit: number, page: number): Promise<T[]> {
|
requestPage<T>(url: string, limit: number, page: number): Promise<T[]> {
|
||||||
|
@ -32,7 +40,6 @@ export class ApiService {
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
await this.request('post', '/users/logout', '');
|
await this.request('post', '/users/logout', '');
|
||||||
this.loginService.token = null;
|
this.loginService.deleteToken();
|
||||||
this.loginService.user = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,20 @@ import { User } from '../model/user.model';
|
||||||
})
|
})
|
||||||
export class LoginService {
|
export class LoginService {
|
||||||
|
|
||||||
token: string;
|
private tokenP: string;
|
||||||
user: User;
|
private userP: User;
|
||||||
|
|
||||||
|
get token(): string {
|
||||||
|
return this.tokenP;
|
||||||
|
}
|
||||||
|
|
||||||
|
get user(): User {
|
||||||
|
return this.userP;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
this.token = window.localStorage.getItem('token');
|
this.tokenP = window.localStorage.getItem('token');
|
||||||
this.user = JSON.parse(window.localStorage.getItem('user'));
|
this.userP = JSON.parse(window.localStorage.getItem('user'));
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser(email: string, password: string, name: string): Promise<void> {
|
async createUser(email: string, password: string, name: string): Promise<void> {
|
||||||
|
@ -26,8 +34,8 @@ export class LoginService {
|
||||||
email,
|
email,
|
||||||
password
|
password
|
||||||
}).toPromise();
|
}).toPromise();
|
||||||
this.token = resp.token;
|
this.tokenP = resp.token;
|
||||||
this.user = resp.user;
|
this.userP = resp.user;
|
||||||
window.localStorage.setItem('token', resp.token);
|
window.localStorage.setItem('token', resp.token);
|
||||||
window.localStorage.setItem('user', JSON.stringify(resp.user));
|
window.localStorage.setItem('user', JSON.stringify(resp.user));
|
||||||
return true;
|
return true;
|
||||||
|
@ -38,4 +46,11 @@ export class LoginService {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteToken(): void {
|
||||||
|
this.tokenP = null;
|
||||||
|
this.userP = null;
|
||||||
|
window.localStorage.removeItem('token');
|
||||||
|
window.localStorage.removeItem('user');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
<div *ngFor="let field of fields">
|
<div *ngFor="let field of fields">
|
||||||
<mat-label>{{ field.title }}</mat-label>
|
<mat-label>{{ field.title }}</mat-label>
|
||||||
<span [ngSwitch]="getType(item[field.name])">
|
<span [ngSwitch]="getType(item[field.name])">
|
||||||
<span *ngSwitchCase="'boolean'"><mat-checkbox [checked]="item[field.name]" [formControlName]="field.name"></mat-checkbox></span>
|
<span *ngSwitchCase="'boolean'">
|
||||||
|
<mat-checkbox [checked]="item[field.name]" [formControlName]="field.name"></mat-checkbox>
|
||||||
|
</span>
|
||||||
<mat-form-field *ngSwitchDefault>
|
<mat-form-field *ngSwitchDefault>
|
||||||
<input matInput [formControlName]="field.name" type="text" [value]="item[field.name]"/>
|
<input matInput [formControlName]="field.name" type="text" [value]="item[field.name]"/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
@Input() apiPath: string;
|
@Input() apiPath: string;
|
||||||
@Input() fields: { title: string, name: keyof T }[];
|
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[];
|
||||||
@Input() itemType: Type<T>;
|
@Input() itemType: Type<T>;
|
||||||
formGroup: FormGroup;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
|
@ -30,7 +30,13 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
if (!this.item && url[url.length - 1].path !== 'new') {
|
if (!this.item && url[url.length - 1].path !== 'new') {
|
||||||
this.item = await this.api.request('get', this.apiPath + '/' + this.route.snapshot.url[this.route.snapshot.url.length - 1], {});
|
this.item = await this.api.request('get', this.apiPath + '/' + this.route.snapshot.url[this.route.snapshot.url.length - 1], {});
|
||||||
}
|
}
|
||||||
this.formGroup = this.fb.group(this.fields.reduce((pv, cv) => Object.assign(pv, {[cv.name]: new FormControl()}), {}));
|
this.formGroup = this.fb.group(this.fields.reduce((pv, cv) => {
|
||||||
|
const control = new FormControl();
|
||||||
|
if (cv.readonly && cv.readonly(this.item)) {
|
||||||
|
control.disable();
|
||||||
|
}
|
||||||
|
return Object.assign(pv, {[cv.name]: control});
|
||||||
|
}, {}));
|
||||||
if (this.item) {
|
if (this.item) {
|
||||||
this.formGroup.patchValue(this.item);
|
this.formGroup.patchValue(this.item);
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,7 +54,7 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
} else {
|
} else {
|
||||||
await this.api.request('post', this.apiPath, this.formGroup.value);
|
await this.api.request('post', this.apiPath, this.formGroup.value);
|
||||||
}
|
}
|
||||||
await this.router.navigateByUrl(this.router.url.substring(0, this.router.url.lastIndexOf('/')));
|
await this.router.navigate(this.route.parent.snapshot.url.map(segment => segment.path));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.message);
|
alert(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<app-edit [apiPath]="'/users'" [itemType]="itemType" [fields]="[
|
<app-edit [apiPath]="'/users'" [itemType]="itemType" [fields]="[
|
||||||
{title: 'E-mail', name: 'email'},
|
{title: 'E-mail', name: 'email'},
|
||||||
{title: 'Név', name: 'name'},
|
{title: 'Név', name: 'name'},
|
||||||
{title: 'Admin', name: 'isAdmin'}
|
{title: 'Admin', name: 'isAdmin', readonly: isEditingSelf}
|
||||||
]"></app-edit>
|
]"></app-edit>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { User } from '../../model/user.model';
|
import { User } from '../../model/user.model';
|
||||||
|
import { LoginService } from '../../auth/login.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-edit',
|
selector: 'app-user-edit',
|
||||||
|
@ -8,8 +9,9 @@ import { User } from '../../model/user.model';
|
||||||
})
|
})
|
||||||
export class UserEditComponent implements OnInit {
|
export class UserEditComponent implements OnInit {
|
||||||
itemType = User;
|
itemType = User;
|
||||||
|
isEditingSelf = user => user.id === this.userService.user.id;
|
||||||
|
|
||||||
constructor() { }
|
constructor(private userService: LoginService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue