Compare commits
5 Commits
e82a0ecb5e
...
95a593bdb6
Author | SHA1 | Date | |
---|---|---|---|
95a593bdb6 | |||
f4b25f428d | |||
b764f2a77b | |||
95165a0940 | |||
e9735a4e99 |
1202
package-lock.json
generated
1202
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -10,29 +10,29 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^18.1.1",
|
"@angular/animations": "^18.1.3",
|
||||||
"@angular/cdk": "~18.1.1",
|
"@angular/cdk": "~18.1.3",
|
||||||
"@angular/cdk-experimental": "^18.1.1",
|
"@angular/cdk-experimental": "^18.1.3",
|
||||||
"@angular/common": "^18.1.1",
|
"@angular/common": "^18.1.3",
|
||||||
"@angular/compiler": "^18.1.1",
|
"@angular/compiler": "^18.1.3",
|
||||||
"@angular/core": "^18.1.1",
|
"@angular/core": "^18.1.3",
|
||||||
"@angular/forms": "^18.1.1",
|
"@angular/forms": "^18.1.3",
|
||||||
"@angular/material": "~18.1.1",
|
"@angular/material": "~18.1.3",
|
||||||
"@angular/platform-browser": "^18.1.1",
|
"@angular/platform-browser": "^18.1.3",
|
||||||
"@angular/platform-browser-dynamic": "^18.1.1",
|
"@angular/platform-browser-dynamic": "^18.1.3",
|
||||||
"@angular/router": "^18.1.1",
|
"@angular/router": "^18.1.3",
|
||||||
"@progress/kendo-date-math": "^1.5.13",
|
"@progress/kendo-date-math": "^1.5.13",
|
||||||
"rxjs": "~7.8.1",
|
"rxjs": "~7.8.1",
|
||||||
"tslib": "^2.6.3",
|
"tslib": "^2.6.3",
|
||||||
"zone.js": "~0.14.8"
|
"zone.js": "~0.14.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^18.1.1",
|
"@angular-devkit/build-angular": "^18.1.3",
|
||||||
"@angular/cli": "^18.1.1",
|
"@angular/cli": "^18.1.3",
|
||||||
"@angular/compiler-cli": "^18.1.1",
|
"@angular/compiler-cli": "^18.1.3",
|
||||||
"@types/jasmine": "~5.1.4",
|
"@types/jasmine": "~5.1.4",
|
||||||
"jasmine-core": "~5.2.0",
|
"jasmine-core": "~5.2.0",
|
||||||
"karma": "~6.4.3",
|
"karma": "~6.4.4",
|
||||||
"karma-chrome-launcher": "~3.2.0",
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
"karma-coverage": "~2.2.1",
|
"karma-coverage": "~2.2.1",
|
||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
|
@ -98,7 +98,7 @@ export class RequestBuilder {
|
|||||||
data: null,
|
data: null,
|
||||||
silenceMode: false,
|
silenceMode: false,
|
||||||
withCredentials: false
|
withCredentials: false
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
|
@ -73,17 +73,16 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected get combinedUrl() {
|
protected get combinedUrl() {
|
||||||
return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, this.request.endpoint), this.request.queryParams)
|
return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, this.request.endpoint), this.request.queryParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put'): Observable<Type> {
|
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put'): Observable<Type> {
|
||||||
const doneEndpoint = this.combinedUrl;
|
const doneEndpoint = this.combinedUrl;
|
||||||
|
|
||||||
return this.tokenRefreshService.getTokenRefreshing$().pipe(
|
return this.tokenRefreshService.getTokenRefreshing$().pipe(
|
||||||
filter(refreshing => !refreshing),
|
filter(isRefreshing => !isRefreshing),
|
||||||
take(1),
|
switchMap(() =>
|
||||||
switchMap(_ => {
|
this.http.request<Type>(method, doneEndpoint, {
|
||||||
return this.http.request<Type>(method, doneEndpoint, {
|
|
||||||
withCredentials: this.request.withCredentials,
|
withCredentials: this.request.withCredentials,
|
||||||
headers: this.request.httpHeaders,
|
headers: this.request.httpHeaders,
|
||||||
body: this.request.data
|
body: this.request.data
|
||||||
@ -97,8 +96,8 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
|
|||||||
this.request = RequestBuilder.getStandardRequestData();
|
this.request = RequestBuilder.getStandardRequestData();
|
||||||
throw error;
|
throw error;
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
})
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +137,7 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
|
|||||||
return this;
|
return this;
|
||||||
|
|
||||||
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
|
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
|
||||||
authToken.keys().forEach(key => this.request.httpHeaders = this.request.httpHeaders.append(key, authToken.get(key) ?? ''))
|
authToken.keys().forEach(key => this.request.httpHeaders = this.request.httpHeaders.append(key, authToken.get(key) ?? ''));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
54
src/api/v1/authApiService.ts
Normal file
54
src/api/v1/authApiService.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import ApiService, {AvailableVersion} from "@api/api.service";
|
||||||
|
import {LoginRequest} from "@api/v1/loginRequest";
|
||||||
|
import {TokenResponse} from "@api/v1/tokenResponse";
|
||||||
|
import {catchError, of, tap} from "rxjs";
|
||||||
|
import {AuthRoles} from "@model/AuthRoles";
|
||||||
|
import {AuthService, AvailableAuthenticationProvider} from "@service/auth.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class AuthApiService extends ApiService {
|
||||||
|
public readonly basePath = 'Auth/';
|
||||||
|
public readonly version = AvailableVersion.v1;
|
||||||
|
|
||||||
|
public login(login: LoginRequest) {
|
||||||
|
return this.createRequestBuilder()
|
||||||
|
.setEndpoint('Login')
|
||||||
|
.setData(login)
|
||||||
|
.build<ApiService>()
|
||||||
|
.post<TokenResponse>()
|
||||||
|
.pipe(
|
||||||
|
tap(response => {
|
||||||
|
AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, this.createRequestBuilder().setEndpoint('ReLogin').build<AuthApiService>().combinedUrl);
|
||||||
|
this.tokenRefreshService.startTokenRefresh(response.expiresIn);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout() {
|
||||||
|
return this.createRequestBuilder()
|
||||||
|
.setWithCredentials()
|
||||||
|
.setEndpoint('Logout')
|
||||||
|
.build<ApiService>()
|
||||||
|
.addAuth()
|
||||||
|
.get()
|
||||||
|
.pipe(
|
||||||
|
tap(_ => {
|
||||||
|
localStorage.removeItem(ApiService.tokenKey);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRole(isSilence: boolean = true) {
|
||||||
|
return this.createRequestBuilder()
|
||||||
|
.setSilenceMode(isSilence)
|
||||||
|
.build<ApiService>()
|
||||||
|
.addAuth()
|
||||||
|
.get<AuthRoles>('GetRole')
|
||||||
|
.pipe(
|
||||||
|
catchError(_ => {
|
||||||
|
return of(null);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ export class ScheduleService extends ApiService {
|
|||||||
.post<ScheduleResponse[]>();
|
.post<ScheduleResponse[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByGroup(id : number, isEven: boolean | null = null, disciplines: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
public getByGroup(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
||||||
return this.createRequestBuilder()
|
return this.createRequestBuilder()
|
||||||
.setEndpoint('GetByGroup/' + id.toString())
|
.setEndpoint('GetByGroup/' + id.toString())
|
||||||
.setQueryParams({isEven: isEven, disciplines: disciplines, professors: professors, lectureHalls: lectureHalls})
|
.setQueryParams({isEven: isEven, disciplines: disciplines, professors: professors, lectureHalls: lectureHalls})
|
||||||
@ -34,7 +34,7 @@ export class ScheduleService extends ApiService {
|
|||||||
.get<ScheduleResponse[]>();
|
.get<ScheduleResponse[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByProfessor(id : number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
public getByProfessor(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
||||||
return this.createRequestBuilder()
|
return this.createRequestBuilder()
|
||||||
.setEndpoint('GetByProfessor/' + id.toString())
|
.setEndpoint('GetByProfessor/' + id.toString())
|
||||||
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, lectureHalls: lectureHalls})
|
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, lectureHalls: lectureHalls})
|
||||||
@ -42,7 +42,7 @@ export class ScheduleService extends ApiService {
|
|||||||
.get<ScheduleResponse[]>();
|
.get<ScheduleResponse[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByLectureHall(id : number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null) {
|
public getByLectureHall(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null) {
|
||||||
return this.createRequestBuilder()
|
return this.createRequestBuilder()
|
||||||
.setEndpoint('GetByLectureHall/' + id.toString())
|
.setEndpoint('GetByLectureHall/' + id.toString())
|
||||||
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, professors: professors})
|
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, professors: professors})
|
||||||
@ -50,7 +50,7 @@ export class ScheduleService extends ApiService {
|
|||||||
.get<ScheduleResponse[]>();
|
.get<ScheduleResponse[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByDiscipline(id : number, isEven: boolean | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
public getByDiscipline(id: number, isEven: boolean | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
|
||||||
return this.createRequestBuilder()
|
return this.createRequestBuilder()
|
||||||
.setEndpoint('GetByDiscipline/' + id.toString())
|
.setEndpoint('GetByDiscipline/' + id.toString())
|
||||||
.setQueryParams({isEven: isEven, groups: groups, professors: professors, lectureHalls: lectureHalls})
|
.setQueryParams({isEven: isEven, groups: groups, professors: professors, lectureHalls: lectureHalls})
|
||||||
|
@ -2,7 +2,7 @@ import {Component} from '@angular/core';
|
|||||||
import {RouterOutlet} from '@angular/router';
|
import {RouterOutlet} from '@angular/router';
|
||||||
import {FooterComponent} from "@component/common/footer/footer.component";
|
import {FooterComponent} from "@component/common/footer/footer.component";
|
||||||
import localeRu from '@angular/common/locales/ru';
|
import localeRu from '@angular/common/locales/ru';
|
||||||
import { registerLocaleData } from '@angular/common';
|
import {registerLocaleData} from '@angular/common';
|
||||||
import {FocusNextDirective} from "@/directives/focus-next.directive";
|
import {FocusNextDirective} from "@/directives/focus-next.directive";
|
||||||
import {TokenRefreshService} from "@service/token-refresh.service";
|
import {TokenRefreshService} from "@service/token-refresh.service";
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ApplicationConfig } from '@angular/core';
|
import {ApplicationConfig} from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import {provideRouter} from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import {routes} from './app.routes';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||||
import {provideHttpClient} from "@angular/common/http";
|
import {provideHttpClient} from "@angular/common/http";
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
|
@ -8,6 +8,7 @@ import {ScheduleComponent as SetupScheduleComponent} from "@page/setup/schedule/
|
|||||||
import {SetupComponent} from "@page/setup/setup.component";
|
import {SetupComponent} from "@page/setup/setup.component";
|
||||||
import {CreateAdminComponent} from "@page/setup/create-admin/create-admin.component";
|
import {CreateAdminComponent} from "@page/setup/create-admin/create-admin.component";
|
||||||
import {SummaryComponent} from "@page/setup/summary/summary.component";
|
import {SummaryComponent} from "@page/setup/summary/summary.component";
|
||||||
|
import {LoginComponent} from "@page/login/login.component";
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{path: '', title: 'Расписание', pathMatch: 'full', component: ScheduleComponent},
|
{path: '', title: 'Расписание', pathMatch: 'full', component: ScheduleComponent},
|
||||||
@ -22,7 +23,8 @@ export const routes: Routes = [
|
|||||||
{path: 'summary', component: SummaryComponent},
|
{path: 'summary', component: SummaryComponent},
|
||||||
{path: '', redirectTo: 'welcome', pathMatch: 'full'}
|
{path: '', redirectTo: 'welcome', pathMatch: 'full'}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
{path: 'login', title: 'Вход', component: LoginComponent},
|
||||||
/*{path: 'not-found', title: '404 страница не найдена'},
|
/*{path: 'not-found', title: '404 страница не найдена'},
|
||||||
{path: '**', redirectTo: '/not-found'}*/
|
{path: '**', redirectTo: '/not-found'}*/
|
||||||
];
|
];
|
||||||
|
34
src/directives/has-role.directive.ts
Normal file
34
src/directives/has-role.directive.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
|
import AuthApiService from "@api/v1/authApiService";
|
||||||
|
import {AuthRoles} from "@model/AuthRoles";
|
||||||
|
import {catchError, of} from "rxjs";
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[appHasRole]',
|
||||||
|
standalone: true,
|
||||||
|
providers: [AuthApiService]
|
||||||
|
})
|
||||||
|
export class HasRoleDirective {
|
||||||
|
constructor(
|
||||||
|
private templateRef: TemplateRef<any>,
|
||||||
|
private viewContainer: ViewContainerRef,
|
||||||
|
private authService: AuthApiService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Input() set appHasRole(role: AuthRoles) {
|
||||||
|
this.viewContainer.clear();
|
||||||
|
|
||||||
|
this.authService
|
||||||
|
.getRole()
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
this.viewContainer.clear();
|
||||||
|
return of(null);
|
||||||
|
}))
|
||||||
|
.subscribe(data => {
|
||||||
|
if (data === role)
|
||||||
|
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||||
|
else
|
||||||
|
this.viewContainer.clear();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
26
src/pages/login/login.component.css
Normal file
26
src/pages/login/login.component.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.formLogin {
|
||||||
|
display: flex;
|
||||||
|
padding: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 40vh;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formLogin mat-card {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formLogin p {
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.formLogin form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formLoginButton {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
76
src/pages/login/login.component.html
Normal file
76
src/pages/login/login.component.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<mat-sidenav-container class="formLogin">
|
||||||
|
|
||||||
|
<mat-card>
|
||||||
|
<p class="mat-h3">
|
||||||
|
Вход в систему
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form [formGroup]="loginForm">
|
||||||
|
<mat-form-field color="accent">
|
||||||
|
<mat-label>Имя пользователя/email</mat-label>
|
||||||
|
<input matInput
|
||||||
|
formControlName="user"
|
||||||
|
matTooltip='Укажите имя пользователя используя латинские буквы и цифры без пробелов или email'
|
||||||
|
required
|
||||||
|
focusNext="passwordNextFocus">
|
||||||
|
|
||||||
|
@if (loginForm.get('user')?.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
Имя пользователя или email является <i>обязательным</i>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (loginForm.get('user')?.hasError('minlength')) {
|
||||||
|
<mat-error>
|
||||||
|
Количество символов должно быть не менее 4
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field color="accent" style="margin-bottom: 20px">
|
||||||
|
<mat-label>Пароль</mat-label>
|
||||||
|
<input matInput
|
||||||
|
matTooltip="Укажите пароль"
|
||||||
|
formControlName="password"
|
||||||
|
required
|
||||||
|
[type]="hidePass ? 'password' : 'text'"
|
||||||
|
id="passwordNextFocus"
|
||||||
|
focusNext="loginNextFocus">
|
||||||
|
|
||||||
|
<button mat-icon-button matSuffix (click)="togglePassword($event)" [attr.aria-label]="'Hide password'"
|
||||||
|
[attr.aria-pressed]="hidePass">
|
||||||
|
<mat-icon>{{ hidePass ? 'visibility_off' : 'visibility' }}</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (loginForm.get('password')?.hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
Пароль является <i>обязательным</i>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (loginForm.get('password')?.hasError('minlength')) {
|
||||||
|
<mat-error>
|
||||||
|
Пароль должен быть не менее 8 символов
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<mat-error>
|
||||||
|
{{errorText}}
|
||||||
|
</mat-error>
|
||||||
|
|
||||||
|
<div class="formLoginButton">
|
||||||
|
@if (loaderActive) {
|
||||||
|
<app-data-spinner [scale]="40"/>
|
||||||
|
} @else {
|
||||||
|
<button mat-flat-button color="accent"
|
||||||
|
[disabled]="loginButtonIsDisable"
|
||||||
|
(click)="login()"
|
||||||
|
id="loginNextFocus">
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</mat-card>
|
||||||
|
</mat-sidenav-container>
|
95
src/pages/login/login.component.ts
Normal file
95
src/pages/login/login.component.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {MatSidenavContainer} from "@angular/material/sidenav";
|
||||||
|
import {MatFormFieldModule} from "@angular/material/form-field";
|
||||||
|
import {MatInput} from "@angular/material/input";
|
||||||
|
import {MatTooltip} from "@angular/material/tooltip";
|
||||||
|
import {MatIcon} from "@angular/material/icon";
|
||||||
|
import {MatButton, MatIconButton} from "@angular/material/button";
|
||||||
|
import {MatCard} from "@angular/material/card";
|
||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms";
|
||||||
|
import {FocusNextDirective} from "@/directives/focus-next.directive";
|
||||||
|
import {DataSpinnerComponent} from "@component/common/data-spinner/data-spinner.component";
|
||||||
|
import AuthApiService from "@api/v1/authApiService";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
import {catchError, of} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
MatSidenavContainer,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInput,
|
||||||
|
MatTooltip,
|
||||||
|
MatIcon,
|
||||||
|
MatIconButton,
|
||||||
|
MatButton,
|
||||||
|
MatCard,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FocusNextDirective,
|
||||||
|
DataSpinnerComponent
|
||||||
|
],
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrl: './login.component.css',
|
||||||
|
providers: [AuthApiService]
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
protected loginForm!: FormGroup;
|
||||||
|
protected hidePass: boolean = true;
|
||||||
|
protected loaderActive: boolean = false;
|
||||||
|
protected loginButtonIsDisable: boolean = true;
|
||||||
|
protected errorText: string = '';
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder, private auth: AuthApiService, private route: Router) {
|
||||||
|
this.auth.getRole()
|
||||||
|
.subscribe(data => {
|
||||||
|
if (data != null)
|
||||||
|
route.navigate(['admin']).then();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loginForm = this.formBuilder.group({
|
||||||
|
user: ['',],
|
||||||
|
password: ['',]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.loginForm.get('password')?.setValidators([
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(8)
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.loginForm.get('user')?.setValidators([
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(4)
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.loginForm.valueChanges.subscribe(() => {
|
||||||
|
this.loginButtonIsDisable = !this.loginForm.valid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected togglePassword(event: MouseEvent) {
|
||||||
|
this.hidePass = !this.hidePass;
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected login() {
|
||||||
|
this.loaderActive = true;
|
||||||
|
|
||||||
|
this.auth.login({
|
||||||
|
username: this.loginForm.get('user')?.value,
|
||||||
|
password: this.loginForm.get('password')?.value
|
||||||
|
})
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
this.loaderActive = false;
|
||||||
|
this.errorText = error.error;
|
||||||
|
this.loginButtonIsDisable = true;
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(_ => {
|
||||||
|
this.loaderActive = false;
|
||||||
|
this.errorText = '';
|
||||||
|
this.route.navigate(['admin']).then();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
<mat-sidenav-container class="schedule">
|
<mat-sidenav-container class="schedule">
|
||||||
<app-tabs (eventResult)="result($event)"/>
|
<app-tabs (eventResult)="result($event)"/>
|
||||||
<app-table-header [startWeek]="startWeek" [currentWeek]="currentWeek" (weekEvent)="handleWeekEvent($event)" #tableHeader/>
|
<app-table-header [startWeek]="startWeek" [currentWeek]="currentWeek" (weekEvent)="handleWeekEvent($event)"
|
||||||
|
#tableHeader/>
|
||||||
<app-table [currentWeek]="currentWeek" [startWeek]="startWeek" [data]="data" [isLoad]="isLoadTable"/>
|
<app-table [currentWeek]="currentWeek" [startWeek]="startWeek" [data]="data" [isLoad]="isLoadTable"/>
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
|
@ -61,7 +61,7 @@ export class AuthService {
|
|||||||
const token = localStorage.getItem(ApiService.tokenKey);
|
const token = localStorage.getItem(ApiService.tokenKey);
|
||||||
|
|
||||||
if (!token)
|
if (!token)
|
||||||
return of();
|
return of({} as TokenResponse);
|
||||||
|
|
||||||
const authToken = JSON.parse(token) as AuthToken;
|
const authToken = JSON.parse(token) as AuthToken;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {BehaviorSubject, interval, Subscription, switchMap} from "rxjs";
|
import {BehaviorSubject, filter, interval, Subscription, switchMap} from "rxjs";
|
||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {AuthService} from "@service/auth.service";
|
import {AuthService} from "@service/auth.service";
|
||||||
import {environment} from "@environment";
|
import {environment} from "@environment";
|
||||||
|
import ApiService from "@api/api.service";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@ -15,12 +16,12 @@ export class TokenRefreshService {
|
|||||||
this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn.getTime() - 1000 - Date.now());
|
this.setRefreshTokenExpireMs(AuthService.tokenExpiresIn.getTime() - 1000 - Date.now());
|
||||||
|
|
||||||
authService.tokenChangeError.subscribe(_ => {
|
authService.tokenChangeError.subscribe(_ => {
|
||||||
console.log('Token change error event received');
|
console.debug('Token change error event received');
|
||||||
this.tokenRefreshing$.next(false);
|
this.tokenRefreshing$.next(false);
|
||||||
this.stopTokenRefresh();
|
this.stopTokenRefresh();
|
||||||
});
|
});
|
||||||
authService.expireTokenChange.subscribe(date => {
|
authService.expireTokenChange.subscribe(date => {
|
||||||
console.log('Expire token change event received:', date);
|
console.debug('Expire token change event received:', date);
|
||||||
this.setRefreshTokenExpireMs(date.getTime() - 1000 - Date.now());
|
this.setRefreshTokenExpireMs(date.getTime() - 1000 - Date.now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -29,22 +30,26 @@ export class TokenRefreshService {
|
|||||||
if (date)
|
if (date)
|
||||||
this.refreshTokenExpireMs = new Date(date).getTime() - 1000 - Date.now();
|
this.refreshTokenExpireMs = new Date(date).getTime() - 1000 - Date.now();
|
||||||
|
|
||||||
if (!this.tokenRefreshSubscription || this.tokenRefreshSubscription.closed) {
|
console.debug(this.tokenRefreshSubscription);
|
||||||
this.tokenRefreshSubscription = interval(this.refreshTokenExpireMs).pipe(
|
if (this.tokenRefreshSubscription && !this.tokenRefreshSubscription.closed)
|
||||||
switchMap(() => {
|
return;
|
||||||
this.tokenRefreshing$.next(true);
|
|
||||||
return this.authService.refreshToken();
|
this.tokenRefreshSubscription = interval(this.refreshTokenExpireMs).pipe(
|
||||||
})
|
filter(isRefreshing => !isRefreshing),
|
||||||
).subscribe({
|
switchMap(() => {
|
||||||
next: (_) => {
|
this.tokenRefreshing$.next(true);
|
||||||
this.tokenRefreshing$.next(false);
|
console.debug('Send query to refresh token');
|
||||||
},
|
return this.authService.refreshToken();
|
||||||
error: error => {
|
})
|
||||||
console.error('Token refresh error:', error);
|
).subscribe({
|
||||||
this.tokenRefreshing$.next(false);
|
next: (_) => {
|
||||||
}
|
this.tokenRefreshing$.next(false);
|
||||||
});
|
},
|
||||||
}
|
error: error => {
|
||||||
|
this.tokenRefreshing$.next(false);
|
||||||
|
localStorage.removeItem(ApiService.tokenKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTokenRefreshing$(): BehaviorSubject<boolean> {
|
public getTokenRefreshing$(): BehaviorSubject<boolean> {
|
||||||
@ -60,13 +65,12 @@ export class TokenRefreshService {
|
|||||||
|
|
||||||
public setRefreshTokenExpireMs(expireMs: number): void {
|
public setRefreshTokenExpireMs(expireMs: number): void {
|
||||||
if (expireMs < environment.retryDelay)
|
if (expireMs < environment.retryDelay)
|
||||||
expireMs = 3000;
|
expireMs = environment.retryDelay;
|
||||||
|
|
||||||
console.log(expireMs);
|
|
||||||
this.refreshTokenExpireMs = expireMs;
|
this.refreshTokenExpireMs = expireMs;
|
||||||
|
|
||||||
|
console.log(expireMs);
|
||||||
this.stopTokenRefresh();
|
this.stopTokenRefresh();
|
||||||
this.startTokenRefresh();
|
this.startTokenRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
3
src/shared/structs/AuthRoles.ts
Normal file
3
src/shared/structs/AuthRoles.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export enum AuthRoles {
|
||||||
|
Admin
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user