add api services to frontend
This commit is contained in:
parent
0e0060ffb5
commit
e0230b26ae
|
@ -30,7 +30,7 @@ export class AuthController {
|
||||||
@Post('login')
|
@Post('login')
|
||||||
async login(@Request() req: AuthFasityRequest) {
|
async login(@Request() req: AuthFasityRequest) {
|
||||||
const response: AuthLoginResponse = {
|
const response: AuthLoginResponse = {
|
||||||
access_token: await this.authService.createToken(req.user),
|
jwt_token: await this.authService.createToken(req.user),
|
||||||
};
|
};
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
"ngx-dropzone": "^3.1.0",
|
"ngx-dropzone": "^3.1.0",
|
||||||
"picsur-shared": "*",
|
"picsur-shared": "*",
|
||||||
"rxjs": "~7.5.4",
|
"rxjs": "~7.5.4",
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ApiService } from './api.service';
|
import { ApiService } from './api.service';
|
||||||
import { ImageService } from './image.service';
|
import { ImageService } from './image.service';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { KeyService } from './key.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [ApiService, ImageService],
|
providers: [ApiService, ImageService, UserService, KeyService],
|
||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
})
|
})
|
||||||
export class ApiModule {}
|
export class ApiModule {}
|
||||||
|
|
|
@ -8,12 +8,13 @@ import {
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import { ClassConstructor, plainToClass } from 'class-transformer';
|
import { ClassConstructor, plainToClass } from 'class-transformer';
|
||||||
import { MultiPartRequest } from '../models/multi-part-request';
|
import { MultiPartRequest } from '../models/multi-part-request';
|
||||||
|
import { KeyService } from './key.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ApiService {
|
export class ApiService {
|
||||||
constructor() {}
|
constructor(private keyService: KeyService) {}
|
||||||
|
|
||||||
public async get<T extends Object>(
|
public async get<T extends Object>(
|
||||||
type: ClassConstructor<T>,
|
type: ClassConstructor<T>,
|
||||||
|
@ -52,16 +53,34 @@ export class ApiService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetch(
|
private async fetchSafeJson<T extends Object>(
|
||||||
|
type: ClassConstructor<T>,
|
||||||
url: RequestInfo,
|
url: RequestInfo,
|
||||||
options: RequestInit
|
options: RequestInit
|
||||||
): AsyncFailable<Response> {
|
): AsyncFailable<T> {
|
||||||
try {
|
let result = await this.fetchJsonAs<ApiResponse<T>>(url, options);
|
||||||
return await window.fetch(url, options);
|
if (HasFailed(result)) return result;
|
||||||
} catch (e: any) {
|
|
||||||
console.warn(e);
|
if (result.success === false) return Fail(result.data.message);
|
||||||
|
|
||||||
|
const resultClass = plainToClass<
|
||||||
|
ApiSuccessResponse<T>,
|
||||||
|
ApiSuccessResponse<T>
|
||||||
|
>(ApiSuccessResponse, result);
|
||||||
|
const resultErrors = await validate(resultClass);
|
||||||
|
if (resultErrors.length > 0) {
|
||||||
|
console.warn('result', resultErrors);
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dataClass = plainToClass(type, result.data);
|
||||||
|
const dataErrors = await validate(dataClass);
|
||||||
|
if (dataErrors.length > 0) {
|
||||||
|
console.warn('data', dataErrors);
|
||||||
|
return Fail('Something went wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchJsonAs<T>(
|
private async fetchJsonAs<T>(
|
||||||
|
@ -95,33 +114,24 @@ export class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchSafeJson<T extends Object>(
|
private async fetch(
|
||||||
type: ClassConstructor<T>,
|
|
||||||
url: RequestInfo,
|
url: RequestInfo,
|
||||||
options: RequestInit
|
options: RequestInit
|
||||||
): AsyncFailable<T> {
|
): AsyncFailable<Response> {
|
||||||
let result = await this.fetchJsonAs<ApiResponse<T>>(url, options);
|
try {
|
||||||
if (HasFailed(result)) return result;
|
const key = this.keyService.get();
|
||||||
|
const isJSON = typeof options.body === 'string';
|
||||||
|
|
||||||
if (result.success === false) return Fail(result.data.message);
|
const headers: any = options.headers || {};
|
||||||
|
if (key !== null)
|
||||||
|
headers['Authorization'] = `Bearer ${this.keyService.get()}`;
|
||||||
|
if (isJSON) headers['Content-Type'] = 'application/json';
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
const resultClass = plainToClass<
|
return await window.fetch(url, options);
|
||||||
ApiSuccessResponse<T>,
|
} catch (e: any) {
|
||||||
ApiSuccessResponse<T>
|
console.warn(e);
|
||||||
>(ApiSuccessResponse, result);
|
|
||||||
const resultErrors = await validate(resultClass);
|
|
||||||
if (resultErrors.length > 0) {
|
|
||||||
console.warn('result', resultErrors);
|
|
||||||
return Fail('Something went wrong');
|
return Fail('Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataClass = plainToClass(type, result.data);
|
|
||||||
const dataErrors = await validate(dataClass);
|
|
||||||
if (dataErrors.length > 0) {
|
|
||||||
console.warn('data', dataErrors);
|
|
||||||
return Fail('Something went wrong');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
frontend/src/app/api/key.service.ts
Normal file
36
frontend/src/app/api/key.service.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class KeyService {
|
||||||
|
private key: string | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private load() {
|
||||||
|
this.key = localStorage.getItem('apiKey');
|
||||||
|
}
|
||||||
|
|
||||||
|
private store() {
|
||||||
|
if (this.key) localStorage.setItem('apiKey', this.key);
|
||||||
|
else localStorage.removeItem('apiKey');
|
||||||
|
}
|
||||||
|
|
||||||
|
public get() {
|
||||||
|
setTimeout(this.load.bind(this), 0);
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(key: string) {
|
||||||
|
this.key = key;
|
||||||
|
setTimeout(this.store.bind(this), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.key = null;
|
||||||
|
setTimeout(this.store.bind(this), 0);
|
||||||
|
}
|
||||||
|
}
|
116
frontend/src/app/api/user.service.ts
Normal file
116
frontend/src/app/api/user.service.ts
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { Injectable, OnInit } from '@angular/core';
|
||||||
|
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||||
|
import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
|
import jwt_decode from 'jwt-decode';
|
||||||
|
import {
|
||||||
|
AsyncFailable,
|
||||||
|
Fail,
|
||||||
|
Failable,
|
||||||
|
HasFailed,
|
||||||
|
} from 'picsur-shared/dist/types';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
|
import {
|
||||||
|
AuthLoginRequest,
|
||||||
|
AuthLoginResponse,
|
||||||
|
AuthMeResponse,
|
||||||
|
JwtDataDto,
|
||||||
|
} from 'picsur-shared/dist/dto/auth.dto';
|
||||||
|
import { validate } from 'class-validator';
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
import { KeyService } from './key.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UserService {
|
||||||
|
public get user() {
|
||||||
|
return this.userSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
private userSubject = new BehaviorSubject<EUser | null>(null);
|
||||||
|
|
||||||
|
constructor(private api: ApiService, private key: KeyService) {
|
||||||
|
this.init().catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async login(username: string, password: string): AsyncFailable<EUser> {
|
||||||
|
const request: AuthLoginRequest = {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await this.api.post(
|
||||||
|
AuthLoginRequest,
|
||||||
|
AuthLoginResponse,
|
||||||
|
'/api/auth/login',
|
||||||
|
request
|
||||||
|
);
|
||||||
|
|
||||||
|
if (HasFailed(response)) return response;
|
||||||
|
this.key.set(response.jwt_token);
|
||||||
|
|
||||||
|
const user = await this.fetchUser();
|
||||||
|
if (HasFailed(user)) return user;
|
||||||
|
|
||||||
|
this.userSubject.next(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async logout(): AsyncFailable<EUser> {
|
||||||
|
const value = this.userSubject.getValue();
|
||||||
|
this.key.clear();
|
||||||
|
this.userSubject.next(null);
|
||||||
|
if (value === null) {
|
||||||
|
return Fail('Not logged in');
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init() {
|
||||||
|
const apikey = await this.key.get();
|
||||||
|
if (!apikey) return;
|
||||||
|
|
||||||
|
const user = await this.extractUser(apikey);
|
||||||
|
if (HasFailed(user)) {
|
||||||
|
console.warn(user.getReason());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userSubject.next(user);
|
||||||
|
|
||||||
|
const fetchedUser = await this.fetchUser();
|
||||||
|
if (HasFailed(fetchedUser)) {
|
||||||
|
console.warn(fetchedUser.getReason());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userSubject.next(fetchedUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async extractUser(token: string): AsyncFailable<EUser> {
|
||||||
|
let decoded: any;
|
||||||
|
try {
|
||||||
|
decoded = jwt_decode(token);
|
||||||
|
} catch (e) {
|
||||||
|
return Fail('Invalid token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwtData = plainToClass(JwtDataDto, decoded);
|
||||||
|
const errors = await validate(jwtData);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.warn(errors);
|
||||||
|
return Fail('Invalid token data');
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwtData.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchUser(): AsyncFailable<EUser> {
|
||||||
|
const got = await this.api.get(AuthMeResponse, '/api/auth/me');
|
||||||
|
if (HasFailed(got)) return got;
|
||||||
|
|
||||||
|
this.key.set(got.newJwtToken);
|
||||||
|
return got.user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,5 +6,5 @@
|
||||||
<span>Picsur</span>
|
<span>Picsur</span>
|
||||||
</a>
|
</a>
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
<button mat-stroked-button>Login</button>
|
<button mat-stroked-button (click)="doLogin()">Login</button>
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
})
|
})
|
||||||
export class HeaderComponent {}
|
export class HeaderComponent {
|
||||||
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
|
doLogin() {
|
||||||
|
this.router.navigate(['/login']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ import { CommonModule } from '@angular/common';
|
||||||
import { HeaderComponent } from './header.component';
|
import { HeaderComponent } from './header.component';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { AppRouterModule } from 'src/app/router/router.module';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, MatToolbarModule, MatButtonModule, AppRouterModule],
|
imports: [CommonModule, MatToolbarModule, MatButtonModule, RouterModule],
|
||||||
declarations: [HeaderComponent],
|
declarations: [HeaderComponent],
|
||||||
exports: [HeaderComponent],
|
exports: [HeaderComponent],
|
||||||
})
|
})
|
||||||
export class HeaderModule {}
|
export class HeaderModule {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
4
frontend/src/app/models/login.ts
Normal file
4
frontend/src/app/models/login.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface LoginModel {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
|
@ -11,6 +11,11 @@ import { ViewComponent } from '../routes/view/view.component';
|
||||||
import { CopyFieldModule } from '../components/copy-field/copy-field.module';
|
import { CopyFieldModule } from '../components/copy-field/copy-field.module';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { UtilModule } from '../util/util.module';
|
import { UtilModule } from '../util/util.module';
|
||||||
|
import { LoginComponent } from '../routes/login/login.component';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {MatFormFieldControl, MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: UploadComponent },
|
{ path: '', component: UploadComponent },
|
||||||
|
@ -19,21 +24,32 @@ const routes: Routes = [
|
||||||
component: ProcessingComponent,
|
component: ProcessingComponent,
|
||||||
},
|
},
|
||||||
{ path: 'view/:hash', component: ViewComponent },
|
{ path: 'view/:hash', component: ViewComponent },
|
||||||
|
{ path: 'login', component: LoginComponent },
|
||||||
{ path: '**', component: PageNotFoundComponent },
|
{ path: '**', component: PageNotFoundComponent },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
CommonModule,
|
||||||
NgxDropzoneModule,
|
NgxDropzoneModule,
|
||||||
UtilModule,
|
UtilModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatInputModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
PageNotFoundModule,
|
PageNotFoundModule,
|
||||||
CopyFieldModule,
|
CopyFieldModule,
|
||||||
ApiModule,
|
ApiModule,
|
||||||
|
FormsModule,
|
||||||
RouterModule.forRoot(routes),
|
RouterModule.forRoot(routes),
|
||||||
],
|
],
|
||||||
declarations: [UploadComponent, ProcessingComponent, ViewComponent],
|
declarations: [
|
||||||
|
UploadComponent,
|
||||||
|
ProcessingComponent,
|
||||||
|
ViewComponent,
|
||||||
|
LoginComponent,
|
||||||
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRouterModule {}
|
export class AppRouterModule {}
|
||||||
|
|
44
frontend/src/app/routes/login/login.component.html
Normal file
44
frontend/src/app/routes/login/login.component.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<div class="content-border">
|
||||||
|
<div class="container centered">
|
||||||
|
<form class="row" (ngSubmit)="onSubmit()">
|
||||||
|
<div class="col-12 py-2" *ngIf="loginFail">
|
||||||
|
<mat-error>
|
||||||
|
Failed to login. Please check your username and password.
|
||||||
|
</mat-error>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 py-2">
|
||||||
|
<mat-form-field appearance="outline" color="accent">
|
||||||
|
<mat-label>Username</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
type="text"
|
||||||
|
[formControl]="model.username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="model.username.errors">{{
|
||||||
|
model.usernameError
|
||||||
|
}}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 py-2">
|
||||||
|
<mat-form-field appearance="outline" color="accent">
|
||||||
|
<mat-label>Password</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
type="password"
|
||||||
|
[formControl]="model.password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="model.password.errors">{{
|
||||||
|
model.passwordError
|
||||||
|
}}</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 py-2">
|
||||||
|
<button mat-raised-button color="accent" type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
0
frontend/src/app/routes/login/login.component.scss
Normal file
0
frontend/src/app/routes/login/login.component.scss
Normal file
47
frontend/src/app/routes/login/login.component.ts
Normal file
47
frontend/src/app/routes/login/login.component.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { UserService } from 'src/app/api/user.service';
|
||||||
|
import { LoginControl } from './login.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss'],
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit, OnDestroy {
|
||||||
|
private userSubscription: Subscription;
|
||||||
|
|
||||||
|
model = new LoginControl();
|
||||||
|
loginFail = false;
|
||||||
|
|
||||||
|
constructor(private userService: UserService, private router: Router) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
console.log('init');
|
||||||
|
this.userSubscription = this.userService.user.subscribe((user) => {
|
||||||
|
console.log('sub', user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.userSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSubmit() {
|
||||||
|
const data = this.model.getData();
|
||||||
|
if (HasFailed(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.userService.login(data.username, data.password);
|
||||||
|
if (HasFailed(user)) {
|
||||||
|
console.warn(user);
|
||||||
|
this.loginFail = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
42
frontend/src/app/routes/login/login.model.ts
Normal file
42
frontend/src/app/routes/login/login.model.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { FormControl, Validators } from '@angular/forms';
|
||||||
|
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||||
|
import { LoginModel } from '../../models/login';
|
||||||
|
|
||||||
|
export class LoginControl {
|
||||||
|
public username = new FormControl('', [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(3),
|
||||||
|
]);
|
||||||
|
|
||||||
|
public password = new FormControl('', [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(3),
|
||||||
|
]);
|
||||||
|
|
||||||
|
public get usernameError() {
|
||||||
|
return this.username.hasError('required')
|
||||||
|
? 'Username is required'
|
||||||
|
: this.username.hasError('minlength')
|
||||||
|
? 'Username is too short'
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public get passwordError() {
|
||||||
|
return this.password.hasError('required')
|
||||||
|
? 'Password is required'
|
||||||
|
: this.password.hasError('minlength')
|
||||||
|
? 'Password is too short'
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getData(): Failable<LoginModel> {
|
||||||
|
if (this.username.errors || this.password.errors) {
|
||||||
|
return Fail('Invalid username or password');
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
username: this.username.value,
|
||||||
|
password: this.password.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,8 @@ export class ViewComponent implements OnInit {
|
||||||
private utilService: UtilService
|
private utilService: UtilService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public imageUrl: string;
|
public imageUrl: string = '';
|
||||||
public imageLinks: ImageLinks;
|
public imageLinks: ImageLinks = new ImageLinks();
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const params = this.route.snapshot.paramMap;
|
const params = this.route.snapshot.paramMap;
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
* Zone JS is required by default for Angular itself.
|
* Zone JS is required by default for Angular itself.
|
||||||
*/
|
*/
|
||||||
import 'zone.js'; // Included with Angular CLI.
|
import 'zone.js'; // Included with Angular CLI.
|
||||||
|
import 'reflect-metadata';
|
||||||
|
|
||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class AuthLoginRequest {
|
||||||
export class AuthLoginResponse {
|
export class AuthLoginResponse {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
access_token: string;
|
jwt_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthRegisterRequest {
|
export class AuthRegisterRequest {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export interface ImageLinks {
|
export class ImageLinks {
|
||||||
source: string;
|
source: string = '';
|
||||||
markdown: string;
|
markdown: string = '';
|
||||||
html: string;
|
html: string = '';
|
||||||
rst: string;
|
rst: string = '';
|
||||||
bbcode: string;
|
bbcode: string = '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,19 @@
|
||||||
import { Exclude } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
import {
|
import { IsDefined, IsEnum, IsHash, IsOptional } from 'class-validator';
|
||||||
IsDefined,
|
|
||||||
IsEnum,
|
|
||||||
IsHash,
|
|
||||||
IsOptional,
|
|
||||||
} from 'class-validator';
|
|
||||||
//import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
|
||||||
import { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
|
import { SupportedMime, SupportedMimes } from '../dto/mimes.dto';
|
||||||
|
|
||||||
//@Entity()
|
|
||||||
export class EImage {
|
export class EImage {
|
||||||
// @PrimaryGeneratedColumn()
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
// @Index()
|
|
||||||
// @Column({ unique: true })
|
|
||||||
@IsHash('sha256')
|
@IsHash('sha256')
|
||||||
hash: string;
|
hash: string;
|
||||||
|
|
||||||
// Binary data
|
// Binary data
|
||||||
// @Column({ type: 'bytea', nullable: false, select: false })
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Exclude()
|
@Exclude()
|
||||||
data?: Buffer;
|
data?: object;
|
||||||
|
|
||||||
// @Column({ enum: SupportedMimes })
|
|
||||||
@IsEnum(SupportedMimes)
|
@IsEnum(SupportedMimes)
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
mime: SupportedMime;
|
mime: SupportedMime;
|
||||||
|
|
|
@ -1,30 +1,20 @@
|
||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsDefined,
|
IsDefined,
|
||||||
IsEmpty,
|
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
// import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
|
||||||
|
|
||||||
// Different data for public and private
|
|
||||||
|
|
||||||
// @Entity()
|
|
||||||
export class EUser {
|
export class EUser {
|
||||||
// @PrimaryGeneratedColumn()
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
// @Index()
|
|
||||||
// @Column({ unique: true })
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
// @Column({ default: false })
|
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
|
||||||
// @Column({ select: false })
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Exclude()
|
@Exclude()
|
||||||
password?: string;
|
password?: string;
|
||||||
|
|
|
@ -4976,6 +4976,11 @@ jws@^3.2.2:
|
||||||
jwa "^1.4.1"
|
jwa "^1.4.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
jwt-decode@^3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||||
|
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
||||||
|
|
||||||
karma-chrome-launcher@~3.1.0:
|
karma-chrome-launcher@~3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738"
|
resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738"
|
||||||
|
|
Loading…
Reference in a new issue