mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-01 18:05:47 +01:00
add register view
This commit is contained in:
@@ -63,14 +63,6 @@ export class UserController {
|
||||
throw new InternalServerErrorException('Could not register user');
|
||||
}
|
||||
|
||||
if (register.isAdmin) {
|
||||
const result = await this.usersService.addRoles(user, ['admin']);
|
||||
if (HasFailed(result)) {
|
||||
this.logger.warn(result.getReason());
|
||||
throw new InternalServerErrorException('Could not add admin role');
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { validate } from 'class-validator';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import {
|
||||
UserLoginRequest,
|
||||
UserLoginResponse, UserMeResponse
|
||||
UserLoginResponse, UserMeResponse, UserRegisterRequest, UserRegisterResponse
|
||||
} from 'picsur-shared/dist/dto/api/user.dto';
|
||||
import { JwtDataDto } from 'picsur-shared/dist/dto/jwt.dto';
|
||||
import { EUser } from 'picsur-shared/dist/entities/user.entity';
|
||||
@@ -60,6 +60,22 @@ export class UserService {
|
||||
return user;
|
||||
}
|
||||
|
||||
public async register(username: string, password: string): AsyncFailable<EUser> {
|
||||
const request: UserRegisterRequest = {
|
||||
username,
|
||||
password,
|
||||
};
|
||||
|
||||
const response = await this.api.post(
|
||||
UserRegisterRequest,
|
||||
UserRegisterResponse,
|
||||
'/api/user/register',
|
||||
request
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async logout(): AsyncFailable<EUser> {
|
||||
const value = this.userSubject.getValue();
|
||||
this.key.clear();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivate,
|
||||
Router,
|
||||
RouterStateSnapshot
|
||||
} from '@angular/router';
|
||||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
@@ -12,7 +13,10 @@ import { PermissionService } from '../api/permission.service';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PermissionGuard implements CanActivate {
|
||||
constructor(private permissionService: PermissionService) {}
|
||||
constructor(
|
||||
private permissionService: PermissionService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const requiredPermissions: Permissions = route.data['permissions'];
|
||||
@@ -28,6 +32,9 @@ export class PermissionGuard implements CanActivate {
|
||||
ourPermissions.includes(permission)
|
||||
);
|
||||
|
||||
if (!isOk) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
return isOk;
|
||||
}
|
||||
}
|
||||
|
||||
17
frontend/src/app/models/forms/compare.validator.ts
Normal file
17
frontend/src/app/models/forms/compare.validator.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import {
|
||||
AbstractControl,
|
||||
FormControl,
|
||||
ValidationErrors,
|
||||
ValidatorFn
|
||||
} from '@angular/forms';
|
||||
|
||||
export function Compare(compareTo: FormControl): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
if (control.value !== compareTo.value) {
|
||||
return {
|
||||
compare: true,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import { LoginModel } from '../../models/login';
|
||||
import { UserPassModel } from './userpass';
|
||||
|
||||
export class LoginControl {
|
||||
public username = new FormControl('', [
|
||||
@@ -29,7 +29,7 @@ export class LoginControl {
|
||||
: '';
|
||||
}
|
||||
|
||||
public getData(): Failable<LoginModel> {
|
||||
public getData(): Failable<UserPassModel> {
|
||||
if (this.username.errors || this.password.errors) {
|
||||
return Fail('Invalid username or password');
|
||||
} else {
|
||||
@@ -39,4 +39,16 @@ export class LoginControl {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getRawData(): UserPassModel {
|
||||
return {
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
};
|
||||
}
|
||||
|
||||
public putData(data: UserPassModel) {
|
||||
this.username.setValue(data.username);
|
||||
this.password.setValue(data.password);
|
||||
}
|
||||
}
|
||||
76
frontend/src/app/models/forms/register.model.ts
Normal file
76
frontend/src/app/models/forms/register.model.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import { Fail, Failable } from 'picsur-shared/dist/types';
|
||||
import { Compare } from './compare.validator';
|
||||
import { UserPassModel } from './userpass';
|
||||
|
||||
export class RegisterControl {
|
||||
public username = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
|
||||
public password = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
]);
|
||||
|
||||
public passwordConfirm = new FormControl('', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
Compare(this.password),
|
||||
]);
|
||||
|
||||
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 get passwordConfirmError() {
|
||||
return this.passwordConfirm.hasError('required')
|
||||
? 'Password confirmation is required'
|
||||
: this.passwordConfirm.hasError('minlength')
|
||||
? 'Password confirmation is too short'
|
||||
: this.passwordConfirm.hasError('compare')
|
||||
? 'Password confirmation does not match'
|
||||
: '';
|
||||
}
|
||||
|
||||
public getData(): Failable<UserPassModel> {
|
||||
if (
|
||||
this.username.errors ||
|
||||
this.password.errors ||
|
||||
this.passwordConfirm.errors
|
||||
) {
|
||||
return Fail('Invalid username or password');
|
||||
} else {
|
||||
return {
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getRawData(): UserPassModel {
|
||||
return {
|
||||
username: this.username.value,
|
||||
password: this.password.value,
|
||||
};
|
||||
}
|
||||
|
||||
public putData(data: UserPassModel) {
|
||||
this.username.setValue(data.username);
|
||||
this.password.setValue(data.password);
|
||||
this.passwordConfirm.setValue(data.password);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface LoginModel {
|
||||
export interface UserPassModel {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
@@ -4,38 +4,19 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgxDropzoneModule } from 'ngx-dropzone';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { ApiModule } from '../api/api.module';
|
||||
import { CopyFieldModule } from '../components/copyfield/copyfield.module';
|
||||
import { PageNotFoundComponent } from '../components/pagenotfound/pagenotfound.component';
|
||||
import { PageNotFoundModule } from '../components/pagenotfound/pagenotfound.module';
|
||||
import { GuardsModule } from '../guards/guards.module';
|
||||
import { PermissionGuard } from '../guards/permission.guard';
|
||||
import { LoginComponent } from '../routes/login/login.component';
|
||||
import { ProcessingComponent } from '../routes/processing/processing.component';
|
||||
import { RegisterComponent } from '../routes/register/register.component';
|
||||
import { UploadComponent } from '../routes/upload/upload.component';
|
||||
import { ViewComponent } from '../routes/view/view.component';
|
||||
import { UtilModule } from '../util/util.module';
|
||||
|
||||
// TODO: split up router
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: UploadComponent },
|
||||
{
|
||||
path: 'processing',
|
||||
component: ProcessingComponent,
|
||||
},
|
||||
{ path: 'view/:hash', component: ViewComponent },
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.UserLogin] },
|
||||
},
|
||||
{ path: '**', component: PageNotFoundComponent },
|
||||
];
|
||||
import { routes } from './routes';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -59,6 +40,7 @@ const routes: Routes = [
|
||||
ProcessingComponent,
|
||||
ViewComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
|
||||
37
frontend/src/app/router/routes.ts
Normal file
37
frontend/src/app/router/routes.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { Permission } from 'picsur-shared/dist/dto/permissions';
|
||||
import { PageNotFoundComponent } from '../components/pagenotfound/pagenotfound.component';
|
||||
import { PermissionGuard } from '../guards/permission.guard';
|
||||
import { LoginComponent } from '../routes/login/login.component';
|
||||
import { ProcessingComponent } from '../routes/processing/processing.component';
|
||||
import { RegisterComponent } from '../routes/register/register.component';
|
||||
import { UploadComponent } from '../routes/upload/upload.component';
|
||||
import { ViewComponent } from '../routes/view/view.component';
|
||||
|
||||
// TODO: split up router
|
||||
export const routes: Routes = [
|
||||
{ path: '', component: UploadComponent },
|
||||
{
|
||||
path: 'processing',
|
||||
component: ProcessingComponent,
|
||||
},
|
||||
{
|
||||
path: 'view/:hash',
|
||||
component: ViewComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.ImageView] },
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.UserLogin] },
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
component: RegisterComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.UserRegister] },
|
||||
},
|
||||
{ path: '**', component: PageNotFoundComponent },
|
||||
];
|
||||
@@ -1,6 +1,9 @@
|
||||
<div class="content-border">
|
||||
<div class="content-border padded">
|
||||
<div class="container centered">
|
||||
<form class="row" (ngSubmit)="onSubmit()">
|
||||
<div class="col-12 py-2">
|
||||
<h1>Login</h1>
|
||||
</div>
|
||||
<div class="col-12 py-2" *ngIf="loginFail">
|
||||
<mat-error>
|
||||
Failed to login. Please check your username and password.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import { PermissionService } from 'src/app/api/permission.service';
|
||||
import { UserService } from 'src/app/api/user.service';
|
||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||
import { UtilService } from 'src/app/util/util.service';
|
||||
import { LoginControl } from './login.model';
|
||||
import { LoginControl } from '../../models/forms/login.model';
|
||||
import { UserPassModel } from '../../models/forms/userpass';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
@@ -34,9 +35,10 @@ export class LoginComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.userService.isLoggedIn) {
|
||||
this.router.navigate(['/'], { replaceUrl: true });
|
||||
return;
|
||||
const state = history.state as UserPassModel;
|
||||
if (state) {
|
||||
this.model.putData(state);
|
||||
history.replaceState(null, '');
|
||||
}
|
||||
|
||||
this.onPermissions();
|
||||
@@ -67,8 +69,8 @@ export class LoginComponent implements OnInit {
|
||||
}
|
||||
|
||||
async onRegister() {
|
||||
//prevent default
|
||||
|
||||
console.log('click');
|
||||
this.router.navigate(['/register'], {
|
||||
state: this.model.getRawData(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { UtilService } from 'src/app/util/util.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './processing.component.html',
|
||||
styleUrls: ['./processing.component.scss'],
|
||||
})
|
||||
export class ProcessingComponent implements OnInit {
|
||||
constructor(
|
||||
|
||||
72
frontend/src/app/routes/register/register.component.html
Normal file
72
frontend/src/app/routes/register/register.component.html
Normal file
@@ -0,0 +1,72 @@
|
||||
<div class="content-border padded">
|
||||
<div class="container centered">
|
||||
<form class="row" (ngSubmit)="onSubmit()">
|
||||
<div class="col-12 py-2">
|
||||
<h1>Register</h1>
|
||||
</div>
|
||||
<div class="col-12 py-2" *ngIf="registerFail">
|
||||
<mat-error> Failed to register. </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">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
<mat-label>Confirm Password</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="password"
|
||||
[formControl]="model.passwordConfirm"
|
||||
name="confirmpassword"
|
||||
required
|
||||
/>
|
||||
<mat-error *ngIf="model.passwordConfirm.errors">{{
|
||||
model.passwordConfirmError
|
||||
}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12 py-2">
|
||||
<button mat-raised-button color="accent" class="mx-2" type="submit">
|
||||
Register
|
||||
</button>
|
||||
<button
|
||||
*ngIf="showLogin"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="mx-2"
|
||||
(click)="onLogin()"
|
||||
type="button"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
85
frontend/src/app/routes/register/register.component.ts
Normal file
85
frontend/src/app/routes/register/register.component.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { PermissionService } from 'src/app/api/permission.service';
|
||||
import { UserService } from 'src/app/api/user.service';
|
||||
import { UserPassModel } from 'src/app/models/forms/userpass';
|
||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||
import { UtilService } from 'src/app/util/util.service';
|
||||
import { RegisterControl } from '../../models/forms/register.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.scss'],
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
private readonly logger = console;
|
||||
|
||||
private permissions: Permissions = [];
|
||||
|
||||
public get showLogin() {
|
||||
return this.permissions.includes(Permission.UserLogin);
|
||||
}
|
||||
|
||||
model = new RegisterControl();
|
||||
registerFail = false;
|
||||
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private permissionService: PermissionService,
|
||||
private router: Router,
|
||||
private utilService: UtilService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
const state = history.state as UserPassModel;
|
||||
if (state) {
|
||||
this.model.putData(state);
|
||||
history.replaceState(null, '');
|
||||
}
|
||||
|
||||
this.onPermissions();
|
||||
}
|
||||
|
||||
@AutoUnsubscribe()
|
||||
onPermissions() {
|
||||
return this.permissionService.live.subscribe((permissions) => {
|
||||
this.permissions = permissions;
|
||||
});
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
const data = this.model.getData();
|
||||
if (HasFailed(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await this.userService.register(data.username, data.password);
|
||||
if (HasFailed(user)) {
|
||||
this.logger.warn(user);
|
||||
this.registerFail = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.utilService.showSnackBar('Register successful', SnackBarType.Success);
|
||||
|
||||
if (!this.userService.isLoggedIn) {
|
||||
const loginResult = this.userService.login(data.username, data.password);
|
||||
if (HasFailed(loginResult)) {
|
||||
this.logger.warn(loginResult);
|
||||
this.utilService.showSnackBar('Failed to login', SnackBarType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
async onLogin() {
|
||||
this.router.navigate(['/login'], {
|
||||
state: this.model.getRawData(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="content-border">
|
||||
<div class="content-border padded">
|
||||
<div class="container centered">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
||||
@@ -4,3 +4,8 @@
|
||||
height: initial !important;
|
||||
width: initial !important;
|
||||
}
|
||||
|
||||
form mat-form-field {
|
||||
width: inherit;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDefined,
|
||||
IsArray, IsDefined,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsNotEmpty, IsPositive,
|
||||
IsString,
|
||||
ValidateNested
|
||||
} from 'class-validator';
|
||||
@@ -43,10 +39,6 @@ export class UserRegisterRequest {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isAdmin?: boolean;
|
||||
}
|
||||
|
||||
export class UserRegisterResponse extends EUser {}
|
||||
|
||||
Reference in New Issue
Block a user